public inbox for devicetree@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520)
@ 2025-12-24 16:11 ` Icenowy Zheng
  2025-12-24 16:11   ` [PATCH v4 1/9] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
                     ` (9 more replies)
  0 siblings, 10 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:11 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng

This patchset tries to add a driver for Verisilicon DC8200 driver, and
demonstrates the driver on T-Head TH1520 with its HDMI output.

This display controller IP is used on StarFive JH7110 too, but as the
HDMI controller used there isn't as common as the DesignWare one, I
choose to use TH1520 in this patchset.

The DC driver is written with other DC-series (mainly DC8000, which is
known to be used on Eswin EIC7700 SoC) display controllers in mind, and
uses the identification registers available on all Vivante branded IPs.
A known exception is DCNano display controller, which is unlikely to be
supported by this driver because of totally different register map and
no known identification registers. (P.S. the in-tree loongson DRM driver
seems to be for some DCNano instances based on the register map.)

The HDMI controller seems to come with some common PHY by Synopsys, the
DesignWare HDMI TX 2.0 PHY. By searching a few register names from the
BSP driver of that PHY, that PHY seems to be used by a in-tree dw-hdmi
glue, rcar_dw_hdmi -- an updated downstream version of rcar_dw_hdmi
contains all 6 registers set here in the th1520-dw-hdmi driver. Some
more suprising thing is that RK3288 uses the same PHY too, but the
in-tree dw_hdmi-rockchip driver writes the configuration data array in a
weird way to reuse the HDMI 3D TX PHY configuring function. It might be
valuable to add common configuring function and configuration data
definition for this HDMI 2.0 PHY too, but the current driver in this
patchset simply duplicated most configuration logic from rcar_dw_hdmi
driver (but with 3 extra configuration registers configured).

Icenowy Zheng (9):
  dt-bindings: vendor-prefixes: add verisilicon
  dt-bindings: display: add verisilicon,dc
  drm: verisilicon: add a driver for Verisilicon display controllers
  dt-bindings: display/bridge: add binding for TH1520 HDMI controller
  drm/bridge: add a driver for T-Head TH1520 HDMI controller
  riscv: dts: thead: add DPU and HDMI device tree nodes
  riscv: dts: thead: lichee-pi-4a: enable HDMI
  MAINTAINERS: assign myself as maintainer for verisilicon DC driver
  mailmap: map all Icenowy Zheng's mail addresses

 .mailmap                                      |   4 +
 .../display/bridge/thead,th1520-dw-hdmi.yaml  | 120 +++++++
 .../bindings/display/verisilicon,dc.yaml      | 144 ++++++++
 .../devicetree/bindings/vendor-prefixes.yaml  |   2 +
 MAINTAINERS                                   |   8 +
 .../boot/dts/thead/th1520-lichee-pi-4a.dts    |  26 +-
 arch/riscv/boot/dts/thead/th1520.dtsi         |  70 ++++
 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/bridge/Kconfig                |  10 +
 drivers/gpu/drm/bridge/Makefile               |   1 +
 drivers/gpu/drm/bridge/th1520-dw-hdmi.c       | 173 +++++++++
 drivers/gpu/drm/verisilicon/Kconfig           |  15 +
 drivers/gpu/drm/verisilicon/Makefile          |   5 +
 drivers/gpu/drm/verisilicon/vs_bridge.c       | 331 ++++++++++++++++++
 drivers/gpu/drm/verisilicon/vs_bridge.h       |  40 +++
 drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
 drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 ++++++++++++
 drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
 drivers/gpu/drm/verisilicon/vs_dc.c           | 205 +++++++++++
 drivers/gpu/drm/verisilicon/vs_dc.h           |  39 +++
 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
 drivers/gpu/drm/verisilicon/vs_drm.c          | 177 ++++++++++
 drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
 drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
 drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
 drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
 .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 +++++++++
 .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
 31 files changed, 2347 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
 create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
 create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
 create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
 create mode 100644 drivers/gpu/drm/verisilicon/Makefile
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h

-- 
2.52.0


^ permalink raw reply	[flat|nested] 33+ messages in thread

* [PATCH v4 1/9] dt-bindings: vendor-prefixes: add verisilicon
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
@ 2025-12-24 16:11   ` Icenowy Zheng
  2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
                     ` (8 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:11 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

VeriSilicon is a Silicon IP vendor, which is the current owner of
Vivante series video-related IPs and Hantro series video codec IPs.

Add a vendor prefix for this company.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Acked-by: Rob Herring (Arm) <robh@kernel.org>
---
No changes in v4.

Changes in v3:
- Add Rob's ACK.

No changes in v2.

 Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index c7591b2aec2a7..18f931f369198 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1745,6 +1745,8 @@ patternProperties:
     description: Variscite Ltd.
   "^vdl,.*":
     description: Van der Laan b.v.
+  "^verisilicon,.*":
+    description: VeriSilicon Microelectronics (Shanghai) Co., Ltd.
   "^vertexcom,.*":
     description: Vertexcom Technologies, Inc.
   "^via,.*":
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
  2025-12-24 16:11   ` [PATCH v4 1/9] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
@ 2025-12-24 16:11   ` Icenowy Zheng
  2025-12-25  9:35     ` Han Gao (Revy)
                       ` (2 more replies)
  2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
                     ` (7 subsequent siblings)
  9 siblings, 3 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:11 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

Verisilicon has a series of display controllers prefixed with DC and
with self-identification facility like their GC series GPUs.

Add a device tree binding for it.

Depends on the specific DC model, it can have either one or two display
outputs, and each display output could be set to DPI signal or "DP"
signal (which seems to be some plain parallel bus to HDMI controllers).

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
Changes in v4:
- Added a comment for "verisilicon,dc" that says the ID/revision is
  discoverable via registers.
- Removed clock minItems constraint w/o specific compatible strings.

Changes in v3:
- Added SoC-specific compatible string, and arm the binding with clock /
  port checking for the specific SoC (with a 2-output DC).

Changes in v2:
- Fixed misspelt "versilicon" in title.
- Moved minItems in clock properties to be earlier than items.
- Re-aligned multi-line clocks and resets in example.

 .../bindings/display/verisilicon,dc.yaml      | 144 ++++++++++++++++++
 1 file changed, 144 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml

diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
new file mode 100644
index 0000000000000..fe64cc1466690
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
@@ -0,0 +1,144 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Verisilicon DC-series display controllers
+
+maintainers:
+  - Icenowy Zheng <uwu@icenowy.me>
+
+properties:
+  $nodename:
+    pattern: "^display@[0-9a-f]+$"
+
+  compatible:
+    items:
+      - enum:
+          - thead,th1520-dc8200
+      - const: verisilicon,dc # DC IPs have discoverable ID/revision registers
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  clocks:
+    items:
+      - description: DC Core clock
+      - description: DMA AXI bus clock
+      - description: Configuration AHB bus clock
+      - description: Pixel clock of output 0
+      - description: Pixel clock of output 1
+
+  clock-names:
+    items:
+      - const: core
+      - const: axi
+      - const: ahb
+      - const: pix0
+      - const: pix1
+
+  resets:
+    items:
+      - description: DC Core reset
+      - description: DMA AXI bus reset
+      - description: Configuration AHB bus reset
+
+  reset-names:
+    items:
+      - const: core
+      - const: axi
+      - const: ahb
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: The first output channel , endpoint 0 should be
+          used for DPI format output and endpoint 1 should be used
+          for DP format output.
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: The second output channel if the DC variant
+          supports. Follow the same endpoint addressing rule with
+          the first port.
+
+    required:
+      - port@0
+
+required:
+  - compatible
+  - reg
+  - interrupts
+  - clocks
+  - clock-names
+  - ports
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: thead,th1520-dc8200
+    then:
+      properties:
+        clocks:
+          minItems: 5
+        ports:
+          required:
+            - port@0
+            - port@1
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
+    #include <dt-bindings/reset/thead,th1520-reset.h>
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      display@ffef600000 {
+        compatible = "thead,th1520-dc8200", "verisilicon,dc";
+        reg = <0xff 0xef600000 0x0 0x100000>;
+        interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk_vo CLK_DPU_CCLK>,
+                 <&clk_vo CLK_DPU_ACLK>,
+                 <&clk_vo CLK_DPU_HCLK>,
+                 <&clk_vo CLK_DPU_PIXELCLK0>,
+                 <&clk_vo CLK_DPU_PIXELCLK1>;
+        clock-names = "core", "axi", "ahb", "pix0", "pix1";
+        resets = <&rst TH1520_RESET_ID_DPU_CORE>,
+                 <&rst TH1520_RESET_ID_DPU_AXI>,
+                 <&rst TH1520_RESET_ID_DPU_AHB>;
+        reset-names = "core", "axi", "ahb";
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          port@0 {
+            reg = <0>;
+          };
+
+          port@1 {
+            reg = <1>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            dpu_out_dp1: endpoint@1 {
+              reg = <1>;
+              remote-endpoint = <&hdmi_in>;
+            };
+          };
+        };
+      };
+    };
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
  2025-12-24 16:11   ` [PATCH v4 1/9] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
  2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
@ 2025-12-24 16:11   ` Icenowy Zheng
  2025-12-25  9:36     ` Han Gao (Revy)
                       ` (2 more replies)
  2025-12-24 16:12   ` [PATCH v4 4/9] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
                     ` (6 subsequent siblings)
  9 siblings, 3 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:11 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

This is a from-scratch driver targeting Verisilicon DC-series display
controllers, which feature self-identification functionality like their
GC-series GPUs.

Only DC8200 is being supported now, and only the main framebuffer is set
up (as the DRM primary plane). Support for more DC models and more
features is my further targets.

As the display controller is delivered to SoC vendors as a whole part,
this driver does not use component framework and extra bridges inside a
SoC is expected to be implemented as dedicated bridges (this driver
properly supports bridge chaining).

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
Changes in v4:
- Switch to drm_* logger when we're handling with struct drm_device.

Changes in v3:
- Get rid of drm_atomic_get_existing_crtc_state() which is marked
  deprecated.

Changes in v2:
- Changed some Control flows according to previous reviews.
- Added missing of_node_put when checking of endpoints for output type.
- Switched all userspace-visible modeset objects to be managed by drmm
  instead of devm.
- Utilize devm_drm_bridge_alloc() in internal bridge.
- Prevented the usage of simple encoder helpers by passing a NULL funcs pointer.
- Let devm enable clocks when getting them.
- Removed explicit `.cache_type = REGCACHE_NONE` in regmap config.
- Fixed a debug print using a variable before initialization.
- Fixed a wrong index when using bulk to handle resets.
- Added missing configuration for DPI format (currently fixed RGB888).

 drivers/gpu/drm/Kconfig                       |   2 +
 drivers/gpu/drm/Makefile                      |   1 +
 drivers/gpu/drm/verisilicon/Kconfig           |  15 +
 drivers/gpu/drm/verisilicon/Makefile          |   5 +
 drivers/gpu/drm/verisilicon/vs_bridge.c       | 331 ++++++++++++++++++
 drivers/gpu/drm/verisilicon/vs_bridge.h       |  40 +++
 drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
 drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 ++++++++++++
 drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
 drivers/gpu/drm/verisilicon/vs_dc.c           | 205 +++++++++++
 drivers/gpu/drm/verisilicon/vs_dc.h           |  39 +++
 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
 drivers/gpu/drm/verisilicon/vs_drm.c          | 177 ++++++++++
 drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
 drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
 drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
 drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
 drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
 .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 +++++++++
 .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
 21 files changed, 1790 insertions(+)
 create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
 create mode 100644 drivers/gpu/drm/verisilicon/Makefile
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
 create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 7e6bc0b3a589c..41363da2cc59f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -398,6 +398,8 @@ source "drivers/gpu/drm/imagination/Kconfig"
 
 source "drivers/gpu/drm/tyr/Kconfig"
 
+source "drivers/gpu/drm/verisilicon/Kconfig"
+
 config DRM_HYPERV
 	tristate "DRM Support for Hyper-V synthetic video device"
 	depends on DRM && PCI && HYPERV_VMBUS
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 0e1c668b46d21..f2dfa0ad0ab78 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -235,6 +235,7 @@ obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
+obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon/
 
 # Ensure drm headers are self-contained and pass kernel-doc
 hdrtest-files := \
diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig
new file mode 100644
index 0000000000000..0235577c72824
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_VERISILICON_DC
+	tristate "DRM Support for Verisilicon DC-series display controllers"
+	depends on DRM && COMMON_CLK
+	depends on RISCV || COMPILER_TEST
+	select DRM_CLIENT_SELECTION
+	select DRM_GEM_DMA_HELPER
+	select DRM_KMS_HELPER
+	select DRM_BRIDGE_CONNECTOR
+	select REGMAP_MMIO
+	select VIDEOMODE_HELPERS
+	help
+	  Choose this option if you have a SoC with Verisilicon DC-series
+	  display controllers. If M is selected, the module will be called
+	  verisilicon-dc.
diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile
new file mode 100644
index 0000000000000..fd8d805fbcde1
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o
+
+obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c
new file mode 100644
index 0000000000000..90e4aa8012fae
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
@@ -0,0 +1,331 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#include <uapi/linux/media-bus-format.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_print.h>
+#include <drm/drm_simple_kms_helper.h>
+
+#include "vs_bridge.h"
+#include "vs_bridge_regs.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+
+static int vs_bridge_attach(struct drm_bridge *bridge,
+			    struct drm_encoder *encoder,
+			    enum drm_bridge_attach_flags flags)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	return drm_bridge_attach(encoder, vbridge->next,
+				 bridge, flags);
+}
+
+struct vsdc_dp_format {
+	u32 linux_fmt;
+	bool is_yuv;
+	u32 vsdc_fmt;
+};
+
+static struct vsdc_dp_format vsdc_dp_supported_fmts[] = {
+	/* default to RGB888 */
+	{ MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
+	{ MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
+	{ MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 },
+	{ MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 },
+	{ MEDIA_BUS_FMT_RGB101010_1X30,
+	  false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 },
+	{ MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 },
+	{ MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 },
+	{ MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 },
+	{ MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 },
+	{ MEDIA_BUS_FMT_UYYVYY8_0_5X24,
+	  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 },
+	{ MEDIA_BUS_FMT_UYYVYY10_0_5X30,
+	  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 },
+};
+
+static u32 *vs_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state,
+					unsigned int *num_output_fmts)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	u32 *output_fmts;
+	unsigned int i;
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
+		*num_output_fmts = 2;
+	else
+		*num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts);
+
+	output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
+			      GFP_KERNEL);
+	if (!output_fmts)
+		return NULL;
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) {
+		/* TODO: support more DPI output formats */
+		output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
+		output_fmts[1] = MEDIA_BUS_FMT_FIXED;
+	} else {
+		for (i = 0; i < *num_output_fmts; i++)
+			output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
+	}
+
+	return output_fmts;
+}
+
+static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++)
+		if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt)
+			return true;
+
+	return false;
+}
+
+static u32 *vs_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
+					struct drm_bridge_state *bridge_state,
+					struct drm_crtc_state *crtc_state,
+					struct drm_connector_state *conn_state,
+					u32 output_fmt,
+					unsigned int *num_input_fmts)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
+	    !vs_bridge_out_dp_fmt_supported(output_fmt)) {
+		*num_input_fmts = 0;
+		return NULL;
+	}
+
+	return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
+							  crtc_state,
+							  conn_state,
+							  output_fmt,
+							  num_input_fmts);
+}
+
+static int vs_bridge_atomic_check(struct drm_bridge *bridge,
+				  struct drm_bridge_state *bridge_state,
+				  struct drm_crtc_state *crtc_state,
+				  struct drm_connector_state *conn_state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+
+	if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
+	    !vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format))
+		return -EINVAL;
+
+	vbridge->output_bus_fmt = bridge_state->output_bus_cfg.format;
+
+	return 0;
+}
+
+static void vs_bridge_atomic_enable(struct drm_bridge *bridge,
+				    struct drm_atomic_state *state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	struct drm_bridge_state *br_state = drm_atomic_get_bridge_state(state,
+									bridge);
+	struct vs_crtc *crtc = vbridge->crtc;
+	struct vs_dc *dc = crtc->dc;
+	unsigned int output = crtc->id;
+	u32 dp_fmt;
+	unsigned int i;
+
+	DRM_DEBUG_DRIVER("Enabling output %u\n", output);
+
+	switch (vbridge->intf) {
+	case VSDC_OUTPUT_INTERFACE_DPI:
+		regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output),
+				  VSDC_DISP_DP_CONFIG_DP_EN);
+		regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output),
+			     VSDC_DISP_DPI_CONFIG_FMT_RGB888);
+		break;
+	case VSDC_OUTPUT_INTERFACE_DP:
+		for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) {
+			if (vsdc_dp_supported_fmts[i].linux_fmt ==
+			    vbridge->output_bus_fmt)
+				break;
+		}
+		if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts)))
+			return;
+		dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
+		dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN;
+		regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt);
+		regmap_assign_bits(dc->regs,
+				   VSDC_DISP_PANEL_CONFIG(output),
+				   VSDC_DISP_PANEL_CONFIG_YUV,
+				   vsdc_dp_supported_fmts[i].is_yuv);
+		break;
+	}
+
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			  VSDC_DISP_PANEL_CONFIG_DAT_POL);
+	regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			   VSDC_DISP_PANEL_CONFIG_DE_POL,
+			   br_state->output_bus_cfg.flags &
+			   DRM_BUS_FLAG_DE_LOW);
+	regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			   VSDC_DISP_PANEL_CONFIG_CLK_POL,
+			   br_state->output_bus_cfg.flags &
+			   DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			VSDC_DISP_PANEL_CONFIG_DE_EN |
+			VSDC_DISP_PANEL_CONFIG_DAT_EN |
+			VSDC_DISP_PANEL_CONFIG_CLK_EN);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			VSDC_DISP_PANEL_CONFIG_RUNNING);
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
+			  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC);
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START,
+			VSDC_DISP_PANEL_START_RUNNING(output));
+
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
+			VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
+}
+
+static void vs_bridge_atomic_disable(struct drm_bridge *bridge,
+				     struct drm_atomic_state *state)
+{
+	struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
+	struct vs_crtc *crtc = vbridge->crtc;
+	struct vs_dc *dc = crtc->dc;
+	unsigned int output = crtc->id;
+
+	DRM_DEBUG_DRIVER("Disabling output %u\n", output);
+
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
+			  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC |
+			  VSDC_DISP_PANEL_START_RUNNING(output));
+	regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
+			  VSDC_DISP_PANEL_CONFIG_RUNNING);
+
+	regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
+			VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
+}
+
+static const struct drm_bridge_funcs vs_bridge_funcs = {
+	.attach = vs_bridge_attach,
+	.atomic_enable = vs_bridge_atomic_enable,
+	.atomic_disable = vs_bridge_atomic_disable,
+	.atomic_check = vs_bridge_atomic_check,
+	.atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts,
+	.atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts,
+	.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
+	.atomic_reset = drm_atomic_helper_bridge_reset,
+};
+
+static int vs_bridge_detect_output_interface(struct device_node *of_node,
+					     unsigned int output)
+{
+	int ret;
+	struct device_node *remote;
+
+	remote = of_graph_get_remote_node(of_node, output,
+					  VSDC_OUTPUT_INTERFACE_DPI);
+	if (remote) {
+		ret = VSDC_OUTPUT_INTERFACE_DPI;
+	} else {
+		remote = of_graph_get_remote_node(of_node, output,
+						  VSDC_OUTPUT_INTERFACE_DP);
+		if (remote)
+			ret = VSDC_OUTPUT_INTERFACE_DP;
+		else
+			ret = -ENODEV;
+	}
+
+	if (remote)
+		of_node_put(remote);
+
+	return ret;
+}
+
+struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
+				 struct vs_crtc *crtc)
+{
+	unsigned int output = crtc->id;
+	struct vs_bridge *bridge;
+	struct drm_bridge *next;
+	enum vs_bridge_output_interface intf;
+	int ret, enctype;
+
+	intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
+						 output);
+	if (intf == -ENODEV) {
+		drm_info(drm_dev, "Skipping output %u\n", output);
+		return NULL;
+	}
+
+	next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
+				      output, intf);
+	if (IS_ERR(next)) {
+		ret = PTR_ERR(next);
+		if (ret != -EPROBE_DEFER)
+			drm_err(drm_dev,
+				"Cannot get downstream bridge of output %u\n",
+				output);
+		return ERR_PTR(ret);
+	}
+
+	bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base,
+				       &vs_bridge_funcs);
+	if (!bridge)
+		return ERR_PTR(-ENOMEM);
+
+	bridge->crtc = crtc;
+	bridge->intf = intf;
+	bridge->next = next;
+
+	if (intf == VSDC_OUTPUT_INTERFACE_DPI)
+		enctype = DRM_MODE_ENCODER_DPI;
+	else
+		enctype = DRM_MODE_ENCODER_NONE;
+
+	bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL);
+	if (IS_ERR(bridge->enc)) {
+		drm_err(drm_dev,
+			"Cannot initialize encoder for output %u\n", output);
+		ret = PTR_ERR(bridge->enc);
+		return ERR_PTR(ret);
+	}
+
+	bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base);
+
+	ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL,
+				DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+	if (ret) {
+		drm_err(drm_dev,
+			"Cannot attach bridge for output %u\n", output);
+		return ERR_PTR(ret);
+	}
+
+	bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc);
+	if (IS_ERR(bridge->conn)) {
+		drm_err(drm_dev,
+			"Cannot create connector for output %u\n", output);
+		ret = PTR_ERR(bridge->conn);
+		return ERR_PTR(ret);
+	}
+	drm_connector_attach_encoder(bridge->conn, bridge->enc);
+
+	return bridge;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
new file mode 100644
index 0000000000000..4120abafdaed6
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge.h
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_BRIDGE_H_
+#define _VS_BRIDGE_H_
+
+#include <linux/types.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+
+struct vs_crtc;
+
+enum vs_bridge_output_interface {
+	VSDC_OUTPUT_INTERFACE_DPI = 0,
+	VSDC_OUTPUT_INTERFACE_DP = 1
+};
+
+struct vs_bridge {
+	struct drm_bridge base;
+	struct drm_encoder *enc;
+	struct drm_connector *conn;
+
+	struct vs_crtc *crtc;
+	struct drm_bridge *next;
+	enum vs_bridge_output_interface intf;
+	u32 output_bus_fmt;
+};
+
+static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge)
+{
+	return container_of(bridge, struct vs_bridge, base);
+}
+
+struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
+				 struct vs_crtc *crtc);
+#endif /* _VS_BRIDGE_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
new file mode 100644
index 0000000000000..9eb30e4564beb
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_BRIDGE_REGS_H_
+#define _VS_BRIDGE_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_DISP_PANEL_CONFIG(n)		(0x1418 + 0x4 * (n))
+#define VSDC_DISP_PANEL_CONFIG_DE_EN		BIT(0)
+#define VSDC_DISP_PANEL_CONFIG_DE_POL		BIT(1)
+#define VSDC_DISP_PANEL_CONFIG_DAT_EN		BIT(4)
+#define VSDC_DISP_PANEL_CONFIG_DAT_POL		BIT(5)
+#define VSDC_DISP_PANEL_CONFIG_CLK_EN		BIT(8)
+#define VSDC_DISP_PANEL_CONFIG_CLK_POL		BIT(9)
+#define VSDC_DISP_PANEL_CONFIG_RUNNING		BIT(12)
+#define VSDC_DISP_PANEL_CONFIG_GAMMA		BIT(13)
+#define VSDC_DISP_PANEL_CONFIG_YUV		BIT(16)
+
+#define VSDC_DISP_DPI_CONFIG(n)			(0x14B8 + 0x4 * (n))
+#define VSDC_DISP_DPI_CONFIG_FMT_MASK		GENMASK(2, 0)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB565		(0)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB666		(3)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB888		(5)
+#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010	(6)
+
+#define VSDC_DISP_PANEL_START			0x1CCC
+#define VSDC_DISP_PANEL_START_RUNNING(n)	BIT(n)
+#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC	BIT(3)
+
+#define VSDC_DISP_DP_CONFIG(n)			(0x1CD0 + 0x4 * (n))
+#define VSDC_DISP_DP_CONFIG_DP_EN		BIT(3)
+#define VSDC_DISP_DP_CONFIG_FMT_MASK		GENMASK(2, 0)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB565		(0)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB666		(1)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB888		(2)
+#define VSDC_DISP_DP_CONFIG_FMT_RGB101010	(3)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK	GENMASK(7, 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8	(2 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8	(4 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10	(8 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10	(10 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8	(12 << 4)
+#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10	(13 << 4)
+
+#define VSDC_DISP_PANEL_CONFIG_EX(n)		(0x2518 + 0x4 * (n))
+#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT	BIT(0)
+
+#endif /* _VS_BRIDGE_REGS_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c
new file mode 100644
index 0000000000000..bd5304a25368a
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
@@ -0,0 +1,217 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_print.h>
+#include <drm/drm_managed.h>
+
+#include "vs_crtc_regs.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_plane.h"
+
+static void vs_crtc_atomic_flush(struct drm_crtc *crtc,
+				 struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
+									  crtc);
+	struct drm_pending_vblank_event *event = crtc_state->event;
+
+	DRM_DEBUG_DRIVER("Flushing CRTC %u vblank events\n", vcrtc->id);
+
+	if (event) {
+		crtc_state->event = NULL;
+
+		spin_lock_irq(&crtc->dev->event_lock);
+		if (drm_crtc_vblank_get(crtc) == 0)
+			drm_crtc_arm_vblank_event(crtc, event);
+		else
+			drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
+				   struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Disabling CRTC %u\n", output);
+
+	drm_crtc_vblank_off(crtc);
+
+	clk_disable_unprepare(dc->pix_clk[output]);
+}
+
+static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
+				     struct drm_atomic_state *state)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Enabling CRTC %u\n", output);
+
+	WARN_ON(clk_prepare_enable(dc->pix_clk[output]));
+
+	drm_crtc_vblank_on(crtc);
+}
+
+static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+
+	DRM_DEBUG_DRIVER("Setting mode on CRTC %u\n", output);
+
+	regmap_write(dc->regs, VSDC_DISP_HSIZE(output),
+		     VSDC_DISP_HSIZE_DISP(mode->hdisplay) |
+		     VSDC_DISP_HSIZE_TOTAL(mode->htotal));
+	regmap_write(dc->regs, VSDC_DISP_VSIZE(output),
+		     VSDC_DISP_VSIZE_DISP(mode->vdisplay) |
+		     VSDC_DISP_VSIZE_TOTAL(mode->vtotal));
+	regmap_write(dc->regs, VSDC_DISP_HSYNC(output),
+		     VSDC_DISP_HSYNC_START(mode->hsync_start) |
+		     VSDC_DISP_HSYNC_END(mode->hsync_end) |
+		     VSDC_DISP_HSYNC_EN);
+	if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
+		regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output),
+				VSDC_DISP_HSYNC_POL);
+	regmap_write(dc->regs, VSDC_DISP_VSYNC(output),
+		     VSDC_DISP_VSYNC_START(mode->vsync_start) |
+		     VSDC_DISP_VSYNC_END(mode->vsync_end) |
+		     VSDC_DISP_VSYNC_EN);
+	if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
+		regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output),
+				VSDC_DISP_VSYNC_POL);
+
+	WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000));
+}
+
+static enum drm_mode_status
+vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+	long rate;
+
+	if (mode->htotal > 0x7FFF)
+		return MODE_BAD_HVALUE;
+	if (mode->vtotal > 0x7FFF)
+		return MODE_BAD_VVALUE;
+
+	rate = clk_round_rate(dc->pix_clk[output], mode->clock * 1000);
+	if (rate <= 0)
+		return MODE_CLOCK_RANGE;
+
+	return MODE_OK;
+}
+
+static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
+			       const struct drm_display_mode *m,
+			       struct drm_display_mode *adjusted_mode)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+	unsigned int output = vcrtc->id;
+	long clk_rate;
+
+	drm_mode_set_crtcinfo(adjusted_mode, 0);
+
+	/* Feedback the pixel clock to crtc_clock */
+	clk_rate = adjusted_mode->crtc_clock * 1000;
+	clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate);
+	if (clk_rate <= 0)
+		return false;
+
+	adjusted_mode->crtc_clock = clk_rate / 1000;
+
+	return true;
+}
+
+static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
+	.atomic_flush	= vs_crtc_atomic_flush,
+	.atomic_enable	= vs_crtc_atomic_enable,
+	.atomic_disable	= vs_crtc_atomic_disable,
+	.mode_set_nofb	= vs_crtc_mode_set_nofb,
+	.mode_valid	= vs_crtc_mode_valid,
+	.mode_fixup	= vs_crtc_mode_fixup,
+};
+
+static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Enabling VBLANK on CRTC %u\n", vcrtc->id);
+	regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
+
+	return 0;
+}
+
+static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
+	struct vs_dc *dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Disabling VBLANK on CRTC %u\n", vcrtc->id);
+	regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
+}
+
+static const struct drm_crtc_funcs vs_crtc_funcs = {
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+	.atomic_duplicate_state	= drm_atomic_helper_crtc_duplicate_state,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.reset			= drm_atomic_helper_crtc_reset,
+	.set_config		= drm_atomic_helper_set_config,
+	.enable_vblank		= vs_crtc_enable_vblank,
+	.disable_vblank		= vs_crtc_disable_vblank,
+};
+
+struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
+			     unsigned int output)
+{
+	struct vs_crtc *vcrtc;
+	struct drm_plane *primary;
+	int ret;
+
+	vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL);
+	if (!vcrtc)
+		return ERR_PTR(-ENOMEM);
+	vcrtc->dc = dc;
+	vcrtc->id = output;
+
+	/* Create our primary plane */
+	primary = vs_primary_plane_init(drm_dev, dc);
+	if (IS_ERR(primary)) {
+		drm_err(drm_dev, "Couldn't create the primary plane\n");
+		return ERR_PTR(PTR_ERR(primary));
+	}
+
+	ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base,
+					 primary,
+					 NULL,
+					 &vs_crtc_funcs,
+					 NULL);
+	if (ret) {
+		drm_err(drm_dev, "Couldn't initialize CRTC\n");
+		return ERR_PTR(ret);
+	}
+
+	drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs);
+
+	return vcrtc;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h
new file mode 100644
index 0000000000000..6f862d609b984
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_CRTC_H_
+#define _VS_CRTC_H_
+
+#include <drm/drm_crtc.h>
+#include <drm/drm_vblank.h>
+
+struct vs_dc;
+
+struct vs_crtc {
+	struct drm_crtc base;
+
+	struct vs_dc *dc;
+	unsigned int id;
+};
+
+static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc)
+{
+	return container_of(crtc, struct vs_crtc, base);
+}
+
+struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
+			     unsigned int output);
+
+#endif /* _VS_CRTC_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
new file mode 100644
index 0000000000000..c7930e817635c
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_CRTC_REGS_H_
+#define _VS_CRTC_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_DISP_DITHER_CONFIG(n)		(0x1410 + 0x4 * (n))
+
+#define VSDC_DISP_DITHER_TABLE_LOW(n)		(0x1420 + 0x4 * (n))
+#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT	0x7B48F3C0
+
+#define VSDC_DISP_DITHER_TABLE_HIGH(n)		(0x1428 + 0x4 * (n))
+#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT	0x596AD1E2
+
+#define VSDC_DISP_HSIZE(n)			(0x1430 + 0x4 * (n))
+#define VSDC_DISP_HSIZE_DISP_MASK		GENMASK(14, 0)
+#define VSDC_DISP_HSIZE_DISP(v)			((v) << 0)
+#define VSDC_DISP_HSIZE_TOTAL_MASK		GENMASK(30, 16)
+#define VSDC_DISP_HSIZE_TOTAL(v)		((v) << 16)
+
+#define VSDC_DISP_HSYNC(n)			(0x1438 + 0x4 * (n))
+#define VSDC_DISP_HSYNC_START_MASK		GENMASK(14, 0)
+#define VSDC_DISP_HSYNC_START(v)		((v) << 0)
+#define VSDC_DISP_HSYNC_END_MASK		GENMASK(29, 15)
+#define VSDC_DISP_HSYNC_END(v)			((v) << 15)
+#define VSDC_DISP_HSYNC_EN			BIT(30)
+#define VSDC_DISP_HSYNC_POL			BIT(31)
+
+#define VSDC_DISP_VSIZE(n)			(0x1440 + 0x4 * (n))
+#define VSDC_DISP_VSIZE_DISP_MASK		GENMASK(14, 0)
+#define VSDC_DISP_VSIZE_DISP(v)			((v) << 0)
+#define VSDC_DISP_VSIZE_TOTAL_MASK		GENMASK(30, 16)
+#define VSDC_DISP_VSIZE_TOTAL(v)		((v) << 16)
+
+#define VSDC_DISP_VSYNC(n)			(0x1448 + 0x4 * (n))
+#define VSDC_DISP_VSYNC_START_MASK		GENMASK(14, 0)
+#define VSDC_DISP_VSYNC_START(v)		((v) << 0)
+#define VSDC_DISP_VSYNC_END_MASK		GENMASK(29, 15)
+#define VSDC_DISP_VSYNC_END(v)			((v) << 15)
+#define VSDC_DISP_VSYNC_EN			BIT(30)
+#define VSDC_DISP_VSYNC_POL			BIT(31)
+
+#define VSDC_DISP_CURRENT_LOCATION(n)		(0x1450 + 0x4 * (n))
+
+#define VSDC_DISP_GAMMA_INDEX(n)		(0x1458 + 0x4 * (n))
+
+#define VSDC_DISP_GAMMA_DATA(n)			(0x1460 + 0x4 * (n))
+
+#define VSDC_DISP_IRQ_STA			0x147C
+
+#define VSDC_DISP_IRQ_EN			0x1480
+
+#endif /* _VS_CRTC_REGS_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
new file mode 100644
index 0000000000000..a413479c6cfff
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_drm.h"
+#include "vs_hwdb.h"
+
+static const struct regmap_config vs_dc_regmap_cfg = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = sizeof(u32),
+	/* VSDC_OVL_CONFIG_EX(1) */
+	.max_register = 0x2544,
+};
+
+static const struct of_device_id vs_dc_driver_dt_match[] = {
+	{ .compatible = "verisilicon,dc" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match);
+
+static irqreturn_t vs_dc_irq_handler(int irq, void *private)
+{
+	struct vs_dc *dc = private;
+	u32 irqs;
+
+	regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs);
+
+	return vs_drm_handle_irq(dc, irqs);
+}
+
+static int vs_dc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct vs_dc *dc;
+	void __iomem *regs;
+	unsigned int outputs, i;
+	/* pix0/pix1 */
+	char pixclk_name[5];
+	int irq, ret;
+
+	if (!dev->of_node) {
+		dev_err(dev, "can't find DC devices\n");
+		return -ENODEV;
+	}
+
+	outputs = of_graph_get_port_count(dev->of_node);
+	if (!outputs) {
+		dev_err(dev, "can't find DC downstream ports\n");
+		return -ENODEV;
+	}
+	if (outputs > VSDC_MAX_OUTPUTS) {
+		dev_err(dev, "too many DC downstream ports than possible\n");
+		return -EINVAL;
+	}
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret) {
+		dev_err(dev, "No suitable DMA available\n");
+		return ret;
+	}
+
+	dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
+	if (!dc)
+		return -ENOMEM;
+
+	dc->outputs = outputs;
+
+	dc->rsts[0].id = "core";
+	dc->rsts[1].id = "axi";
+	dc->rsts[2].id = "ahb";
+
+	ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT,
+							  dc->rsts);
+	if (ret) {
+		dev_err(dev, "can't get reset lines\n");
+		return ret;
+	}
+
+	dc->core_clk = devm_clk_get_enabled(dev, "core");
+	if (IS_ERR(dc->core_clk)) {
+		dev_err(dev, "can't get core clock\n");
+		return PTR_ERR(dc->core_clk);
+	}
+
+	dc->axi_clk = devm_clk_get_enabled(dev, "axi");
+	if (IS_ERR(dc->axi_clk)) {
+		dev_err(dev, "can't get axi clock\n");
+		return PTR_ERR(dc->axi_clk);
+	}
+
+	dc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
+	if (IS_ERR(dc->ahb_clk)) {
+		dev_err(dev, "can't get ahb clock\n");
+		return PTR_ERR(dc->ahb_clk);
+	}
+
+	for (i = 0; i < outputs; i++) {
+		snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i);
+		dc->pix_clk[i] = devm_clk_get(dev, pixclk_name);
+		if (IS_ERR(dc->pix_clk[i])) {
+			dev_err(dev, "can't get pixel clk %u\n", i);
+			return PTR_ERR(dc->pix_clk[i]);
+		}
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(dev, "can't get irq\n");
+		return irq;
+	}
+
+	ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts);
+	if (ret) {
+		dev_err(dev, "can't deassert reset lines\n");
+		return ret;
+	}
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs)) {
+		dev_err(dev, "can't map registers");
+		ret = PTR_ERR(regs);
+		goto err_rst_assert;
+	}
+
+	dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
+	if (IS_ERR(dc->regs)) {
+		ret = PTR_ERR(dc->regs);
+		goto err_rst_assert;
+	}
+
+	ret = vs_fill_chip_identity(dc->regs, &dc->identity);
+	if (ret)
+		goto err_rst_assert;
+
+	dev_info(dev, "DC%x rev %x customer %x\n", dc->identity.model,
+		 dc->identity.revision, dc->identity.customer_id);
+
+	if (outputs > dc->identity.display_count) {
+		dev_err(dev, "too many downstream ports than HW capability\n");
+		ret = -EINVAL;
+		goto err_rst_assert;
+	}
+
+	ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
+			       dev_name(dev), dc);
+	if (ret) {
+		dev_err(dev, "can't request irq\n");
+		goto err_rst_assert;
+	}
+
+	dev_set_drvdata(dev, dc);
+
+	ret = vs_drm_initialize(dc, pdev);
+	if (ret)
+		goto err_rst_assert;
+
+	return 0;
+
+err_rst_assert:
+	reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
+	return ret;
+}
+
+static void vs_dc_remove(struct platform_device *pdev)
+{
+	struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
+
+	vs_drm_finalize(dc);
+
+	dev_set_drvdata(&pdev->dev, NULL);
+
+	reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
+}
+
+static void vs_dc_shutdown(struct platform_device *pdev)
+{
+	struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
+
+	vs_drm_shutdown_handler(dc);
+}
+
+struct platform_driver vs_dc_platform_driver = {
+	.probe = vs_dc_probe,
+	.remove = vs_dc_remove,
+	.shutdown = vs_dc_shutdown,
+	.driver = {
+		.name = "verisilicon-dc",
+		.of_match_table = vs_dc_driver_dt_match,
+	},
+};
+
+module_platform_driver(vs_dc_platform_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
+MODULE_DESCRIPTION("Verisilicon display controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h
new file mode 100644
index 0000000000000..5e071501b1c38
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_H_
+#define _VS_DC_H_
+
+#include <linux/clk.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#include <drm/drm_device.h>
+
+#include "vs_hwdb.h"
+
+#define VSDC_MAX_OUTPUTS 2
+#define VSDC_RESET_COUNT 3
+
+struct vs_drm_dev;
+struct vs_crtc;
+
+struct vs_dc {
+	struct regmap *regs;
+	struct clk *core_clk;
+	struct clk *axi_clk;
+	struct clk *ahb_clk;
+	struct clk *pix_clk[VSDC_MAX_OUTPUTS];
+	struct reset_control_bulk_data rsts[VSDC_RESET_COUNT];
+
+	struct vs_drm_dev *drm_dev;
+	struct vs_chip_identity identity;
+	unsigned int outputs;
+};
+
+#endif /* _VS_DC_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
new file mode 100644
index 0000000000000..50509bbbff08f
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_DC_TOP_H_
+#define _VS_DC_TOP_H_
+
+#include <linux/bits.h>
+
+#define VSDC_TOP_RST				0x0000
+
+#define VSDC_TOP_IRQ_ACK			0x0010
+#define VSDC_TOP_IRQ_VSYNC(n)			BIT(n)
+
+#define VSDC_TOP_IRQ_EN				0x0014
+
+#define VSDC_TOP_CHIP_MODEL			0x0020
+
+#define VSDC_TOP_CHIP_REV			0x0024
+
+#define VSDC_TOP_CHIP_CUSTOMER_ID		0x0030
+
+#endif /* _VS_DC_TOP_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c
new file mode 100644
index 0000000000000..f356d7832c449
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_drm.c
@@ -0,0 +1,177 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/aperture.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/console.h>
+
+#include <drm/clients/drm_client_setup.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "vs_bridge.h"
+#include "vs_crtc.h"
+#include "vs_dc.h"
+#include "vs_dc_top_regs.h"
+#include "vs_drm.h"
+
+#define DRIVER_NAME	"verisilicon"
+#define DRIVER_DESC	"Verisilicon DC-series display controller driver"
+#define DRIVER_MAJOR	1
+#define DRIVER_MINOR	0
+
+static int vs_gem_dumb_create(struct drm_file *file_priv,
+			      struct drm_device *drm,
+			      struct drm_mode_create_dumb *args)
+{
+	/* The hardware wants 128B-aligned pitches for linear buffers. */
+	args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 128);
+
+	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
+}
+
+DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops);
+
+static const struct drm_driver vs_drm_driver = {
+	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.fops			= &vs_drm_driver_fops,
+	.name	= DRIVER_NAME,
+	.desc	= DRIVER_DESC,
+	.major	= DRIVER_MAJOR,
+	.minor	= DRIVER_MINOR,
+
+	/* GEM Operations */
+	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create),
+	DRM_FBDEV_DMA_DRIVER_OPS,
+};
+
+static const struct drm_mode_config_funcs vs_mode_config_funcs = {
+	.fb_create		= drm_gem_fb_create,
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+};
+
+static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = {
+	.atomic_commit_tail = drm_atomic_helper_commit_tail,
+};
+
+static void vs_mode_config_init(struct drm_device *drm)
+{
+	drm_mode_config_reset(drm);
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+	drm->mode_config.max_width = 8192;
+	drm->mode_config.max_height = 8192;
+	drm->mode_config.funcs = &vs_mode_config_funcs;
+	drm->mode_config.helper_private = &vs_mode_config_helper_funcs;
+}
+
+int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct vs_drm_dev *vdrm;
+	struct drm_device *drm;
+	struct vs_crtc *crtc;
+	struct vs_bridge *bridge;
+	unsigned int i;
+	int ret;
+
+	vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base);
+	if (IS_ERR(vdrm))
+		return PTR_ERR(vdrm);
+
+	drm = &vdrm->base;
+	vdrm->dc = dc;
+	dc->drm_dev = vdrm;
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	for (i = 0; i < dc->outputs; i++) {
+		crtc = vs_crtc_init(drm, dc, i);
+		if (IS_ERR(crtc))
+			return PTR_ERR(crtc);
+
+		bridge = vs_bridge_init(drm, crtc);
+		if (IS_ERR(bridge))
+			return PTR_ERR(bridge);
+
+		vdrm->crtcs[i] = crtc;
+	}
+
+	ret = drm_vblank_init(drm, dc->outputs);
+	if (ret)
+		return ret;
+
+	/* Remove early framebuffers (ie. simplefb) */
+	ret = aperture_remove_all_conflicting_devices(DRIVER_NAME);
+	if (ret)
+		return ret;
+
+	vs_mode_config_init(drm);
+
+	/* Enable connectors polling */
+	drm_kms_helper_poll_init(drm);
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto err_fini_poll;
+
+	drm_client_setup(drm, NULL);
+
+	return 0;
+
+err_fini_poll:
+	drm_kms_helper_poll_fini(drm);
+	return ret;
+}
+
+void vs_drm_finalize(struct vs_dc *dc)
+{
+	struct vs_drm_dev *vdrm = dc->drm_dev;
+	struct drm_device *drm = &vdrm->base;
+
+	drm_dev_unregister(drm);
+	drm_kms_helper_poll_fini(drm);
+	drm_atomic_helper_shutdown(drm);
+	dc->drm_dev = NULL;
+}
+
+void vs_drm_shutdown_handler(struct vs_dc *dc)
+{
+	struct vs_drm_dev *vdrm = dc->drm_dev;
+
+	drm_atomic_helper_shutdown(&vdrm->base);
+}
+
+irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs)
+{
+	unsigned int i;
+
+	for (i = 0; i < dc->outputs; i++) {
+		if (irqs & VSDC_TOP_IRQ_VSYNC(i)) {
+			irqs &= ~VSDC_TOP_IRQ_VSYNC(i);
+			if (dc->drm_dev->crtcs[i])
+				drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base);
+		}
+	}
+
+	if (irqs)
+		pr_warn("Unknown Verisilicon DC interrupt 0x%x fired!\n", irqs);
+
+	return IRQ_HANDLED;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h
new file mode 100644
index 0000000000000..bbcd2e527deb6
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_drm.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_DRM_H_
+#define _VS_DRM_H_
+
+#include <linux/irqreturn.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <drm/drm_device.h>
+
+struct vs_dc;
+
+struct vs_drm_dev {
+	struct drm_device base;
+
+	struct vs_dc *dc;
+	struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS];
+};
+
+int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev);
+void vs_drm_finalize(struct vs_dc *dc);
+void vs_drm_shutdown_handler(struct vs_dc *dc);
+irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs);
+
+#endif /* _VS_DRM_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c
new file mode 100644
index 0000000000000..4a87e5d4701f3
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/errno.h>
+
+#include <drm/drm_fourcc.h>
+
+#include "vs_dc_top_regs.h"
+#include "vs_hwdb.h"
+
+static const u32 vs_formats_array_no_yuv444[] = {
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_XBGR4444,
+	DRM_FORMAT_RGBX4444,
+	DRM_FORMAT_BGRX4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_ABGR4444,
+	DRM_FORMAT_RGBA4444,
+	DRM_FORMAT_BGRA4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_XBGR1555,
+	DRM_FORMAT_RGBX5551,
+	DRM_FORMAT_BGRX5551,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ABGR1555,
+	DRM_FORMAT_RGBA5551,
+	DRM_FORMAT_BGRA5551,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_BGR565,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_RGBX8888,
+	DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGBA8888,
+	DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_ABGR2101010,
+	DRM_FORMAT_RGBA1010102,
+	DRM_FORMAT_BGRA1010102,
+	/* TODO: non-RGB formats */
+};
+
+static const u32 vs_formats_array_with_yuv444[] = {
+	DRM_FORMAT_XRGB4444,
+	DRM_FORMAT_XBGR4444,
+	DRM_FORMAT_RGBX4444,
+	DRM_FORMAT_BGRX4444,
+	DRM_FORMAT_ARGB4444,
+	DRM_FORMAT_ABGR4444,
+	DRM_FORMAT_RGBA4444,
+	DRM_FORMAT_BGRA4444,
+	DRM_FORMAT_XRGB1555,
+	DRM_FORMAT_XBGR1555,
+	DRM_FORMAT_RGBX5551,
+	DRM_FORMAT_BGRX5551,
+	DRM_FORMAT_ARGB1555,
+	DRM_FORMAT_ABGR1555,
+	DRM_FORMAT_RGBA5551,
+	DRM_FORMAT_BGRA5551,
+	DRM_FORMAT_RGB565,
+	DRM_FORMAT_BGR565,
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_RGBX8888,
+	DRM_FORMAT_BGRX8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_ABGR8888,
+	DRM_FORMAT_RGBA8888,
+	DRM_FORMAT_BGRA8888,
+	DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_ABGR2101010,
+	DRM_FORMAT_RGBA1010102,
+	DRM_FORMAT_BGRA1010102,
+	/* TODO: non-RGB formats */
+};
+
+static const struct vs_formats vs_formats_no_yuv444 = {
+	.array = vs_formats_array_no_yuv444,
+	.num = ARRAY_SIZE(vs_formats_array_no_yuv444)
+};
+
+static const struct vs_formats vs_formats_with_yuv444 = {
+	.array = vs_formats_array_with_yuv444,
+	.num = ARRAY_SIZE(vs_formats_array_with_yuv444)
+};
+
+static struct vs_chip_identity vs_chip_identities[] = {
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = ~0U,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5721,
+		.customer_id = 0x30B,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = 0x310,
+
+		.display_count = 2,
+		.formats = &vs_formats_with_yuv444,
+	},
+	{
+		.model = 0x8200,
+		.revision = 0x5720,
+		.customer_id = 0x311,
+
+		.display_count = 2,
+		.formats = &vs_formats_no_yuv444,
+	},
+};
+
+int vs_fill_chip_identity(struct regmap *regs,
+			  struct vs_chip_identity *ident)
+{
+	u32 model;
+	u32 revision;
+	u32 customer_id;
+	int i;
+
+	regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model);
+	regmap_read(regs, VSDC_TOP_CHIP_REV, &revision);
+	regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id);
+
+	for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) {
+		if (vs_chip_identities[i].model == model &&
+		    vs_chip_identities[i].revision == revision &&
+		    (vs_chip_identities[i].customer_id == customer_id ||
+			 vs_chip_identities[i].customer_id == ~0U)) {
+			memcpy(ident, &vs_chip_identities[i], sizeof(*ident));
+			ident->customer_id = customer_id;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h
new file mode 100644
index 0000000000000..92192e4fa0862
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#ifndef _VS_HWDB_H_
+#define _VS_HWDB_H_
+
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+struct vs_formats {
+	const u32 *array;
+	unsigned int num;
+};
+
+struct vs_chip_identity {
+	u32 model;
+	u32 revision;
+	u32 customer_id;
+
+	u32 display_count;
+	const struct vs_formats *formats;
+};
+
+int vs_fill_chip_identity(struct regmap *regs,
+			  struct vs_chip_identity *ident);
+
+#endif /* _VS_HWDB_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c
new file mode 100644
index 0000000000000..f3c9963b6a4ea
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_plane.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/errno.h>
+
+#include <drm/drm_fourcc.h>
+#include <drm/drm_print.h>
+
+#include "vs_plane.h"
+
+void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format)
+{
+	switch (drm_format) {
+	case DRM_FORMAT_XRGB4444:
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_BGRX4444:
+		vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4;
+		break;
+	case DRM_FORMAT_ARGB4444:
+	case DRM_FORMAT_RGBA4444:
+	case DRM_FORMAT_ABGR4444:
+	case DRM_FORMAT_BGRA4444:
+		vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4;
+		break;
+	case DRM_FORMAT_XRGB1555:
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_BGRX5551:
+		vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5;
+		break;
+	case DRM_FORMAT_ARGB1555:
+	case DRM_FORMAT_RGBA5551:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_BGRA5551:
+		vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5;
+		break;
+	case DRM_FORMAT_RGB565:
+	case DRM_FORMAT_BGR565:
+		vs_format->color = VSDC_COLOR_FORMAT_R5G6B5;
+		break;
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_BGRX8888:
+		vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_RGBA8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_BGRA8888:
+		vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8;
+		break;
+	case DRM_FORMAT_ARGB2101010:
+	case DRM_FORMAT_RGBA1010102:
+	case DRM_FORMAT_ABGR2101010:
+	case DRM_FORMAT_BGRA1010102:
+		vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10;
+		break;
+	default:
+		DRM_WARN("Unexpected drm format!\n");
+	}
+
+	switch (drm_format) {
+	case DRM_FORMAT_RGBX4444:
+	case DRM_FORMAT_RGBA4444:
+	case DRM_FORMAT_RGBX5551:
+	case DRM_FORMAT_RGBA5551:
+	case DRM_FORMAT_RGBX8888:
+	case DRM_FORMAT_RGBA8888:
+	case DRM_FORMAT_RGBA1010102:
+		vs_format->swizzle = VSDC_SWIZZLE_RGBA;
+		break;
+	case DRM_FORMAT_XBGR4444:
+	case DRM_FORMAT_ABGR4444:
+	case DRM_FORMAT_XBGR1555:
+	case DRM_FORMAT_ABGR1555:
+	case DRM_FORMAT_BGR565:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+	case DRM_FORMAT_ABGR2101010:
+		vs_format->swizzle = VSDC_SWIZZLE_ABGR;
+		break;
+	case DRM_FORMAT_BGRX4444:
+	case DRM_FORMAT_BGRA4444:
+	case DRM_FORMAT_BGRX5551:
+	case DRM_FORMAT_BGRA5551:
+	case DRM_FORMAT_BGRX8888:
+	case DRM_FORMAT_BGRA8888:
+	case DRM_FORMAT_BGRA1010102:
+		vs_format->swizzle = VSDC_SWIZZLE_BGRA;
+		break;
+	default:
+		/* N/A for YUV formats */
+		vs_format->swizzle = VSDC_SWIZZLE_ARGB;
+	}
+
+	/* N/A for non-YUV formats */
+	vs_format->uv_swizzle = false;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h
new file mode 100644
index 0000000000000..3595267c89b53
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_plane.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_PLANE_H_
+#define _VS_PLANE_H_
+
+#include <linux/types.h>
+
+#include <drm/drm_device.h>
+#include <drm/drm_plane.h>
+
+#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15))
+#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15))
+
+struct vs_dc;
+
+enum vs_color_format {
+	VSDC_COLOR_FORMAT_X4R4G4B4,
+	VSDC_COLOR_FORMAT_A4R4G4B4,
+	VSDC_COLOR_FORMAT_X1R5G5B5,
+	VSDC_COLOR_FORMAT_A1R5G5B5,
+	VSDC_COLOR_FORMAT_R5G6B5,
+	VSDC_COLOR_FORMAT_X8R8G8B8,
+	VSDC_COLOR_FORMAT_A8R8G8B8,
+	VSDC_COLOR_FORMAT_YUY2,
+	VSDC_COLOR_FORMAT_UYVY,
+	VSDC_COLOR_FORMAT_INDEX8,
+	VSDC_COLOR_FORMAT_MONOCHROME,
+	VSDC_COLOR_FORMAT_YV12 = 0xf,
+	VSDC_COLOR_FORMAT_A8,
+	VSDC_COLOR_FORMAT_NV12,
+	VSDC_COLOR_FORMAT_NV16,
+	VSDC_COLOR_FORMAT_RG16,
+	VSDC_COLOR_FORMAT_R8,
+	VSDC_COLOR_FORMAT_NV12_10BIT,
+	VSDC_COLOR_FORMAT_A2R10G10B10,
+	VSDC_COLOR_FORMAT_NV16_10BIT,
+	VSDC_COLOR_FORMAT_INDEX1,
+	VSDC_COLOR_FORMAT_INDEX2,
+	VSDC_COLOR_FORMAT_INDEX4,
+	VSDC_COLOR_FORMAT_P010,
+	VSDC_COLOR_FORMAT_YUV444,
+	VSDC_COLOR_FORMAT_YUV444_10BIT
+};
+
+enum vs_swizzle {
+	VSDC_SWIZZLE_ARGB,
+	VSDC_SWIZZLE_RGBA,
+	VSDC_SWIZZLE_ABGR,
+	VSDC_SWIZZLE_BGRA,
+};
+
+struct vs_format {
+	enum vs_color_format color;
+	enum vs_swizzle swizzle;
+	bool uv_swizzle;
+};
+
+void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format);
+
+struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc);
+
+#endif /* _VS_PLANE_H_ */
diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
new file mode 100644
index 0000000000000..5b03783d4f284
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ */
+
+#include <linux/regmap.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_modeset_helper_vtables.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_print.h>
+
+#include "vs_crtc.h"
+#include "vs_plane.h"
+#include "vs_dc.h"
+#include "vs_primary_plane_regs.h"
+
+static int vs_primary_plane_atomic_check(struct drm_plane *plane,
+					 struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
+										 plane);
+	struct drm_crtc *crtc = new_plane_state->crtc;
+	struct drm_crtc_state *crtc_state;
+
+	if (!crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (WARN_ON(!crtc_state))
+		return -EINVAL;
+
+	return drm_atomic_helper_check_plane_state(new_plane_state,
+						   crtc_state,
+						   DRM_PLANE_NO_SCALING,
+						   DRM_PLANE_NO_SCALING,
+						   false, true);
+}
+
+
+static void vs_primary_plane_atomic_update(struct drm_plane *plane,
+					   struct drm_atomic_state *atomic_state)
+{
+	struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state,
+								       plane);
+	struct drm_framebuffer *fb = state->fb;
+	struct drm_crtc *crtc = state->crtc;
+	struct drm_gem_dma_object *gem;
+	struct vs_dc *dc;
+	struct vs_crtc *vcrtc;
+	struct vs_format fmt;
+	unsigned int output, bpp;
+	dma_addr_t dma_addr;
+
+	if (!crtc)
+		return;
+
+	vcrtc = drm_crtc_to_vs_crtc(crtc);
+	output = vcrtc->id;
+	dc = vcrtc->dc;
+
+	DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
+
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+			   VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK,
+			   VSDC_FB_CONFIG_EX_DISPLAY_ID(output));
+
+	if (!state->visible || !fb) {
+		regmap_write(dc->regs, VSDC_FB_CONFIG(output), 0);
+		regmap_write(dc->regs, VSDC_FB_CONFIG_EX(output), 0);
+		goto commit;
+	} else {
+		regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+				VSDC_FB_CONFIG_EX_FB_EN);
+	}
+
+	drm_format_to_vs_format(state->fb->format->format, &fmt);
+
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_FMT_MASK,
+			   VSDC_FB_CONFIG_FMT(fmt.color));
+	regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_SWIZZLE_MASK,
+			   VSDC_FB_CONFIG_SWIZZLE(fmt.swizzle));
+	regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output),
+			   VSDC_FB_CONFIG_UV_SWIZZLE_EN, fmt.uv_swizzle);
+
+	/* Get the physical address of the buffer in memory */
+	gem = drm_fb_dma_get_gem_obj(fb, 0);
+
+	/* Compute the start of the displayed memory */
+	bpp = fb->format->cpp[0];
+	dma_addr = gem->dma_addr + fb->offsets[0];
+
+	/* Fixup framebuffer address for src coordinates */
+	dma_addr += (state->src.x1 >> 16) * bpp;
+	dma_addr += (state->src.y1 >> 16) * fb->pitches[0];
+
+	regmap_write(dc->regs, VSDC_FB_ADDRESS(output),
+		     lower_32_bits(dma_addr));
+	regmap_write(dc->regs, VSDC_FB_STRIDE(output),
+		     fb->pitches[0]);
+
+	regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output),
+		     VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y));
+	regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output),
+		     VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w,
+					 state->crtc_y + state->crtc_h));
+	regmap_write(dc->regs, VSDC_FB_SIZE(output),
+		     VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h));
+
+	regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output),
+		     VSDC_FB_BLEND_CONFIG_BLEND_DISABLE);
+commit:
+	regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
+			VSDC_FB_CONFIG_EX_COMMIT);
+}
+
+static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = {
+	.atomic_check	= vs_primary_plane_atomic_check,
+	.atomic_update	= vs_primary_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs vs_primary_plane_funcs = {
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+	.atomic_duplicate_state	= drm_atomic_helper_plane_duplicate_state,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.reset			= drm_atomic_helper_plane_reset,
+	.update_plane		= drm_atomic_helper_update_plane,
+};
+
+struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc)
+{
+	struct drm_plane *plane;
+
+	plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0,
+					   &vs_primary_plane_funcs,
+					   dc->identity.formats->array,
+					   dc->identity.formats->num,
+					   NULL,
+					   DRM_PLANE_TYPE_PRIMARY,
+					   NULL);
+
+	if (IS_ERR(plane))
+		return plane;
+
+	drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs);
+
+	return plane;
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
new file mode 100644
index 0000000000000..cbb125c46b390
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on vs_dc_hw.h, which is:
+ *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
+ */
+
+#ifndef _VS_PRIMARY_PLANE_REGS_H_
+#define _VS_PRIMARY_PLANE_REGS_H_
+
+#include <linux/bits.h>
+
+#define VSDC_FB_ADDRESS(n)			(0x1400 + 0x4 * (n))
+
+#define VSDC_FB_STRIDE(n)			(0x1408 + 0x4 * (n))
+
+#define VSDC_FB_CONFIG(n)			(0x1518 + 0x4 * (n))
+#define VSDC_FB_CONFIG_CLEAR_EN			BIT(8)
+#define VSDC_FB_CONFIG_ROT_MASK			GENMASK(13, 11)
+#define VSDC_FB_CONFIG_ROT(v)			((v) << 11)
+#define VSDC_FB_CONFIG_YUV_SPACE_MASK		GENMASK(16, 14)
+#define VSDC_FB_CONFIG_YUV_SPACE(v)		((v) << 14)
+#define VSDC_FB_CONFIG_TILE_MODE_MASK		GENMASK(21, 17)
+#define VSDC_FB_CONFIG_TILE_MODE(v)		((v) << 14)
+#define VSDC_FB_CONFIG_SCALE_EN			BIT(22)
+#define VSDC_FB_CONFIG_SWIZZLE_MASK		GENMASK(24, 23)
+#define VSDC_FB_CONFIG_SWIZZLE(v)		((v) << 23)
+#define VSDC_FB_CONFIG_UV_SWIZZLE_EN		BIT(25)
+#define VSDC_FB_CONFIG_FMT_MASK			GENMASK(31, 26)
+#define VSDC_FB_CONFIG_FMT(v)			((v) << 26)
+
+#define VSDC_FB_SIZE(n)				(0x1810 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */
+
+#define VSDC_FB_CONFIG_EX(n)			(0x1CC0 + 0x4 * (n))
+#define VSDC_FB_CONFIG_EX_COMMIT		BIT(12)
+#define VSDC_FB_CONFIG_EX_FB_EN			BIT(13)
+#define VSDC_FB_CONFIG_EX_ZPOS_MASK		GENMASK(18, 16)
+#define VSDC_FB_CONFIG_EX_ZPOS(v)		((v) << 16)
+#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK	GENMASK(19, 19)
+#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v)		((v) << 19)
+
+#define VSDC_FB_TOP_LEFT(n)			(0x24D8 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
+
+#define VSDC_FB_BOTTOM_RIGHT(n)			(0x24E0 + 0x4 * (n))
+/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
+
+#define VSDC_FB_BLEND_CONFIG(n)			(0x2510 + 0x4 * (n))
+#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE	BIT(1)
+
+#endif /* _VS_PRIMARY_PLANE_REGS_H_ */
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 4/9] dt-bindings: display/bridge: add binding for TH1520 HDMI controller
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (2 preceding siblings ...)
  2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-24 16:12   ` [PATCH v4 5/9] drm/bridge: add a driver for T-Head " Icenowy Zheng
                     ` (5 subsequent siblings)
  9 siblings, 0 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng, Krzysztof Kozlowski

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller paired
with DesignWare HDMI PHY, with an extra clock gate for HDMI pixel clock
and two reset controls.

Add a device tree binding to it.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
---
No changes in v3, v4.

Changes in v2:
- Re-aligned multi-line clocks/resets in example.
- Added Krzysztof's R-b.

P.S. Should the mail addresss of Krzysztof be changed? I got notice
email that says he's no longer working for Linaro.

 .../display/bridge/thead,th1520-dw-hdmi.yaml  | 120 ++++++++++++++++++
 1 file changed, 120 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml

diff --git a/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
new file mode 100644
index 0000000000000..68fff885ce15b
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/bridge/thead,th1520-dw-hdmi.yaml
@@ -0,0 +1,120 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/bridge/thead,th1520-dw-hdmi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: T-Head TH1520 DesignWare HDMI TX Encoder
+
+maintainers:
+  - Icenowy Zheng <uwu@icenowy.me>
+
+description:
+  The HDMI transmitter is a Synopsys DesignWare HDMI TX controller
+  paired with a DesignWare HDMI Gen2 TX PHY.
+
+allOf:
+  - $ref: /schemas/display/bridge/synopsys,dw-hdmi.yaml#
+
+properties:
+  compatible:
+    enum:
+      - thead,th1520-dw-hdmi
+
+  reg-io-width:
+    const: 4
+
+  clocks:
+    maxItems: 4
+
+  clock-names:
+    items:
+      - const: iahb
+      - const: isfr
+      - const: cec
+      - const: pix
+
+  resets:
+    items:
+      - description: Main reset
+      - description: Configuration APB reset
+
+  reset-names:
+    items:
+      - const: main
+      - const: apb
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: Input port connected to DC8200 DPU "DP" output
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        description: HDMI output port
+
+    required:
+      - port@0
+      - port@1
+
+required:
+  - compatible
+  - reg
+  - reg-io-width
+  - clocks
+  - clock-names
+  - resets
+  - reset-names
+  - interrupts
+  - ports
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/irq.h>
+    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
+    #include <dt-bindings/reset/thead,th1520-reset.h>
+
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      hdmi@ffef540000 {
+        compatible = "thead,th1520-dw-hdmi";
+        reg = <0xff 0xef540000 0x0 0x40000>;
+        reg-io-width = <4>;
+        interrupts = <111 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&clk_vo CLK_HDMI_PCLK>,
+                 <&clk_vo CLK_HDMI_SFR>,
+                 <&clk_vo CLK_HDMI_CEC>,
+                 <&clk_vo CLK_HDMI_PIXCLK>;
+        clock-names = "iahb", "isfr", "cec", "pix";
+        resets = <&rst_vo TH1520_RESET_ID_HDMI>,
+                 <&rst_vo TH1520_RESET_ID_HDMI_APB>;
+        reset-names = "main", "apb";
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+          port@0 {
+            reg = <0>;
+
+            hdmi_in: endpoint {
+              remote-endpoint = <&dpu_out_dp1>;
+            };
+          };
+
+          port@1 {
+            reg = <1>;
+
+            hdmi_out_conn: endpoint {
+              remote-endpoint = <&hdmi_conn_in>;
+            };
+          };
+        };
+      };
+    };
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (3 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 4/9] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-25  9:46     ` Han Gao (Revy)
  2025-12-25 11:07     ` Andy Yan
  2025-12-24 16:12   ` [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
                     ` (4 subsequent siblings)
  9 siblings, 2 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller (paired
with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from the
display controller.

Add a driver for this controller utilizing the common DesignWare HDMI
code in the kernel.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes in v3, v4.

Changes in v2:
- Created a new function to set PHY parameters and refactored the
  control flow of the configure_phy callback.

 MAINTAINERS                             |   1 +
 drivers/gpu/drm/bridge/Kconfig          |  10 ++
 drivers/gpu/drm/bridge/Makefile         |   1 +
 drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173 ++++++++++++++++++++++++
 4 files changed, 185 insertions(+)
 create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 5b11839cba9de..fc04fc007a054 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -22556,6 +22556,7 @@ F:	Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml
 F:	arch/riscv/boot/dts/thead/
 F:	drivers/clk/thead/clk-th1520-ap.c
 F:	drivers/firmware/thead,th1520-aon.c
+F:	drivers/gpu/drm/bridge/th1520-dw-hdmi.c
 F:	drivers/mailbox/mailbox-th1520.c
 F:	drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
 F:	drivers/pinctrl/pinctrl-th1520.c
diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
index a250afd8d6622..8e19f5fb9ad7c 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -335,6 +335,16 @@ config DRM_THINE_THC63LVD1024
 	help
 	  Thine THC63LVD1024 LVDS/parallel converter driver.
 
+config DRM_THEAD_TH1520_DW_HDMI
+	tristate "T-Head TH1520 DesignWare HDMI bridge"
+	depends on OF
+	depends on COMMON_CLK
+	depends on ARCH_THEAD || COMPILE_TEST
+	select DRM_DW_HDMI
+	help
+	  Choose this to enable support for the internal HDMI bridge found
+	  on the T-Head TH1520 SoC.
+
 config DRM_TOSHIBA_TC358762
 	tristate "TC358762 DSI/DPI bridge"
 	depends on OF
diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
index c7dc03182e592..085b5db45d6fd 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o
 obj-$(CONFIG_DRM_SII9234) += sii9234.o
 obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
 obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o
+obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o
 obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o
 obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
new file mode 100644
index 0000000000000..389eead5f1c45
--- /dev/null
+++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
+ *
+ * Based on rcar_dw_hdmi.c, which is:
+ *   Copyright (C) 2016 Renesas Electronics Corporation
+ * Based on imx8mp-hdmi-tx.c, which is:
+ *   Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de>
+ */
+
+#include <linux/clk.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/bridge/dw_hdmi.h>
+#include <drm/drm_modes.h>
+
+#define TH1520_HDMI_PHY_OPMODE_PLLCFG	0x06	/* Mode of operation and PLL dividers */
+#define TH1520_HDMI_PHY_CKSYMTXCTRL	0x09	/* Clock Symbol and Transmitter Control Register */
+#define TH1520_HDMI_PHY_VLEVCTRL	0x0e	/* Voltage Level Control Register */
+#define TH1520_HDMI_PHY_PLLCURRGMPCTRL	0x10	/* PLL current and Gmp (conductance) */
+#define TH1520_HDMI_PHY_PLLDIVCTRL	0x11	/* PLL dividers */
+#define TH1520_HDMI_PHY_TXTERM		0x19	/* Transmission Termination Register */
+
+struct th1520_hdmi_phy_params {
+	unsigned long mpixelclock;
+	u16 opmode_pllcfg;
+	u16 pllcurrgmpctrl;
+	u16 plldivctrl;
+	u16 cksymtxctrl;
+	u16 vlevctrl;
+	u16 txterm;
+};
+
+static const struct th1520_hdmi_phy_params th1520_hdmi_phy_params[] = {
+	{ 35500000,  0x0003, 0x0283, 0x0628, 0x8088, 0x01a0, 0x0007 },
+	{ 44900000,  0x0003, 0x0285, 0x0228, 0x8088, 0x01a0, 0x0007 },
+	{ 71000000,  0x0002, 0x1183, 0x0614, 0x8088, 0x01a0, 0x0007 },
+	{ 90000000,  0x0002, 0x1142, 0x0214, 0x8088, 0x01a0, 0x0007 },
+	{ 121750000, 0x0001, 0x20c0, 0x060a, 0x8088, 0x01a0, 0x0007 },
+	{ 165000000, 0x0001, 0x2080, 0x020a, 0x8088, 0x01a0, 0x0007 },
+	{ 198000000, 0x0000, 0x3040, 0x0605, 0x83c8, 0x0120, 0x0004 },
+	{ 297000000, 0x0000, 0x3041, 0x0205, 0x81dc, 0x0200, 0x0005 },
+	{ 371250000, 0x0640, 0x3041, 0x0205, 0x80f6, 0x0140, 0x0000 },
+	{ 495000000, 0x0640, 0x3080, 0x0005, 0x80f6, 0x0140, 0x0000 },
+	{ 594000000, 0x0640, 0x3080, 0x0005, 0x80fa, 0x01e0, 0x0004 },
+};
+
+struct th1520_hdmi {
+	struct dw_hdmi_plat_data plat_data;
+	struct dw_hdmi *dw_hdmi;
+	struct clk *pixclk;
+	struct reset_control *mainrst, *prst;
+};
+
+static enum drm_mode_status
+th1520_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
+		       const struct drm_display_info *info,
+		       const struct drm_display_mode *mode)
+{
+	/*
+	 * The maximum supported clock frequency is 594 MHz, as shown in the PHY
+	 * parameters table.
+	 */
+	if (mode->clock > 594000)
+		return MODE_CLOCK_HIGH;
+
+	return MODE_OK;
+}
+
+static void th1520_hdmi_phy_set_params(struct dw_hdmi *hdmi,
+				const struct th1520_hdmi_phy_params *params)
+{
+	dw_hdmi_phy_i2c_write(hdmi, params->opmode_pllcfg,
+			      TH1520_HDMI_PHY_OPMODE_PLLCFG);
+	dw_hdmi_phy_i2c_write(hdmi, params->pllcurrgmpctrl,
+			      TH1520_HDMI_PHY_PLLCURRGMPCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->plldivctrl,
+			      TH1520_HDMI_PHY_PLLDIVCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->vlevctrl,
+			      TH1520_HDMI_PHY_VLEVCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->cksymtxctrl,
+			      TH1520_HDMI_PHY_CKSYMTXCTRL);
+	dw_hdmi_phy_i2c_write(hdmi, params->txterm,
+			      TH1520_HDMI_PHY_TXTERM);
+}
+
+static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
+				     unsigned long mpixelclock)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(th1520_hdmi_phy_params); i++) {
+		if (mpixelclock <= th1520_hdmi_phy_params[i].mpixelclock) {
+			th1520_hdmi_phy_set_params(hdmi,
+						   &th1520_hdmi_phy_params[i]);
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int th1520_dw_hdmi_probe(struct platform_device *pdev)
+{
+	struct th1520_hdmi *hdmi;
+	struct dw_hdmi_plat_data *plat_data;
+	struct device *dev = &pdev->dev;
+
+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
+	if (!hdmi)
+		return -ENOMEM;
+
+	plat_data = &hdmi->plat_data;
+
+	hdmi->pixclk = devm_clk_get_enabled(dev, "pix");
+	if (IS_ERR(hdmi->pixclk))
+		return dev_err_probe(dev, PTR_ERR(hdmi->pixclk),
+				     "Unable to get pixel clock\n");
+
+	hdmi->mainrst = devm_reset_control_get_exclusive_deasserted(dev, "main");
+	if (IS_ERR(hdmi->mainrst))
+		return dev_err_probe(dev, PTR_ERR(hdmi->mainrst),
+				     "Unable to get main reset\n");
+
+	hdmi->prst = devm_reset_control_get_exclusive_deasserted(dev, "apb");
+	if (IS_ERR(hdmi->prst))
+		return dev_err_probe(dev, PTR_ERR(hdmi->prst),
+				     "Unable to get apb reset\n");
+
+	plat_data->output_port = 1;
+	plat_data->mode_valid = th1520_hdmi_mode_valid;
+	plat_data->configure_phy = th1520_hdmi_phy_configure;
+	plat_data->priv_data = hdmi;
+
+	hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data);
+	if (IS_ERR(hdmi))
+		return PTR_ERR(hdmi);
+
+	platform_set_drvdata(pdev, hdmi);
+
+	return 0;
+}
+
+static void th1520_dw_hdmi_remove(struct platform_device *pdev)
+{
+	struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
+
+	dw_hdmi_remove(hdmi);
+}
+
+static const struct of_device_id th1520_dw_hdmi_of_table[] = {
+	{ .compatible = "thead,th1520-dw-hdmi" },
+	{ /* Sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, th1520_dw_hdmi_of_table);
+
+static struct platform_driver th1520_dw_hdmi_platform_driver = {
+	.probe		= th1520_dw_hdmi_probe,
+	.remove		= th1520_dw_hdmi_remove,
+	.driver		= {
+		.name	= "th1520-dw-hdmi",
+		.of_match_table = th1520_dw_hdmi_of_table,
+	},
+};
+
+module_platform_driver(th1520_dw_hdmi_platform_driver);
+
+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
+MODULE_DESCRIPTION("T-Head TH1520 HDMI Encoder Driver");
+MODULE_LICENSE("GPL");
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (4 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 5/9] drm/bridge: add a driver for T-Head " Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-25  9:46     ` Han Gao (Revy)
  2025-12-24 16:12   ` [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
                     ` (3 subsequent siblings)
  9 siblings, 1 reply; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

T-Head TH1520 SoC contains a Verisilicon DC8200 display controller
(called DPU in manual) and a Synopsys DesignWare HDMI TX controller.

Add device tree nodes to them.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes in v4.

Changes in v3:
- Adapting to the changed binding.

No changes in v2.

 arch/riscv/boot/dts/thead/th1520.dtsi | 70 +++++++++++++++++++++++++++
 1 file changed, 70 insertions(+)

diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi
index bd5d33840884e..5c0a7072f253a 100644
--- a/arch/riscv/boot/dts/thead/th1520.dtsi
+++ b/arch/riscv/boot/dts/thead/th1520.dtsi
@@ -585,6 +585,76 @@ clk_vo: clock-controller@ffef528050 {
 			#clock-cells = <1>;
 		};
 
+		hdmi: hdmi@ffef540000 {
+			compatible = "thead,th1520-dw-hdmi";
+			reg = <0xff 0xef540000 0x0 0x40000>;
+			reg-io-width = <4>;
+			interrupts = <111 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_vo CLK_HDMI_PCLK>,
+				 <&clk_vo CLK_HDMI_SFR>,
+				 <&clk_vo CLK_HDMI_CEC>,
+				 <&clk_vo CLK_HDMI_PIXCLK>;
+			clock-names = "iahb", "isfr", "cec", "pix";
+			resets = <&rst TH1520_RESET_ID_HDMI>,
+				 <&rst TH1520_RESET_ID_HDMI_APB>;
+			reset-names = "main", "apb";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				port@0 {
+					reg = <0>;
+
+					hdmi_in: endpoint {
+						remote-endpoint = <&dpu_out_dp1>;
+					};
+				};
+
+				hdmi_out_port: port@1 {
+					reg = <1>;
+				};
+			};
+		};
+
+		dpu: display@ffef600000 {
+			compatible = "thead,th1520-dc8200", "verisilicon,dc";
+			reg = <0xff 0xef600000 0x0 0x100000>;
+			interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_vo CLK_DPU_CCLK>,
+				 <&clk_vo CLK_DPU_ACLK>,
+				 <&clk_vo CLK_DPU_HCLK>,
+				 <&clk_vo CLK_DPU_PIXELCLK0>,
+				 <&clk_vo CLK_DPU_PIXELCLK1>;
+			clock-names = "core", "axi", "ahb", "pix0", "pix1";
+			resets = <&rst TH1520_RESET_ID_DPU_CORE>,
+				 <&rst TH1520_RESET_ID_DPU_AXI>,
+				 <&rst TH1520_RESET_ID_DPU_AHB>;
+			reset-names = "core", "axi", "ahb";
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+
+				dpu_port0: port@0 {
+					reg = <0>;
+				};
+
+				dpu_port1: port@1 {
+					reg = <1>;
+					#address-cells = <1>;
+					#size-cells = <0>;
+
+					dpu_out_dp1: endpoint@1 {
+						reg = <1>;
+						remote-endpoint = <&hdmi_in>;
+					};
+				};
+			};
+		};
+
 		dmac0: dma-controller@ffefc00000 {
 			compatible = "snps,axi-dma-1.01a";
 			reg = <0xff 0xefc00000 0x0 0x1000>;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (5 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-25  9:47     ` Han Gao (Revy)
  2025-12-24 16:12   ` [PATCH v4 8/9] MAINTAINERS: assign myself as maintainer for verisilicon DC driver Icenowy Zheng
                     ` (2 subsequent siblings)
  9 siblings, 1 reply; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

Lichee Pi 4A board features a HDMI Type-A connector connected to the
HDMI TX controller of TH1520 SoC.

Add a device tree node describing the connector, connect it to the HDMI
controller, and enable everything on this display pipeline.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
Changes in v4:
- Rebased on top of v6.19-rc1.

No changes in v2, v3.

 .../boot/dts/thead/th1520-lichee-pi-4a.dts    | 25 +++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
index c58c2085ca92a..7cb7d28683bce 100644
--- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
+++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
@@ -29,6 +29,17 @@ chosen {
 		stdout-path = "serial0:115200n8";
 	};
 
+	hdmi-connector {
+		compatible = "hdmi-connector";
+		type = "a";
+
+		port {
+			hdmi_con_in: endpoint {
+				remote-endpoint = <&hdmi_out_con>;
+			};
+		};
+	};
+
 	thermal-zones {
 		cpu-thermal {
 			polling-delay = <1000>;
@@ -121,6 +132,20 @@ rx-pins {
 	};
 };
 
+&dpu {
+	status = "okay";
+};
+
+&hdmi {
+	status = "okay";
+};
+
+&hdmi_out_port {
+	hdmi_out_con: endpoint {
+		remote-endpoint = <&hdmi_con_in>;
+	};
+};
+
 &uart0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&uart0_pins>;
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 8/9] MAINTAINERS: assign myself as maintainer for verisilicon DC driver
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (6 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-24 16:12   ` [PATCH v4 9/9] mailmap: map all Icenowy Zheng's mail addresses Icenowy Zheng
  2025-12-29 16:32   ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Michal Wilczynski
  9 siblings, 0 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

As I am the author of this rewritten driver, it makes sense for me to be
the maintainer.

Confirm this in MAINTAINERS file.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes in v4.

Changes in v3:
- Switch to my ISCAS mailbox.

No changes in v2.

 MAINTAINERS | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index fc04fc007a054..507ffef26dac1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8658,6 +8658,13 @@ F:	Documentation/devicetree/bindings/display/brcm,bcm2835-*.yaml
 F:	drivers/gpu/drm/vc4/
 F:	include/uapi/drm/vc4_drm.h
 
+DRM DRIVERS FOR VERISILICON DISPLAY CONTROLLER IP
+M:	Icenowy Zheng <zhengxingda@iscas.ac.cn>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/display/verisilicon,dc.yaml
+F:	drivers/gpu/drm/verisilicon/
+
 DRM DRIVERS FOR VIVANTE GPU IP
 M:	Lucas Stach <l.stach@pengutronix.de>
 R:	Russell King <linux+etnaviv@armlinux.org.uk>
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* [PATCH v4 9/9] mailmap: map all Icenowy Zheng's mail addresses
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (7 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 8/9] MAINTAINERS: assign myself as maintainer for verisilicon DC driver Icenowy Zheng
@ 2025-12-24 16:12   ` Icenowy Zheng
  2025-12-29 16:32   ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Michal Wilczynski
  9 siblings, 0 replies; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-24 16:12 UTC (permalink / raw)
  To: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng, Icenowy Zheng

From: Icenowy Zheng <uwu@icenowy.me>

Map all mail addresses Icenowy Zheng had used to the personal mailbox
prefixed "uwu".

All these mailboxes, except the one of Sipeed (which was only used
during a summer vacation internship), can accept mails now.

Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
---
No changes in v4.

New patch in v3.

 .mailmap | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/.mailmap b/.mailmap
index 84309a39d329c..0fc9602ddd9b0 100644
--- a/.mailmap
+++ b/.mailmap
@@ -316,6 +316,10 @@ Henrik Rydberg <rydberg@bitmath.org>
 Herbert Xu <herbert@gondor.apana.org.au>
 Huacai Chen <chenhuacai@kernel.org> <chenhc@lemote.com>
 Huacai Chen <chenhuacai@kernel.org> <chenhuacai@loongson.cn>
+Icenowy Zheng <uwu@icenowy.me> <zhengxingda@iscas.ac.cn>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@aosc.io>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@aosc.xyz>
+Icenowy Zheng <uwu@icenowy.me> <icenowy@sipeed.com>
 Ike Panhc <ikepanhc@gmail.com> <ike.pan@canonical.com>
 J. Bruce Fields <bfields@fieldses.org> <bfields@redhat.com>
 J. Bruce Fields <bfields@fieldses.org> <bfields@citi.umich.edu>
-- 
2.52.0


^ permalink raw reply related	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
@ 2025-12-25  9:35     ` Han Gao (Revy)
  2025-12-27 11:08       ` Krzysztof Kozlowski
  2025-12-25  9:45     ` Han Gao (Revy)
  2026-01-05 15:46     ` Rob Herring
  2 siblings, 1 reply; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:35 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:11, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> Verisilicon has a series of display controllers prefixed with DC and
> with self-identification facility like their GC series GPUs.
> 
> Add a device tree binding for it.
> 
> Depends on the specific DC model, it can have either one or two display
> outputs, and each display output could be set to DPI signal or "DP"
> signal (which seems to be some plain parallel bus to HDMI controllers).
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Added a comment for "verisilicon,dc" that says the ID/revision is
>  discoverable via registers.
> - Removed clock minItems constraint w/o specific compatible strings.
> 
> Changes in v3:
> - Added SoC-specific compatible string, and arm the binding with clock /
>  port checking for the specific SoC (with a 2-output DC).
> 
> Changes in v2:
> - Fixed misspelt "versilicon" in title.
> - Moved minItems in clock properties to be earlier than items.
> - Re-aligned multi-line clocks and resets in example.
> 
> .../bindings/display/verisilicon,dc.yaml      | 144 ++++++++++++++++++
> 1 file changed, 144 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> new file mode 100644
> index 0000000000000..fe64cc1466690
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> @@ -0,0 +1,144 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Verisilicon DC-series display controllers
> +
> +maintainers:
> +  - Icenowy Zheng <uwu@icenowy.me>
> +
> +properties:
> +  $nodename:
> +    pattern: "^display@[0-9a-f]+$"
> +
> +  compatible:
> +    items:
> +      - enum:
> +          - thead,th1520-dc8200
> +      - const: verisilicon,dc # DC IPs have discoverable ID/revision registers
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: DC Core clock
> +      - description: DMA AXI bus clock
> +      - description: Configuration AHB bus clock
> +      - description: Pixel clock of output 0
> +      - description: Pixel clock of output 1
> +
> +  clock-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +      - const: pix0
> +      - const: pix1
> +
> +  resets:
> +    items:
> +      - description: DC Core reset
> +      - description: DMA AXI bus reset
> +      - description: Configuration AHB bus reset
> +
> +  reset-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The first output channel , endpoint 0 should be
> +          used for DPI format output and endpoint 1 should be used
> +          for DP format output.
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The second output channel if the DC variant
> +          supports. Follow the same endpoint addressing rule with
> +          the first port.
> +
> +    required:
> +      - port@0
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - ports
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: thead,th1520-dc8200
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 5
> +        ports:
> +          required:
> +            - port@0
> +            - port@1
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
> +    #include <dt-bindings/reset/thead,th1520-reset.h>
> +    soc {
> +      #address-cells = <2>;
> +      #size-cells = <2>;
> +
> +      display@ffef600000 {
> +        compatible = "thead,th1520-dc8200", "verisilicon,dc";
> +        reg = <0xff 0xef600000 0x0 0x100000>;
> +        interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
> +        clocks = <&clk_vo CLK_DPU_CCLK>,
> +                 <&clk_vo CLK_DPU_ACLK>,
> +                 <&clk_vo CLK_DPU_HCLK>,
> +                 <&clk_vo CLK_DPU_PIXELCLK0>,
> +                 <&clk_vo CLK_DPU_PIXELCLK1>;
> +        clock-names = "core", "axi", "ahb", "pix0", "pix1";
> +        resets = <&rst TH1520_RESET_ID_DPU_CORE>,
> +                 <&rst TH1520_RESET_ID_DPU_AXI>,
> +                 <&rst TH1520_RESET_ID_DPU_AHB>;
> +        reset-names = "core", "axi", "ahb";
> +
> +        ports {
> +          #address-cells = <1>;
> +          #size-cells = <0>;
> +
> +          port@0 {
> +            reg = <0>;
> +          };
> +
> +          port@1 {
> +            reg = <1>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            dpu_out_dp1: endpoint@1 {
> +              reg = <1>;
> +              remote-endpoint = <&hdmi_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn <mailto:gaohan@iscas.ac.cn>>


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers
  2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
@ 2025-12-25  9:36     ` Han Gao (Revy)
  2025-12-25  9:45     ` Han Gao (Revy)
  2025-12-31 14:20     ` Luca Ceresoli
  2 siblings, 0 replies; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:36 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:11, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> This is a from-scratch driver targeting Verisilicon DC-series display
> controllers, which feature self-identification functionality like their
> GC-series GPUs.
> 
> Only DC8200 is being supported now, and only the main framebuffer is set
> up (as the DRM primary plane). Support for more DC models and more
> features is my further targets.
> 
> As the display controller is delivered to SoC vendors as a whole part,
> this driver does not use component framework and extra bridges inside a
> SoC is expected to be implemented as dedicated bridges (this driver
> properly supports bridge chaining).
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Switch to drm_* logger when we're handling with struct drm_device.
> 
> Changes in v3:
> - Get rid of drm_atomic_get_existing_crtc_state() which is marked
>  deprecated.
> 
> Changes in v2:
> - Changed some Control flows according to previous reviews.
> - Added missing of_node_put when checking of endpoints for output type.
> - Switched all userspace-visible modeset objects to be managed by drmm
>  instead of devm.
> - Utilize devm_drm_bridge_alloc() in internal bridge.
> - Prevented the usage of simple encoder helpers by passing a NULL funcs pointer.
> - Let devm enable clocks when getting them.
> - Removed explicit `.cache_type = REGCACHE_NONE` in regmap config.
> - Fixed a debug print using a variable before initialization.
> - Fixed a wrong index when using bulk to handle resets.
> - Added missing configuration for DPI format (currently fixed RGB888).
> 
> drivers/gpu/drm/Kconfig                       |   2 +
> drivers/gpu/drm/Makefile                      |   1 +
> drivers/gpu/drm/verisilicon/Kconfig           |  15 +
> drivers/gpu/drm/verisilicon/Makefile          |   5 +
> drivers/gpu/drm/verisilicon/vs_bridge.c       | 331 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h       |  40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
> drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 ++++++++++++
> drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
> drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
> drivers/gpu/drm/verisilicon/vs_dc.c           | 205 +++++++++++
> drivers/gpu/drm/verisilicon/vs_dc.h           |  39 +++
> drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
> drivers/gpu/drm/verisilicon/vs_drm.c          | 177 ++++++++++
> drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
> drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
> drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
> drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
> drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
> .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
> 21 files changed, 1790 insertions(+)
> create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
> create mode 100644 drivers/gpu/drm/verisilicon/Makefile
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 7e6bc0b3a589c..41363da2cc59f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -398,6 +398,8 @@ source "drivers/gpu/drm/imagination/Kconfig"
> 
> source "drivers/gpu/drm/tyr/Kconfig"
> 
> +source "drivers/gpu/drm/verisilicon/Kconfig"
> +
> config DRM_HYPERV
> tristate "DRM Support for Hyper-V synthetic video device"
> depends on DRM && PCI && HYPERV_VMBUS
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 0e1c668b46d21..f2dfa0ad0ab78 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -235,6 +235,7 @@ obj-y += solomon/
> obj-$(CONFIG_DRM_SPRD) += sprd/
> obj-$(CONFIG_DRM_LOONGSON) += loongson/
> obj-$(CONFIG_DRM_POWERVR) += imagination/
> +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon/
> 
> # Ensure drm headers are self-contained and pass kernel-doc
> hdrtest-files := \
> diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig
> new file mode 100644
> index 0000000000000..0235577c72824
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config DRM_VERISILICON_DC
> + tristate "DRM Support for Verisilicon DC-series display controllers"
> + depends on DRM && COMMON_CLK
> + depends on RISCV || COMPILER_TEST
> + select DRM_CLIENT_SELECTION
> + select DRM_GEM_DMA_HELPER
> + select DRM_KMS_HELPER
> + select DRM_BRIDGE_CONNECTOR
> + select REGMAP_MMIO
> + select VIDEOMODE_HELPERS
> + help
> +  Choose this option if you have a SoC with Verisilicon DC-series
> +  display controllers. If M is selected, the module will be called
> +  verisilicon-dc.
> diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile
> new file mode 100644
> index 0000000000000..fd8d805fbcde1
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o
> +
> +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c
> new file mode 100644
> index 0000000000000..90e4aa8012fae
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> @@ -0,0 +1,331 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +
> +#include <uapi/linux/media-bus-format.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_bridge_connector.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +#include "vs_bridge.h"
> +#include "vs_bridge_regs.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +
> +static int vs_bridge_attach(struct drm_bridge *bridge,
> +    struct drm_encoder *encoder,
> +    enum drm_bridge_attach_flags flags)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + return drm_bridge_attach(encoder, vbridge->next,
> + bridge, flags);
> +}
> +
> +struct vsdc_dp_format {
> + u32 linux_fmt;
> + bool is_yuv;
> + u32 vsdc_fmt;
> +};
> +
> +static struct vsdc_dp_format vsdc_dp_supported_fmts[] = {
> + /* default to RGB888 */
> + { MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 },
> + { MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 },
> + { MEDIA_BUS_FMT_RGB101010_1X30,
> +  false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 },
> + { MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 },
> + { MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 },
> + { MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 },
> + { MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 },
> + { MEDIA_BUS_FMT_UYYVYY8_0_5X24,
> +  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 },
> + { MEDIA_BUS_FMT_UYYVYY10_0_5X30,
> +  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 },
> +};
> +
> +static u32 *vs_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state,
> + unsigned int *num_output_fmts)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + u32 *output_fmts;
> + unsigned int i;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> + *num_output_fmts = 2;
> + else
> + *num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts);
> +
> + output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
> +      GFP_KERNEL);
> + if (!output_fmts)
> + return NULL;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) {
> + /* TODO: support more DPI output formats */
> + output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
> + output_fmts[1] = MEDIA_BUS_FMT_FIXED;
> + } else {
> + for (i = 0; i < *num_output_fmts; i++)
> + output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
> + }
> +
> + return output_fmts;
> +}
> +
> +static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++)
> + if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt)
> + return true;
> +
> + return false;
> +}
> +
> +static u32 *vs_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state,
> + u32 output_fmt,
> + unsigned int *num_input_fmts)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
> +    !vs_bridge_out_dp_fmt_supported(output_fmt)) {
> + *num_input_fmts = 0;
> + return NULL;
> + }
> +
> + return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
> +  crtc_state,
> +  conn_state,
> +  output_fmt,
> +  num_input_fmts);
> +}
> +
> +static int vs_bridge_atomic_check(struct drm_bridge *bridge,
> +  struct drm_bridge_state *bridge_state,
> +  struct drm_crtc_state *crtc_state,
> +  struct drm_connector_state *conn_state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
> +    !vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format))
> + return -EINVAL;
> +
> + vbridge->output_bus_fmt = bridge_state->output_bus_cfg.format;
> +
> + return 0;
> +}
> +
> +static void vs_bridge_atomic_enable(struct drm_bridge *bridge,
> +    struct drm_atomic_state *state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + struct drm_bridge_state *br_state = drm_atomic_get_bridge_state(state,
> + bridge);
> + struct vs_crtc *crtc = vbridge->crtc;
> + struct vs_dc *dc = crtc->dc;
> + unsigned int output = crtc->id;
> + u32 dp_fmt;
> + unsigned int i;
> +
> + DRM_DEBUG_DRIVER("Enabling output %u\n", output);
> +
> + switch (vbridge->intf) {
> + case VSDC_OUTPUT_INTERFACE_DPI:
> + regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output),
> +  VSDC_DISP_DP_CONFIG_DP_EN);
> + regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output),
> +     VSDC_DISP_DPI_CONFIG_FMT_RGB888);
> + break;
> + case VSDC_OUTPUT_INTERFACE_DP:
> + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) {
> + if (vsdc_dp_supported_fmts[i].linux_fmt ==
> +    vbridge->output_bus_fmt)
> + break;
> + }
> + if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts)))
> + return;
> + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> + dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN;
> + regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt);
> + regmap_assign_bits(dc->regs,
> +   VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_YUV,
> +   vsdc_dp_supported_fmts[i].is_yuv);
> + break;
> + }
> +
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +  VSDC_DISP_PANEL_CONFIG_DAT_POL);
> + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_DE_POL,
> +   br_state->output_bus_cfg.flags &
> +   DRM_BUS_FLAG_DE_LOW);
> + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_CLK_POL,
> +   br_state->output_bus_cfg.flags &
> +   DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> + VSDC_DISP_PANEL_CONFIG_DE_EN |
> + VSDC_DISP_PANEL_CONFIG_DAT_EN |
> + VSDC_DISP_PANEL_CONFIG_CLK_EN);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> + VSDC_DISP_PANEL_CONFIG_RUNNING);
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
> +  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START,
> + VSDC_DISP_PANEL_START_RUNNING(output));
> +
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
> + VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
> +}
> +
> +static void vs_bridge_atomic_disable(struct drm_bridge *bridge,
> +     struct drm_atomic_state *state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + struct vs_crtc *crtc = vbridge->crtc;
> + struct vs_dc *dc = crtc->dc;
> + unsigned int output = crtc->id;
> +
> + DRM_DEBUG_DRIVER("Disabling output %u\n", output);
> +
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
> +  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC |
> +  VSDC_DISP_PANEL_START_RUNNING(output));
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +  VSDC_DISP_PANEL_CONFIG_RUNNING);
> +
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
> + VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
> +}
> +
> +static const struct drm_bridge_funcs vs_bridge_funcs = {
> + .attach = vs_bridge_attach,
> + .atomic_enable = vs_bridge_atomic_enable,
> + .atomic_disable = vs_bridge_atomic_disable,
> + .atomic_check = vs_bridge_atomic_check,
> + .atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts,
> + .atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> +};
> +
> +static int vs_bridge_detect_output_interface(struct device_node *of_node,
> +     unsigned int output)
> +{
> + int ret;
> + struct device_node *remote;
> +
> + remote = of_graph_get_remote_node(of_node, output,
> +  VSDC_OUTPUT_INTERFACE_DPI);
> + if (remote) {
> + ret = VSDC_OUTPUT_INTERFACE_DPI;
> + } else {
> + remote = of_graph_get_remote_node(of_node, output,
> +  VSDC_OUTPUT_INTERFACE_DP);
> + if (remote)
> + ret = VSDC_OUTPUT_INTERFACE_DP;
> + else
> + ret = -ENODEV;
> + }
> +
> + if (remote)
> + of_node_put(remote);
> +
> + return ret;
> +}
> +
> +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
> + struct vs_crtc *crtc)
> +{
> + unsigned int output = crtc->id;
> + struct vs_bridge *bridge;
> + struct drm_bridge *next;
> + enum vs_bridge_output_interface intf;
> + int ret, enctype;
> +
> + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
> + output);
> + if (intf == -ENODEV) {
> + drm_info(drm_dev, "Skipping output %u\n", output);
> + return NULL;
> + }
> +
> + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
> +      output, intf);
> + if (IS_ERR(next)) {
> + ret = PTR_ERR(next);
> + if (ret != -EPROBE_DEFER)
> + drm_err(drm_dev,
> + "Cannot get downstream bridge of output %u\n",
> + output);
> + return ERR_PTR(ret);
> + }
> +
> + bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base,
> +       &vs_bridge_funcs);
> + if (!bridge)
> + return ERR_PTR(-ENOMEM);
> +
> + bridge->crtc = crtc;
> + bridge->intf = intf;
> + bridge->next = next;
> +
> + if (intf == VSDC_OUTPUT_INTERFACE_DPI)
> + enctype = DRM_MODE_ENCODER_DPI;
> + else
> + enctype = DRM_MODE_ENCODER_NONE;
> +
> + bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL);
> + if (IS_ERR(bridge->enc)) {
> + drm_err(drm_dev,
> + "Cannot initialize encoder for output %u\n", output);
> + ret = PTR_ERR(bridge->enc);
> + return ERR_PTR(ret);
> + }
> +
> + bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base);
> +
> + ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL,
> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> + if (ret) {
> + drm_err(drm_dev,
> + "Cannot attach bridge for output %u\n", output);
> + return ERR_PTR(ret);
> + }
> +
> + bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc);
> + if (IS_ERR(bridge->conn)) {
> + drm_err(drm_dev,
> + "Cannot create connector for output %u\n", output);
> + ret = PTR_ERR(bridge->conn);
> + return ERR_PTR(ret);
> + }
> + drm_connector_attach_encoder(bridge->conn, bridge->enc);
> +
> + return bridge;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
> new file mode 100644
> index 0000000000000..4120abafdaed6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_BRIDGE_H_
> +#define _VS_BRIDGE_H_
> +
> +#include <linux/types.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +struct vs_crtc;
> +
> +enum vs_bridge_output_interface {
> + VSDC_OUTPUT_INTERFACE_DPI = 0,
> + VSDC_OUTPUT_INTERFACE_DP = 1
> +};
> +
> +struct vs_bridge {
> + struct drm_bridge base;
> + struct drm_encoder *enc;
> + struct drm_connector *conn;
> +
> + struct vs_crtc *crtc;
> + struct drm_bridge *next;
> + enum vs_bridge_output_interface intf;
> + u32 output_bus_fmt;
> +};
> +
> +static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct vs_bridge, base);
> +}
> +
> +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
> + struct vs_crtc *crtc);
> +#endif /* _VS_BRIDGE_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> new file mode 100644
> index 0000000000000..9eb30e4564beb
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_BRIDGE_REGS_H_
> +#define _VS_BRIDGE_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_DISP_PANEL_CONFIG(n) (0x1418 + 0x4 * (n))
> +#define VSDC_DISP_PANEL_CONFIG_DE_EN BIT(0)
> +#define VSDC_DISP_PANEL_CONFIG_DE_POL BIT(1)
> +#define VSDC_DISP_PANEL_CONFIG_DAT_EN BIT(4)
> +#define VSDC_DISP_PANEL_CONFIG_DAT_POL BIT(5)
> +#define VSDC_DISP_PANEL_CONFIG_CLK_EN BIT(8)
> +#define VSDC_DISP_PANEL_CONFIG_CLK_POL BIT(9)
> +#define VSDC_DISP_PANEL_CONFIG_RUNNING BIT(12)
> +#define VSDC_DISP_PANEL_CONFIG_GAMMA BIT(13)
> +#define VSDC_DISP_PANEL_CONFIG_YUV BIT(16)
> +
> +#define VSDC_DISP_DPI_CONFIG(n) (0x14B8 + 0x4 * (n))
> +#define VSDC_DISP_DPI_CONFIG_FMT_MASK GENMASK(2, 0)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB565 (0)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB666 (3)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB888 (5)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010 (6)
> +
> +#define VSDC_DISP_PANEL_START 0x1CCC
> +#define VSDC_DISP_PANEL_START_RUNNING(n) BIT(n)
> +#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC BIT(3)
> +
> +#define VSDC_DISP_DP_CONFIG(n) (0x1CD0 + 0x4 * (n))
> +#define VSDC_DISP_DP_CONFIG_DP_EN BIT(3)
> +#define VSDC_DISP_DP_CONFIG_FMT_MASK GENMASK(2, 0)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB565 (0)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB666 (1)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB888 (2)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB101010 (3)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK GENMASK(7, 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 (2 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 (4 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 (8 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 (10 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 (12 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 (13 << 4)
> +
> +#define VSDC_DISP_PANEL_CONFIG_EX(n) (0x2518 + 0x4 * (n))
> +#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT BIT(0)
> +
> +#endif /* _VS_BRIDGE_REGS_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c
> new file mode 100644
> index 0000000000000..bd5304a25368a
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
> @@ -0,0 +1,217 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_managed.h>
> +
> +#include "vs_crtc_regs.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_plane.h"
> +
> +static void vs_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
> +  crtc);
> + struct drm_pending_vblank_event *event = crtc_state->event;
> +
> + DRM_DEBUG_DRIVER("Flushing CRTC %u vblank events\n", vcrtc->id);
> +
> + if (event) {
> + crtc_state->event = NULL;
> +
> + spin_lock_irq(&crtc->dev->event_lock);
> + if (drm_crtc_vblank_get(crtc) == 0)
> + drm_crtc_arm_vblank_event(crtc, event);
> + else
> + drm_crtc_send_vblank_event(crtc, event);
> + spin_unlock_irq(&crtc->dev->event_lock);
> + }
> +}
> +
> +static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
> +   struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Disabling CRTC %u\n", output);
> +
> + drm_crtc_vblank_off(crtc);
> +
> + clk_disable_unprepare(dc->pix_clk[output]);
> +}
> +
> +static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
> +     struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Enabling CRTC %u\n", output);
> +
> + WARN_ON(clk_prepare_enable(dc->pix_clk[output]));
> +
> + drm_crtc_vblank_on(crtc);
> +}
> +
> +static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> + struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Setting mode on CRTC %u\n", output);
> +
> + regmap_write(dc->regs, VSDC_DISP_HSIZE(output),
> +     VSDC_DISP_HSIZE_DISP(mode->hdisplay) |
> +     VSDC_DISP_HSIZE_TOTAL(mode->htotal));
> + regmap_write(dc->regs, VSDC_DISP_VSIZE(output),
> +     VSDC_DISP_VSIZE_DISP(mode->vdisplay) |
> +     VSDC_DISP_VSIZE_TOTAL(mode->vtotal));
> + regmap_write(dc->regs, VSDC_DISP_HSYNC(output),
> +     VSDC_DISP_HSYNC_START(mode->hsync_start) |
> +     VSDC_DISP_HSYNC_END(mode->hsync_end) |
> +     VSDC_DISP_HSYNC_EN);
> + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
> + regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output),
> + VSDC_DISP_HSYNC_POL);
> + regmap_write(dc->regs, VSDC_DISP_VSYNC(output),
> +     VSDC_DISP_VSYNC_START(mode->vsync_start) |
> +     VSDC_DISP_VSYNC_END(mode->vsync_end) |
> +     VSDC_DISP_VSYNC_EN);
> + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
> + regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output),
> + VSDC_DISP_VSYNC_POL);
> +
> + WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000));
> +}
> +
> +static enum drm_mode_status
> +vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> + long rate;
> +
> + if (mode->htotal > 0x7FFF)
> + return MODE_BAD_HVALUE;
> + if (mode->vtotal > 0x7FFF)
> + return MODE_BAD_VVALUE;
> +
> + rate = clk_round_rate(dc->pix_clk[output], mode->clock * 1000);
> + if (rate <= 0)
> + return MODE_CLOCK_RANGE;
> +
> + return MODE_OK;
> +}
> +
> +static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
> +       const struct drm_display_mode *m,
> +       struct drm_display_mode *adjusted_mode)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> + long clk_rate;
> +
> + drm_mode_set_crtcinfo(adjusted_mode, 0);
> +
> + /* Feedback the pixel clock to crtc_clock */
> + clk_rate = adjusted_mode->crtc_clock * 1000;
> + clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate);
> + if (clk_rate <= 0)
> + return false;
> +
> + adjusted_mode->crtc_clock = clk_rate / 1000;
> +
> + return true;
> +}
> +
> +static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
> + .atomic_flush = vs_crtc_atomic_flush,
> + .atomic_enable = vs_crtc_atomic_enable,
> + .atomic_disable = vs_crtc_atomic_disable,
> + .mode_set_nofb = vs_crtc_mode_set_nofb,
> + .mode_valid = vs_crtc_mode_valid,
> + .mode_fixup = vs_crtc_mode_fixup,
> +};
> +
> +static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Enabling VBLANK on CRTC %u\n", vcrtc->id);
> + regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
> +
> + return 0;
> +}
> +
> +static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Disabling VBLANK on CRTC %u\n", vcrtc->id);
> + regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
> +}
> +
> +static const struct drm_crtc_funcs vs_crtc_funcs = {
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .set_config = drm_atomic_helper_set_config,
> + .enable_vblank = vs_crtc_enable_vblank,
> + .disable_vblank = vs_crtc_disable_vblank,
> +};
> +
> +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
> +     unsigned int output)
> +{
> + struct vs_crtc *vcrtc;
> + struct drm_plane *primary;
> + int ret;
> +
> + vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL);
> + if (!vcrtc)
> + return ERR_PTR(-ENOMEM);
> + vcrtc->dc = dc;
> + vcrtc->id = output;
> +
> + /* Create our primary plane */
> + primary = vs_primary_plane_init(drm_dev, dc);
> + if (IS_ERR(primary)) {
> + drm_err(drm_dev, "Couldn't create the primary plane\n");
> + return ERR_PTR(PTR_ERR(primary));
> + }
> +
> + ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> + primary,
> + NULL,
> + &vs_crtc_funcs,
> + NULL);
> + if (ret) {
> + drm_err(drm_dev, "Couldn't initialize CRTC\n");
> + return ERR_PTR(ret);
> + }
> +
> + drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs);
> +
> + return vcrtc;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h
> new file mode 100644
> index 0000000000000..6f862d609b984
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_CRTC_H_
> +#define _VS_CRTC_H_
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_vblank.h>
> +
> +struct vs_dc;
> +
> +struct vs_crtc {
> + struct drm_crtc base;
> +
> + struct vs_dc *dc;
> + unsigned int id;
> +};
> +
> +static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc)
> +{
> + return container_of(crtc, struct vs_crtc, base);
> +}
> +
> +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
> +     unsigned int output);
> +
> +#endif /* _VS_CRTC_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> new file mode 100644
> index 0000000000000..c7930e817635c
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_CRTC_REGS_H_
> +#define _VS_CRTC_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_DISP_DITHER_CONFIG(n) (0x1410 + 0x4 * (n))
> +
> +#define VSDC_DISP_DITHER_TABLE_LOW(n) (0x1420 + 0x4 * (n))
> +#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT 0x7B48F3C0
> +
> +#define VSDC_DISP_DITHER_TABLE_HIGH(n) (0x1428 + 0x4 * (n))
> +#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT 0x596AD1E2
> +
> +#define VSDC_DISP_HSIZE(n) (0x1430 + 0x4 * (n))
> +#define VSDC_DISP_HSIZE_DISP_MASK GENMASK(14, 0)
> +#define VSDC_DISP_HSIZE_DISP(v) ((v) << 0)
> +#define VSDC_DISP_HSIZE_TOTAL_MASK GENMASK(30, 16)
> +#define VSDC_DISP_HSIZE_TOTAL(v) ((v) << 16)
> +
> +#define VSDC_DISP_HSYNC(n) (0x1438 + 0x4 * (n))
> +#define VSDC_DISP_HSYNC_START_MASK GENMASK(14, 0)
> +#define VSDC_DISP_HSYNC_START(v) ((v) << 0)
> +#define VSDC_DISP_HSYNC_END_MASK GENMASK(29, 15)
> +#define VSDC_DISP_HSYNC_END(v) ((v) << 15)
> +#define VSDC_DISP_HSYNC_EN BIT(30)
> +#define VSDC_DISP_HSYNC_POL BIT(31)
> +
> +#define VSDC_DISP_VSIZE(n) (0x1440 + 0x4 * (n))
> +#define VSDC_DISP_VSIZE_DISP_MASK GENMASK(14, 0)
> +#define VSDC_DISP_VSIZE_DISP(v) ((v) << 0)
> +#define VSDC_DISP_VSIZE_TOTAL_MASK GENMASK(30, 16)
> +#define VSDC_DISP_VSIZE_TOTAL(v) ((v) << 16)
> +
> +#define VSDC_DISP_VSYNC(n) (0x1448 + 0x4 * (n))
> +#define VSDC_DISP_VSYNC_START_MASK GENMASK(14, 0)
> +#define VSDC_DISP_VSYNC_START(v) ((v) << 0)
> +#define VSDC_DISP_VSYNC_END_MASK GENMASK(29, 15)
> +#define VSDC_DISP_VSYNC_END(v) ((v) << 15)
> +#define VSDC_DISP_VSYNC_EN BIT(30)
> +#define VSDC_DISP_VSYNC_POL BIT(31)
> +
> +#define VSDC_DISP_CURRENT_LOCATION(n) (0x1450 + 0x4 * (n))
> +
> +#define VSDC_DISP_GAMMA_INDEX(n) (0x1458 + 0x4 * (n))
> +
> +#define VSDC_DISP_GAMMA_DATA(n) (0x1460 + 0x4 * (n))
> +
> +#define VSDC_DISP_IRQ_STA 0x147C
> +
> +#define VSDC_DISP_IRQ_EN 0x1480
> +
> +#endif /* _VS_CRTC_REGS_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
> new file mode 100644
> index 0000000000000..a413479c6cfff
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> @@ -0,0 +1,205 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_drm.h"
> +#include "vs_hwdb.h"
> +
> +static const struct regmap_config vs_dc_regmap_cfg = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = sizeof(u32),
> + /* VSDC_OVL_CONFIG_EX(1) */
> + .max_register = 0x2544,
> +};
> +
> +static const struct of_device_id vs_dc_driver_dt_match[] = {
> + { .compatible = "verisilicon,dc" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match);
> +
> +static irqreturn_t vs_dc_irq_handler(int irq, void *private)
> +{
> + struct vs_dc *dc = private;
> + u32 irqs;
> +
> + regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs);
> +
> + return vs_drm_handle_irq(dc, irqs);
> +}
> +
> +static int vs_dc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vs_dc *dc;
> + void __iomem *regs;
> + unsigned int outputs, i;
> + /* pix0/pix1 */
> + char pixclk_name[5];
> + int irq, ret;
> +
> + if (!dev->of_node) {
> + dev_err(dev, "can't find DC devices\n");
> + return -ENODEV;
> + }
> +
> + outputs = of_graph_get_port_count(dev->of_node);
> + if (!outputs) {
> + dev_err(dev, "can't find DC downstream ports\n");
> + return -ENODEV;
> + }
> + if (outputs > VSDC_MAX_OUTPUTS) {
> + dev_err(dev, "too many DC downstream ports than possible\n");
> + return -EINVAL;
> + }
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret) {
> + dev_err(dev, "No suitable DMA available\n");
> + return ret;
> + }
> +
> + dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
> + if (!dc)
> + return -ENOMEM;
> +
> + dc->outputs = outputs;
> +
> + dc->rsts[0].id = "core";
> + dc->rsts[1].id = "axi";
> + dc->rsts[2].id = "ahb";
> +
> + ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT,
> +  dc->rsts);
> + if (ret) {
> + dev_err(dev, "can't get reset lines\n");
> + return ret;
> + }
> +
> + dc->core_clk = devm_clk_get_enabled(dev, "core");
> + if (IS_ERR(dc->core_clk)) {
> + dev_err(dev, "can't get core clock\n");
> + return PTR_ERR(dc->core_clk);
> + }
> +
> + dc->axi_clk = devm_clk_get_enabled(dev, "axi");
> + if (IS_ERR(dc->axi_clk)) {
> + dev_err(dev, "can't get axi clock\n");
> + return PTR_ERR(dc->axi_clk);
> + }
> +
> + dc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
> + if (IS_ERR(dc->ahb_clk)) {
> + dev_err(dev, "can't get ahb clock\n");
> + return PTR_ERR(dc->ahb_clk);
> + }
> +
> + for (i = 0; i < outputs; i++) {
> + snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i);
> + dc->pix_clk[i] = devm_clk_get(dev, pixclk_name);
> + if (IS_ERR(dc->pix_clk[i])) {
> + dev_err(dev, "can't get pixel clk %u\n", i);
> + return PTR_ERR(dc->pix_clk[i]);
> + }
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(dev, "can't get irq\n");
> + return irq;
> + }
> +
> + ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts);
> + if (ret) {
> + dev_err(dev, "can't deassert reset lines\n");
> + return ret;
> + }
> +
> + regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(regs)) {
> + dev_err(dev, "can't map registers");
> + ret = PTR_ERR(regs);
> + goto err_rst_assert;
> + }
> +
> + dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
> + if (IS_ERR(dc->regs)) {
> + ret = PTR_ERR(dc->regs);
> + goto err_rst_assert;
> + }
> +
> + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
> + if (ret)
> + goto err_rst_assert;
> +
> + dev_info(dev, "DC%x rev %x customer %x\n", dc->identity.model,
> + dc->identity.revision, dc->identity.customer_id);
> +
> + if (outputs > dc->identity.display_count) {
> + dev_err(dev, "too many downstream ports than HW capability\n");
> + ret = -EINVAL;
> + goto err_rst_assert;
> + }
> +
> + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> +       dev_name(dev), dc);
> + if (ret) {
> + dev_err(dev, "can't request irq\n");
> + goto err_rst_assert;
> + }
> +
> + dev_set_drvdata(dev, dc);
> +
> + ret = vs_drm_initialize(dc, pdev);
> + if (ret)
> + goto err_rst_assert;
> +
> + return 0;
> +
> +err_rst_assert:
> + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> + return ret;
> +}
> +
> +static void vs_dc_remove(struct platform_device *pdev)
> +{
> + struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
> +
> + vs_drm_finalize(dc);
> +
> + dev_set_drvdata(&pdev->dev, NULL);
> +
> + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> +}
> +
> +static void vs_dc_shutdown(struct platform_device *pdev)
> +{
> + struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
> +
> + vs_drm_shutdown_handler(dc);
> +}
> +
> +struct platform_driver vs_dc_platform_driver = {
> + .probe = vs_dc_probe,
> + .remove = vs_dc_remove,
> + .shutdown = vs_dc_shutdown,
> + .driver = {
> + .name = "verisilicon-dc",
> + .of_match_table = vs_dc_driver_dt_match,
> + },
> +};
> +
> +module_platform_driver(vs_dc_platform_driver);
> +
> +MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
> +MODULE_DESCRIPTION("Verisilicon display controller driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h
> new file mode 100644
> index 0000000000000..5e071501b1c38
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_DC_H_
> +#define _VS_DC_H_
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_device.h>
> +
> +#include "vs_hwdb.h"
> +
> +#define VSDC_MAX_OUTPUTS 2
> +#define VSDC_RESET_COUNT 3
> +
> +struct vs_drm_dev;
> +struct vs_crtc;
> +
> +struct vs_dc {
> + struct regmap *regs;
> + struct clk *core_clk;
> + struct clk *axi_clk;
> + struct clk *ahb_clk;
> + struct clk *pix_clk[VSDC_MAX_OUTPUTS];
> + struct reset_control_bulk_data rsts[VSDC_RESET_COUNT];
> +
> + struct vs_drm_dev *drm_dev;
> + struct vs_chip_identity identity;
> + unsigned int outputs;
> +};
> +
> +#endif /* _VS_DC_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> new file mode 100644
> index 0000000000000..50509bbbff08f
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_DC_TOP_H_
> +#define _VS_DC_TOP_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_TOP_RST 0x0000
> +
> +#define VSDC_TOP_IRQ_ACK 0x0010
> +#define VSDC_TOP_IRQ_VSYNC(n) BIT(n)
> +
> +#define VSDC_TOP_IRQ_EN 0x0014
> +
> +#define VSDC_TOP_CHIP_MODEL 0x0020
> +
> +#define VSDC_TOP_CHIP_REV 0x0024
> +
> +#define VSDC_TOP_CHIP_CUSTOMER_ID 0x0030
> +
> +#endif /* _VS_DC_TOP_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c
> new file mode 100644
> index 0000000000000..f356d7832c449
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_drm.c
> @@ -0,0 +1,177 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/aperture.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/console.h>
> +
> +#include <drm/clients/drm_client_setup.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_dma.h>
> +#include <drm/drm_gem_dma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "vs_bridge.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_drm.h"
> +
> +#define DRIVER_NAME "verisilicon"
> +#define DRIVER_DESC "Verisilicon DC-series display controller driver"
> +#define DRIVER_MAJOR 1
> +#define DRIVER_MINOR 0
> +
> +static int vs_gem_dumb_create(struct drm_file *file_priv,
> +      struct drm_device *drm,
> +      struct drm_mode_create_dumb *args)
> +{
> + /* The hardware wants 128B-aligned pitches for linear buffers. */
> + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 128);
> +
> + return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
> +}
> +
> +DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops);
> +
> +static const struct drm_driver vs_drm_driver = {
> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> + .fops = &vs_drm_driver_fops,
> + .name = DRIVER_NAME,
> + .desc = DRIVER_DESC,
> + .major = DRIVER_MAJOR,
> + .minor = DRIVER_MINOR,
> +
> + /* GEM Operations */
> + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create),
> + DRM_FBDEV_DMA_DRIVER_OPS,
> +};
> +
> +static const struct drm_mode_config_funcs vs_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = {
> + .atomic_commit_tail = drm_atomic_helper_commit_tail,
> +};
> +
> +static void vs_mode_config_init(struct drm_device *drm)
> +{
> + drm_mode_config_reset(drm);
> +
> + drm->mode_config.min_width = 0;
> + drm->mode_config.min_height = 0;
> + drm->mode_config.max_width = 8192;
> + drm->mode_config.max_height = 8192;
> + drm->mode_config.funcs = &vs_mode_config_funcs;
> + drm->mode_config.helper_private = &vs_mode_config_helper_funcs;
> +}
> +
> +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vs_drm_dev *vdrm;
> + struct drm_device *drm;
> + struct vs_crtc *crtc;
> + struct vs_bridge *bridge;
> + unsigned int i;
> + int ret;
> +
> + vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base);
> + if (IS_ERR(vdrm))
> + return PTR_ERR(vdrm);
> +
> + drm = &vdrm->base;
> + vdrm->dc = dc;
> + dc->drm_dev = vdrm;
> +
> + ret = drmm_mode_config_init(drm);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < dc->outputs; i++) {
> + crtc = vs_crtc_init(drm, dc, i);
> + if (IS_ERR(crtc))
> + return PTR_ERR(crtc);
> +
> + bridge = vs_bridge_init(drm, crtc);
> + if (IS_ERR(bridge))
> + return PTR_ERR(bridge);
> +
> + vdrm->crtcs[i] = crtc;
> + }
> +
> + ret = drm_vblank_init(drm, dc->outputs);
> + if (ret)
> + return ret;
> +
> + /* Remove early framebuffers (ie. simplefb) */
> + ret = aperture_remove_all_conflicting_devices(DRIVER_NAME);
> + if (ret)
> + return ret;
> +
> + vs_mode_config_init(drm);
> +
> + /* Enable connectors polling */
> + drm_kms_helper_poll_init(drm);
> +
> + ret = drm_dev_register(drm, 0);
> + if (ret)
> + goto err_fini_poll;
> +
> + drm_client_setup(drm, NULL);
> +
> + return 0;
> +
> +err_fini_poll:
> + drm_kms_helper_poll_fini(drm);
> + return ret;
> +}
> +
> +void vs_drm_finalize(struct vs_dc *dc)
> +{
> + struct vs_drm_dev *vdrm = dc->drm_dev;
> + struct drm_device *drm = &vdrm->base;
> +
> + drm_dev_unregister(drm);
> + drm_kms_helper_poll_fini(drm);
> + drm_atomic_helper_shutdown(drm);
> + dc->drm_dev = NULL;
> +}
> +
> +void vs_drm_shutdown_handler(struct vs_dc *dc)
> +{
> + struct vs_drm_dev *vdrm = dc->drm_dev;
> +
> + drm_atomic_helper_shutdown(&vdrm->base);
> +}
> +
> +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < dc->outputs; i++) {
> + if (irqs & VSDC_TOP_IRQ_VSYNC(i)) {
> + irqs &= ~VSDC_TOP_IRQ_VSYNC(i);
> + if (dc->drm_dev->crtcs[i])
> + drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base);
> + }
> + }
> +
> + if (irqs)
> + pr_warn("Unknown Verisilicon DC interrupt 0x%x fired!\n", irqs);
> +
> + return IRQ_HANDLED;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h
> new file mode 100644
> index 0000000000000..bbcd2e527deb6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_drm.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_DRM_H_
> +#define _VS_DRM_H_
> +
> +#include <linux/irqreturn.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#include <drm/drm_device.h>
> +
> +struct vs_dc;
> +
> +struct vs_drm_dev {
> + struct drm_device base;
> +
> + struct vs_dc *dc;
> + struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS];
> +};
> +
> +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev);
> +void vs_drm_finalize(struct vs_dc *dc);
> +void vs_drm_shutdown_handler(struct vs_dc *dc);
> +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs);
> +
> +#endif /* _VS_DRM_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c
> new file mode 100644
> index 0000000000000..4a87e5d4701f3
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/errno.h>
> +
> +#include <drm/drm_fourcc.h>
> +
> +#include "vs_dc_top_regs.h"
> +#include "vs_hwdb.h"
> +
> +static const u32 vs_formats_array_no_yuv444[] = {
> + DRM_FORMAT_XRGB4444,
> + DRM_FORMAT_XBGR4444,
> + DRM_FORMAT_RGBX4444,
> + DRM_FORMAT_BGRX4444,
> + DRM_FORMAT_ARGB4444,
> + DRM_FORMAT_ABGR4444,
> + DRM_FORMAT_RGBA4444,
> + DRM_FORMAT_BGRA4444,
> + DRM_FORMAT_XRGB1555,
> + DRM_FORMAT_XBGR1555,
> + DRM_FORMAT_RGBX5551,
> + DRM_FORMAT_BGRX5551,
> + DRM_FORMAT_ARGB1555,
> + DRM_FORMAT_ABGR1555,
> + DRM_FORMAT_RGBA5551,
> + DRM_FORMAT_BGRA5551,
> + DRM_FORMAT_RGB565,
> + DRM_FORMAT_BGR565,
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_XBGR8888,
> + DRM_FORMAT_RGBX8888,
> + DRM_FORMAT_BGRX8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_ABGR8888,
> + DRM_FORMAT_RGBA8888,
> + DRM_FORMAT_BGRA8888,
> + DRM_FORMAT_ARGB2101010,
> + DRM_FORMAT_ABGR2101010,
> + DRM_FORMAT_RGBA1010102,
> + DRM_FORMAT_BGRA1010102,
> + /* TODO: non-RGB formats */
> +};
> +
> +static const u32 vs_formats_array_with_yuv444[] = {
> + DRM_FORMAT_XRGB4444,
> + DRM_FORMAT_XBGR4444,
> + DRM_FORMAT_RGBX4444,
> + DRM_FORMAT_BGRX4444,
> + DRM_FORMAT_ARGB4444,
> + DRM_FORMAT_ABGR4444,
> + DRM_FORMAT_RGBA4444,
> + DRM_FORMAT_BGRA4444,
> + DRM_FORMAT_XRGB1555,
> + DRM_FORMAT_XBGR1555,
> + DRM_FORMAT_RGBX5551,
> + DRM_FORMAT_BGRX5551,
> + DRM_FORMAT_ARGB1555,
> + DRM_FORMAT_ABGR1555,
> + DRM_FORMAT_RGBA5551,
> + DRM_FORMAT_BGRA5551,
> + DRM_FORMAT_RGB565,
> + DRM_FORMAT_BGR565,
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_XBGR8888,
> + DRM_FORMAT_RGBX8888,
> + DRM_FORMAT_BGRX8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_ABGR8888,
> + DRM_FORMAT_RGBA8888,
> + DRM_FORMAT_BGRA8888,
> + DRM_FORMAT_ARGB2101010,
> + DRM_FORMAT_ABGR2101010,
> + DRM_FORMAT_RGBA1010102,
> + DRM_FORMAT_BGRA1010102,
> + /* TODO: non-RGB formats */
> +};
> +
> +static const struct vs_formats vs_formats_no_yuv444 = {
> + .array = vs_formats_array_no_yuv444,
> + .num = ARRAY_SIZE(vs_formats_array_no_yuv444)
> +};
> +
> +static const struct vs_formats vs_formats_with_yuv444 = {
> + .array = vs_formats_array_with_yuv444,
> + .num = ARRAY_SIZE(vs_formats_array_with_yuv444)
> +};
> +
> +static struct vs_chip_identity vs_chip_identities[] = {
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = ~0U,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5721,
> + .customer_id = 0x30B,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = 0x310,
> +
> + .display_count = 2,
> + .formats = &vs_formats_with_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = 0x311,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> +};
> +
> +int vs_fill_chip_identity(struct regmap *regs,
> +  struct vs_chip_identity *ident)
> +{
> + u32 model;
> + u32 revision;
> + u32 customer_id;
> + int i;
> +
> + regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model);
> + regmap_read(regs, VSDC_TOP_CHIP_REV, &revision);
> + regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id);
> +
> + for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) {
> + if (vs_chip_identities[i].model == model &&
> +    vs_chip_identities[i].revision == revision &&
> +    (vs_chip_identities[i].customer_id == customer_id ||
> + vs_chip_identities[i].customer_id == ~0U)) {
> + memcpy(ident, &vs_chip_identities[i], sizeof(*ident));
> + ident->customer_id = customer_id;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h
> new file mode 100644
> index 0000000000000..92192e4fa0862
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_HWDB_H_
> +#define _VS_HWDB_H_
> +
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +struct vs_formats {
> + const u32 *array;
> + unsigned int num;
> +};
> +
> +struct vs_chip_identity {
> + u32 model;
> + u32 revision;
> + u32 customer_id;
> +
> + u32 display_count;
> + const struct vs_formats *formats;
> +};
> +
> +int vs_fill_chip_identity(struct regmap *regs,
> +  struct vs_chip_identity *ident);
> +
> +#endif /* _VS_HWDB_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c
> new file mode 100644
> index 0000000000000..f3c9963b6a4ea
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_plane.c
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/errno.h>
> +
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_print.h>
> +
> +#include "vs_plane.h"
> +
> +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format)
> +{
> + switch (drm_format) {
> + case DRM_FORMAT_XRGB4444:
> + case DRM_FORMAT_RGBX4444:
> + case DRM_FORMAT_XBGR4444:
> + case DRM_FORMAT_BGRX4444:
> + vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4;
> + break;
> + case DRM_FORMAT_ARGB4444:
> + case DRM_FORMAT_RGBA4444:
> + case DRM_FORMAT_ABGR4444:
> + case DRM_FORMAT_BGRA4444:
> + vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4;
> + break;
> + case DRM_FORMAT_XRGB1555:
> + case DRM_FORMAT_RGBX5551:
> + case DRM_FORMAT_XBGR1555:
> + case DRM_FORMAT_BGRX5551:
> + vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5;
> + break;
> + case DRM_FORMAT_ARGB1555:
> + case DRM_FORMAT_RGBA5551:
> + case DRM_FORMAT_ABGR1555:
> + case DRM_FORMAT_BGRA5551:
> + vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5;
> + break;
> + case DRM_FORMAT_RGB565:
> + case DRM_FORMAT_BGR565:
> + vs_format->color = VSDC_COLOR_FORMAT_R5G6B5;
> + break;
> + case DRM_FORMAT_XRGB8888:
> + case DRM_FORMAT_RGBX8888:
> + case DRM_FORMAT_XBGR8888:
> + case DRM_FORMAT_BGRX8888:
> + vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8;
> + break;
> + case DRM_FORMAT_ARGB8888:
> + case DRM_FORMAT_RGBA8888:
> + case DRM_FORMAT_ABGR8888:
> + case DRM_FORMAT_BGRA8888:
> + vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8;
> + break;
> + case DRM_FORMAT_ARGB2101010:
> + case DRM_FORMAT_RGBA1010102:
> + case DRM_FORMAT_ABGR2101010:
> + case DRM_FORMAT_BGRA1010102:
> + vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10;
> + break;
> + default:
> + DRM_WARN("Unexpected drm format!\n");
> + }
> +
> + switch (drm_format) {
> + case DRM_FORMAT_RGBX4444:
> + case DRM_FORMAT_RGBA4444:
> + case DRM_FORMAT_RGBX5551:
> + case DRM_FORMAT_RGBA5551:
> + case DRM_FORMAT_RGBX8888:
> + case DRM_FORMAT_RGBA8888:
> + case DRM_FORMAT_RGBA1010102:
> + vs_format->swizzle = VSDC_SWIZZLE_RGBA;
> + break;
> + case DRM_FORMAT_XBGR4444:
> + case DRM_FORMAT_ABGR4444:
> + case DRM_FORMAT_XBGR1555:
> + case DRM_FORMAT_ABGR1555:
> + case DRM_FORMAT_BGR565:
> + case DRM_FORMAT_XBGR8888:
> + case DRM_FORMAT_ABGR8888:
> + case DRM_FORMAT_ABGR2101010:
> + vs_format->swizzle = VSDC_SWIZZLE_ABGR;
> + break;
> + case DRM_FORMAT_BGRX4444:
> + case DRM_FORMAT_BGRA4444:
> + case DRM_FORMAT_BGRX5551:
> + case DRM_FORMAT_BGRA5551:
> + case DRM_FORMAT_BGRX8888:
> + case DRM_FORMAT_BGRA8888:
> + case DRM_FORMAT_BGRA1010102:
> + vs_format->swizzle = VSDC_SWIZZLE_BGRA;
> + break;
> + default:
> + /* N/A for YUV formats */
> + vs_format->swizzle = VSDC_SWIZZLE_ARGB;
> + }
> +
> + /* N/A for non-YUV formats */
> + vs_format->uv_swizzle = false;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h
> new file mode 100644
> index 0000000000000..3595267c89b53
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_plane.h
> @@ -0,0 +1,68 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_PLANE_H_
> +#define _VS_PLANE_H_
> +
> +#include <linux/types.h>
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_plane.h>
> +
> +#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15))
> +#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15))
> +
> +struct vs_dc;
> +
> +enum vs_color_format {
> + VSDC_COLOR_FORMAT_X4R4G4B4,
> + VSDC_COLOR_FORMAT_A4R4G4B4,
> + VSDC_COLOR_FORMAT_X1R5G5B5,
> + VSDC_COLOR_FORMAT_A1R5G5B5,
> + VSDC_COLOR_FORMAT_R5G6B5,
> + VSDC_COLOR_FORMAT_X8R8G8B8,
> + VSDC_COLOR_FORMAT_A8R8G8B8,
> + VSDC_COLOR_FORMAT_YUY2,
> + VSDC_COLOR_FORMAT_UYVY,
> + VSDC_COLOR_FORMAT_INDEX8,
> + VSDC_COLOR_FORMAT_MONOCHROME,
> + VSDC_COLOR_FORMAT_YV12 = 0xf,
> + VSDC_COLOR_FORMAT_A8,
> + VSDC_COLOR_FORMAT_NV12,
> + VSDC_COLOR_FORMAT_NV16,
> + VSDC_COLOR_FORMAT_RG16,
> + VSDC_COLOR_FORMAT_R8,
> + VSDC_COLOR_FORMAT_NV12_10BIT,
> + VSDC_COLOR_FORMAT_A2R10G10B10,
> + VSDC_COLOR_FORMAT_NV16_10BIT,
> + VSDC_COLOR_FORMAT_INDEX1,
> + VSDC_COLOR_FORMAT_INDEX2,
> + VSDC_COLOR_FORMAT_INDEX4,
> + VSDC_COLOR_FORMAT_P010,
> + VSDC_COLOR_FORMAT_YUV444,
> + VSDC_COLOR_FORMAT_YUV444_10BIT
> +};
> +
> +enum vs_swizzle {
> + VSDC_SWIZZLE_ARGB,
> + VSDC_SWIZZLE_RGBA,
> + VSDC_SWIZZLE_ABGR,
> + VSDC_SWIZZLE_BGRA,
> +};
> +
> +struct vs_format {
> + enum vs_color_format color;
> + enum vs_swizzle swizzle;
> + bool uv_swizzle;
> +};
> +
> +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format);
> +
> +struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc);
> +
> +#endif /* _VS_PLANE_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> new file mode 100644
> index 0000000000000..5b03783d4f284
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/regmap.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_fb_dma_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_gem_dma_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_print.h>
> +
> +#include "vs_crtc.h"
> +#include "vs_plane.h"
> +#include "vs_dc.h"
> +#include "vs_primary_plane_regs.h"
> +
> +static int vs_primary_plane_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
> + plane);
> + struct drm_crtc *crtc = new_plane_state->crtc;
> + struct drm_crtc_state *crtc_state;
> +
> + if (!crtc)
> + return 0;
> +
> + crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> + if (WARN_ON(!crtc_state))
> + return -EINVAL;
> +
> + return drm_atomic_helper_check_plane_state(new_plane_state,
> +   crtc_state,
> +   DRM_PLANE_NO_SCALING,
> +   DRM_PLANE_NO_SCALING,
> +   false, true);
> +}
> +
> +
> +static void vs_primary_plane_atomic_update(struct drm_plane *plane,
> +   struct drm_atomic_state *atomic_state)
> +{
> + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state,
> +       plane);
> + struct drm_framebuffer *fb = state->fb;
> + struct drm_crtc *crtc = state->crtc;
> + struct drm_gem_dma_object *gem;
> + struct vs_dc *dc;
> + struct vs_crtc *vcrtc;
> + struct vs_format fmt;
> + unsigned int output, bpp;
> + dma_addr_t dma_addr;
> +
> + if (!crtc)
> + return;
> +
> + vcrtc = drm_crtc_to_vs_crtc(crtc);
> + output = vcrtc->id;
> + dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
> +
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> +   VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK,
> +   VSDC_FB_CONFIG_EX_DISPLAY_ID(output));
> +
> + if (!state->visible || !fb) {
> + regmap_write(dc->regs, VSDC_FB_CONFIG(output), 0);
> + regmap_write(dc->regs, VSDC_FB_CONFIG_EX(output), 0);
> + goto commit;
> + } else {
> + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> + VSDC_FB_CONFIG_EX_FB_EN);
> + }
> +
> + drm_format_to_vs_format(state->fb->format->format, &fmt);
> +
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_FMT_MASK,
> +   VSDC_FB_CONFIG_FMT(fmt.color));
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_SWIZZLE_MASK,
> +   VSDC_FB_CONFIG_SWIZZLE(fmt.swizzle));
> + regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_UV_SWIZZLE_EN, fmt.uv_swizzle);
> +
> + /* Get the physical address of the buffer in memory */
> + gem = drm_fb_dma_get_gem_obj(fb, 0);
> +
> + /* Compute the start of the displayed memory */
> + bpp = fb->format->cpp[0];
> + dma_addr = gem->dma_addr + fb->offsets[0];
> +
> + /* Fixup framebuffer address for src coordinates */
> + dma_addr += (state->src.x1 >> 16) * bpp;
> + dma_addr += (state->src.y1 >> 16) * fb->pitches[0];
> +
> + regmap_write(dc->regs, VSDC_FB_ADDRESS(output),
> +     lower_32_bits(dma_addr));
> + regmap_write(dc->regs, VSDC_FB_STRIDE(output),
> +     fb->pitches[0]);
> +
> + regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output),
> +     VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y));
> + regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output),
> +     VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w,
> + state->crtc_y + state->crtc_h));
> + regmap_write(dc->regs, VSDC_FB_SIZE(output),
> +     VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h));
> +
> + regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output),
> +     VSDC_FB_BLEND_CONFIG_BLEND_DISABLE);
> +commit:
> + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> + VSDC_FB_CONFIG_EX_COMMIT);
> +}
> +
> +static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = {
> + .atomic_check = vs_primary_plane_atomic_check,
> + .atomic_update = vs_primary_plane_atomic_update,
> +};
> +
> +static const struct drm_plane_funcs vs_primary_plane_funcs = {
> + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .reset = drm_atomic_helper_plane_reset,
> + .update_plane = drm_atomic_helper_update_plane,
> +};
> +
> +struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc)
> +{
> + struct drm_plane *plane;
> +
> + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0,
> +   &vs_primary_plane_funcs,
> +   dc->identity.formats->array,
> +   dc->identity.formats->num,
> +   NULL,
> +   DRM_PLANE_TYPE_PRIMARY,
> +   NULL);
> +
> + if (IS_ERR(plane))
> + return plane;
> +
> + drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs);
> +
> + return plane;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> new file mode 100644
> index 0000000000000..cbb125c46b390
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_PRIMARY_PLANE_REGS_H_
> +#define _VS_PRIMARY_PLANE_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_FB_ADDRESS(n) (0x1400 + 0x4 * (n))
> +
> +#define VSDC_FB_STRIDE(n) (0x1408 + 0x4 * (n))
> +
> +#define VSDC_FB_CONFIG(n) (0x1518 + 0x4 * (n))
> +#define VSDC_FB_CONFIG_CLEAR_EN BIT(8)
> +#define VSDC_FB_CONFIG_ROT_MASK GENMASK(13, 11)
> +#define VSDC_FB_CONFIG_ROT(v) ((v) << 11)
> +#define VSDC_FB_CONFIG_YUV_SPACE_MASK GENMASK(16, 14)
> +#define VSDC_FB_CONFIG_YUV_SPACE(v) ((v) << 14)
> +#define VSDC_FB_CONFIG_TILE_MODE_MASK GENMASK(21, 17)
> +#define VSDC_FB_CONFIG_TILE_MODE(v) ((v) << 14)
> +#define VSDC_FB_CONFIG_SCALE_EN BIT(22)
> +#define VSDC_FB_CONFIG_SWIZZLE_MASK GENMASK(24, 23)
> +#define VSDC_FB_CONFIG_SWIZZLE(v) ((v) << 23)
> +#define VSDC_FB_CONFIG_UV_SWIZZLE_EN BIT(25)
> +#define VSDC_FB_CONFIG_FMT_MASK GENMASK(31, 26)
> +#define VSDC_FB_CONFIG_FMT(v) ((v) << 26)
> +
> +#define VSDC_FB_SIZE(n) (0x1810 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */
> +
> +#define VSDC_FB_CONFIG_EX(n) (0x1CC0 + 0x4 * (n))
> +#define VSDC_FB_CONFIG_EX_COMMIT BIT(12)
> +#define VSDC_FB_CONFIG_EX_FB_EN BIT(13)
> +#define VSDC_FB_CONFIG_EX_ZPOS_MASK GENMASK(18, 16)
> +#define VSDC_FB_CONFIG_EX_ZPOS(v) ((v) << 16)
> +#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK GENMASK(19, 19)
> +#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v) ((v) << 19)
> +
> +#define VSDC_FB_TOP_LEFT(n) (0x24D8 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
> +
> +#define VSDC_FB_BOTTOM_RIGHT(n) (0x24E0 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
> +
> +#define VSDC_FB_BLEND_CONFIG(n) (0x2510 + 0x4 * (n))
> +#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE BIT(1)
> +
> +#endif /* _VS_PRIMARY_PLANE_REGS_H_ */
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn <mailto:gaohan@iscas.ac.cn>>


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
  2025-12-25  9:35     ` Han Gao (Revy)
@ 2025-12-25  9:45     ` Han Gao (Revy)
  2025-12-27 11:09       ` Krzysztof Kozlowski
  2026-01-05 15:46     ` Rob Herring
  2 siblings, 1 reply; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:45 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:11, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> Verisilicon has a series of display controllers prefixed with DC and
> with self-identification facility like their GC series GPUs.
> 
> Add a device tree binding for it.
> 
> Depends on the specific DC model, it can have either one or two display
> outputs, and each display output could be set to DPI signal or "DP"
> signal (which seems to be some plain parallel bus to HDMI controllers).
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Added a comment for "verisilicon,dc" that says the ID/revision is
>  discoverable via registers.
> - Removed clock minItems constraint w/o specific compatible strings.
> 
> Changes in v3:
> - Added SoC-specific compatible string, and arm the binding with clock /
>  port checking for the specific SoC (with a 2-output DC).
> 
> Changes in v2:
> - Fixed misspelt "versilicon" in title.
> - Moved minItems in clock properties to be earlier than items.
> - Re-aligned multi-line clocks and resets in example.
> 
> .../bindings/display/verisilicon,dc.yaml      | 144 ++++++++++++++++++
> 1 file changed, 144 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> new file mode 100644
> index 0000000000000..fe64cc1466690
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> @@ -0,0 +1,144 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Verisilicon DC-series display controllers
> +
> +maintainers:
> +  - Icenowy Zheng <uwu@icenowy.me>
> +
> +properties:
> +  $nodename:
> +    pattern: "^display@[0-9a-f]+$"
> +
> +  compatible:
> +    items:
> +      - enum:
> +          - thead,th1520-dc8200
> +      - const: verisilicon,dc # DC IPs have discoverable ID/revision registers
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: DC Core clock
> +      - description: DMA AXI bus clock
> +      - description: Configuration AHB bus clock
> +      - description: Pixel clock of output 0
> +      - description: Pixel clock of output 1
> +
> +  clock-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +      - const: pix0
> +      - const: pix1
> +
> +  resets:
> +    items:
> +      - description: DC Core reset
> +      - description: DMA AXI bus reset
> +      - description: Configuration AHB bus reset
> +
> +  reset-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The first output channel , endpoint 0 should be
> +          used for DPI format output and endpoint 1 should be used
> +          for DP format output.
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The second output channel if the DC variant
> +          supports. Follow the same endpoint addressing rule with
> +          the first port.
> +
> +    required:
> +      - port@0
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - ports
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: thead,th1520-dc8200
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 5
> +        ports:
> +          required:
> +            - port@0
> +            - port@1
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
> +    #include <dt-bindings/reset/thead,th1520-reset.h>
> +    soc {
> +      #address-cells = <2>;
> +      #size-cells = <2>;
> +
> +      display@ffef600000 {
> +        compatible = "thead,th1520-dc8200", "verisilicon,dc";
> +        reg = <0xff 0xef600000 0x0 0x100000>;
> +        interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
> +        clocks = <&clk_vo CLK_DPU_CCLK>,
> +                 <&clk_vo CLK_DPU_ACLK>,
> +                 <&clk_vo CLK_DPU_HCLK>,
> +                 <&clk_vo CLK_DPU_PIXELCLK0>,
> +                 <&clk_vo CLK_DPU_PIXELCLK1>;
> +        clock-names = "core", "axi", "ahb", "pix0", "pix1";
> +        resets = <&rst TH1520_RESET_ID_DPU_CORE>,
> +                 <&rst TH1520_RESET_ID_DPU_AXI>,
> +                 <&rst TH1520_RESET_ID_DPU_AHB>;
> +        reset-names = "core", "axi", "ahb";
> +
> +        ports {
> +          #address-cells = <1>;
> +          #size-cells = <0>;
> +
> +          port@0 {
> +            reg = <0>;
> +          };
> +
> +          port@1 {
> +            reg = <1>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            dpu_out_dp1: endpoint@1 {
> +              reg = <1>;
> +              remote-endpoint = <&hdmi_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> -- 
> 2.52.0
> 

Tested-by: Han Gao <gaohan@iscas.ac.cn>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers
  2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
  2025-12-25  9:36     ` Han Gao (Revy)
@ 2025-12-25  9:45     ` Han Gao (Revy)
  2025-12-31 14:20     ` Luca Ceresoli
  2 siblings, 0 replies; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:45 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:11, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> This is a from-scratch driver targeting Verisilicon DC-series display
> controllers, which feature self-identification functionality like their
> GC-series GPUs.
> 
> Only DC8200 is being supported now, and only the main framebuffer is set
> up (as the DRM primary plane). Support for more DC models and more
> features is my further targets.
> 
> As the display controller is delivered to SoC vendors as a whole part,
> this driver does not use component framework and extra bridges inside a
> SoC is expected to be implemented as dedicated bridges (this driver
> properly supports bridge chaining).
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Switch to drm_* logger when we're handling with struct drm_device.
> 
> Changes in v3:
> - Get rid of drm_atomic_get_existing_crtc_state() which is marked
>  deprecated.
> 
> Changes in v2:
> - Changed some Control flows according to previous reviews.
> - Added missing of_node_put when checking of endpoints for output type.
> - Switched all userspace-visible modeset objects to be managed by drmm
>  instead of devm.
> - Utilize devm_drm_bridge_alloc() in internal bridge.
> - Prevented the usage of simple encoder helpers by passing a NULL funcs pointer.
> - Let devm enable clocks when getting them.
> - Removed explicit `.cache_type = REGCACHE_NONE` in regmap config.
> - Fixed a debug print using a variable before initialization.
> - Fixed a wrong index when using bulk to handle resets.
> - Added missing configuration for DPI format (currently fixed RGB888).
> 
> drivers/gpu/drm/Kconfig                       |   2 +
> drivers/gpu/drm/Makefile                      |   1 +
> drivers/gpu/drm/verisilicon/Kconfig           |  15 +
> drivers/gpu/drm/verisilicon/Makefile          |   5 +
> drivers/gpu/drm/verisilicon/vs_bridge.c       | 331 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h       |  40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h  |  54 +++
> drivers/gpu/drm/verisilicon/vs_crtc.c         | 217 ++++++++++++
> drivers/gpu/drm/verisilicon/vs_crtc.h         |  29 ++
> drivers/gpu/drm/verisilicon/vs_crtc_regs.h    |  60 ++++
> drivers/gpu/drm/verisilicon/vs_dc.c           | 205 +++++++++++
> drivers/gpu/drm/verisilicon/vs_dc.h           |  39 +++
> drivers/gpu/drm/verisilicon/vs_dc_top_regs.h  |  27 ++
> drivers/gpu/drm/verisilicon/vs_drm.c          | 177 ++++++++++
> drivers/gpu/drm/verisilicon/vs_drm.h          |  29 ++
> drivers/gpu/drm/verisilicon/vs_hwdb.c         | 150 ++++++++
> drivers/gpu/drm/verisilicon/vs_hwdb.h         |  29 ++
> drivers/gpu/drm/verisilicon/vs_plane.c        | 102 ++++++
> drivers/gpu/drm/verisilicon/vs_plane.h        |  68 ++++
> .../gpu/drm/verisilicon/vs_primary_plane.c    | 157 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h   |  53 +++
> 21 files changed, 1790 insertions(+)
> create mode 100644 drivers/gpu/drm/verisilicon/Kconfig
> create mode 100644 drivers/gpu/drm/verisilicon/Makefile
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_drm.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_hwdb.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_plane.h
> create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane.c
> create mode 100644 drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 7e6bc0b3a589c..41363da2cc59f 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -398,6 +398,8 @@ source "drivers/gpu/drm/imagination/Kconfig"
> 
> source "drivers/gpu/drm/tyr/Kconfig"
> 
> +source "drivers/gpu/drm/verisilicon/Kconfig"
> +
> config DRM_HYPERV
> tristate "DRM Support for Hyper-V synthetic video device"
> depends on DRM && PCI && HYPERV_VMBUS
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 0e1c668b46d21..f2dfa0ad0ab78 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -235,6 +235,7 @@ obj-y += solomon/
> obj-$(CONFIG_DRM_SPRD) += sprd/
> obj-$(CONFIG_DRM_LOONGSON) += loongson/
> obj-$(CONFIG_DRM_POWERVR) += imagination/
> +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon/
> 
> # Ensure drm headers are self-contained and pass kernel-doc
> hdrtest-files := \
> diff --git a/drivers/gpu/drm/verisilicon/Kconfig b/drivers/gpu/drm/verisilicon/Kconfig
> new file mode 100644
> index 0000000000000..0235577c72824
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config DRM_VERISILICON_DC
> + tristate "DRM Support for Verisilicon DC-series display controllers"
> + depends on DRM && COMMON_CLK
> + depends on RISCV || COMPILER_TEST
> + select DRM_CLIENT_SELECTION
> + select DRM_GEM_DMA_HELPER
> + select DRM_KMS_HELPER
> + select DRM_BRIDGE_CONNECTOR
> + select REGMAP_MMIO
> + select VIDEOMODE_HELPERS
> + help
> +  Choose this option if you have a SoC with Verisilicon DC-series
> +  display controllers. If M is selected, the module will be called
> +  verisilicon-dc.
> diff --git a/drivers/gpu/drm/verisilicon/Makefile b/drivers/gpu/drm/verisilicon/Makefile
> new file mode 100644
> index 0000000000000..fd8d805fbcde1
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/Makefile
> @@ -0,0 +1,5 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +
> +verisilicon-dc-objs := vs_bridge.o vs_crtc.o vs_dc.o vs_drm.o vs_hwdb.o vs_plane.o vs_primary_plane.o
> +
> +obj-$(CONFIG_DRM_VERISILICON_DC) += verisilicon-dc.o
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c
> new file mode 100644
> index 0000000000000..90e4aa8012fae
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> @@ -0,0 +1,331 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/of.h>
> +#include <linux/regmap.h>
> +
> +#include <uapi/linux/media-bus-format.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_bridge_connector.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_simple_kms_helper.h>
> +
> +#include "vs_bridge.h"
> +#include "vs_bridge_regs.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +
> +static int vs_bridge_attach(struct drm_bridge *bridge,
> +    struct drm_encoder *encoder,
> +    enum drm_bridge_attach_flags flags)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + return drm_bridge_attach(encoder, vbridge->next,
> + bridge, flags);
> +}
> +
> +struct vsdc_dp_format {
> + u32 linux_fmt;
> + bool is_yuv;
> + u32 vsdc_fmt;
> +};
> +
> +static struct vsdc_dp_format vsdc_dp_supported_fmts[] = {
> + /* default to RGB888 */
> + { MEDIA_BUS_FMT_FIXED, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { MEDIA_BUS_FMT_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { MEDIA_BUS_FMT_RGB565_1X16, false, VSDC_DISP_DP_CONFIG_FMT_RGB565 },
> + { MEDIA_BUS_FMT_RGB666_1X18, false, VSDC_DISP_DP_CONFIG_FMT_RGB666 },
> + { MEDIA_BUS_FMT_RGB101010_1X30,
> +  false, VSDC_DISP_DP_CONFIG_FMT_RGB101010 },
> + { MEDIA_BUS_FMT_UYVY8_1X16, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 },
> + { MEDIA_BUS_FMT_UYVY10_1X20, true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 },
> + { MEDIA_BUS_FMT_YUV8_1X24, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 },
> + { MEDIA_BUS_FMT_YUV10_1X30, true, VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 },
> + { MEDIA_BUS_FMT_UYYVYY8_0_5X24,
> +  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 },
> + { MEDIA_BUS_FMT_UYYVYY10_0_5X30,
> +  true, VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 },
> +};
> +
> +static u32 *vs_bridge_atomic_get_output_bus_fmts(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state,
> + unsigned int *num_output_fmts)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + u32 *output_fmts;
> + unsigned int i;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> + *num_output_fmts = 2;
> + else
> + *num_output_fmts = ARRAY_SIZE(vsdc_dp_supported_fmts);
> +
> + output_fmts = kcalloc(*num_output_fmts, sizeof(*output_fmts),
> +      GFP_KERNEL);
> + if (!output_fmts)
> + return NULL;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI) {
> + /* TODO: support more DPI output formats */
> + output_fmts[0] = MEDIA_BUS_FMT_RGB888_1X24;
> + output_fmts[1] = MEDIA_BUS_FMT_FIXED;
> + } else {
> + for (i = 0; i < *num_output_fmts; i++)
> + output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
> + }
> +
> + return output_fmts;
> +}
> +
> +static bool vs_bridge_out_dp_fmt_supported(u32 out_fmt)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++)
> + if (vsdc_dp_supported_fmts[i].linux_fmt == out_fmt)
> + return true;
> +
> + return false;
> +}
> +
> +static u32 *vs_bridge_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state,
> + struct drm_crtc_state *crtc_state,
> + struct drm_connector_state *conn_state,
> + u32 output_fmt,
> + unsigned int *num_input_fmts)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
> +    !vs_bridge_out_dp_fmt_supported(output_fmt)) {
> + *num_input_fmts = 0;
> + return NULL;
> + }
> +
> + return drm_atomic_helper_bridge_propagate_bus_fmt(bridge, bridge_state,
> +  crtc_state,
> +  conn_state,
> +  output_fmt,
> +  num_input_fmts);
> +}
> +
> +static int vs_bridge_atomic_check(struct drm_bridge *bridge,
> +  struct drm_bridge_state *bridge_state,
> +  struct drm_crtc_state *crtc_state,
> +  struct drm_connector_state *conn_state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DP &&
> +    !vs_bridge_out_dp_fmt_supported(bridge_state->output_bus_cfg.format))
> + return -EINVAL;
> +
> + vbridge->output_bus_fmt = bridge_state->output_bus_cfg.format;
> +
> + return 0;
> +}
> +
> +static void vs_bridge_atomic_enable(struct drm_bridge *bridge,
> +    struct drm_atomic_state *state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + struct drm_bridge_state *br_state = drm_atomic_get_bridge_state(state,
> + bridge);
> + struct vs_crtc *crtc = vbridge->crtc;
> + struct vs_dc *dc = crtc->dc;
> + unsigned int output = crtc->id;
> + u32 dp_fmt;
> + unsigned int i;
> +
> + DRM_DEBUG_DRIVER("Enabling output %u\n", output);
> +
> + switch (vbridge->intf) {
> + case VSDC_OUTPUT_INTERFACE_DPI:
> + regmap_clear_bits(dc->regs, VSDC_DISP_DP_CONFIG(output),
> +  VSDC_DISP_DP_CONFIG_DP_EN);
> + regmap_write(dc->regs, VSDC_DISP_DPI_CONFIG(output),
> +     VSDC_DISP_DPI_CONFIG_FMT_RGB888);
> + break;
> + case VSDC_OUTPUT_INTERFACE_DP:
> + for (i = 0; i < ARRAY_SIZE(vsdc_dp_supported_fmts); i++) {
> + if (vsdc_dp_supported_fmts[i].linux_fmt ==
> +    vbridge->output_bus_fmt)
> + break;
> + }
> + if (WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts)))
> + return;
> + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> + dp_fmt |= VSDC_DISP_DP_CONFIG_DP_EN;
> + regmap_write(dc->regs, VSDC_DISP_DP_CONFIG(output), dp_fmt);
> + regmap_assign_bits(dc->regs,
> +   VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_YUV,
> +   vsdc_dp_supported_fmts[i].is_yuv);
> + break;
> + }
> +
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +  VSDC_DISP_PANEL_CONFIG_DAT_POL);
> + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_DE_POL,
> +   br_state->output_bus_cfg.flags &
> +   DRM_BUS_FLAG_DE_LOW);
> + regmap_assign_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +   VSDC_DISP_PANEL_CONFIG_CLK_POL,
> +   br_state->output_bus_cfg.flags &
> +   DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> + VSDC_DISP_PANEL_CONFIG_DE_EN |
> + VSDC_DISP_PANEL_CONFIG_DAT_EN |
> + VSDC_DISP_PANEL_CONFIG_CLK_EN);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> + VSDC_DISP_PANEL_CONFIG_RUNNING);
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
> +  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC);
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_START,
> + VSDC_DISP_PANEL_START_RUNNING(output));
> +
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
> + VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
> +}
> +
> +static void vs_bridge_atomic_disable(struct drm_bridge *bridge,
> +     struct drm_atomic_state *state)
> +{
> + struct vs_bridge *vbridge = drm_bridge_to_vs_bridge(bridge);
> + struct vs_crtc *crtc = vbridge->crtc;
> + struct vs_dc *dc = crtc->dc;
> + unsigned int output = crtc->id;
> +
> + DRM_DEBUG_DRIVER("Disabling output %u\n", output);
> +
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_START,
> +  VSDC_DISP_PANEL_START_MULTI_DISP_SYNC |
> +  VSDC_DISP_PANEL_START_RUNNING(output));
> + regmap_clear_bits(dc->regs, VSDC_DISP_PANEL_CONFIG(output),
> +  VSDC_DISP_PANEL_CONFIG_RUNNING);
> +
> + regmap_set_bits(dc->regs, VSDC_DISP_PANEL_CONFIG_EX(crtc->id),
> + VSDC_DISP_PANEL_CONFIG_EX_COMMIT);
> +}
> +
> +static const struct drm_bridge_funcs vs_bridge_funcs = {
> + .attach = vs_bridge_attach,
> + .atomic_enable = vs_bridge_atomic_enable,
> + .atomic_disable = vs_bridge_atomic_disable,
> + .atomic_check = vs_bridge_atomic_check,
> + .atomic_get_input_bus_fmts = vs_bridge_atomic_get_input_bus_fmts,
> + .atomic_get_output_bus_fmts = vs_bridge_atomic_get_output_bus_fmts,
> + .atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
> + .atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
> + .atomic_reset = drm_atomic_helper_bridge_reset,
> +};
> +
> +static int vs_bridge_detect_output_interface(struct device_node *of_node,
> +     unsigned int output)
> +{
> + int ret;
> + struct device_node *remote;
> +
> + remote = of_graph_get_remote_node(of_node, output,
> +  VSDC_OUTPUT_INTERFACE_DPI);
> + if (remote) {
> + ret = VSDC_OUTPUT_INTERFACE_DPI;
> + } else {
> + remote = of_graph_get_remote_node(of_node, output,
> +  VSDC_OUTPUT_INTERFACE_DP);
> + if (remote)
> + ret = VSDC_OUTPUT_INTERFACE_DP;
> + else
> + ret = -ENODEV;
> + }
> +
> + if (remote)
> + of_node_put(remote);
> +
> + return ret;
> +}
> +
> +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
> + struct vs_crtc *crtc)
> +{
> + unsigned int output = crtc->id;
> + struct vs_bridge *bridge;
> + struct drm_bridge *next;
> + enum vs_bridge_output_interface intf;
> + int ret, enctype;
> +
> + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
> + output);
> + if (intf == -ENODEV) {
> + drm_info(drm_dev, "Skipping output %u\n", output);
> + return NULL;
> + }
> +
> + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
> +      output, intf);
> + if (IS_ERR(next)) {
> + ret = PTR_ERR(next);
> + if (ret != -EPROBE_DEFER)
> + drm_err(drm_dev,
> + "Cannot get downstream bridge of output %u\n",
> + output);
> + return ERR_PTR(ret);
> + }
> +
> + bridge = devm_drm_bridge_alloc(drm_dev->dev, struct vs_bridge, base,
> +       &vs_bridge_funcs);
> + if (!bridge)
> + return ERR_PTR(-ENOMEM);
> +
> + bridge->crtc = crtc;
> + bridge->intf = intf;
> + bridge->next = next;
> +
> + if (intf == VSDC_OUTPUT_INTERFACE_DPI)
> + enctype = DRM_MODE_ENCODER_DPI;
> + else
> + enctype = DRM_MODE_ENCODER_NONE;
> +
> + bridge->enc = drmm_plain_encoder_alloc(drm_dev, NULL, enctype, NULL);
> + if (IS_ERR(bridge->enc)) {
> + drm_err(drm_dev,
> + "Cannot initialize encoder for output %u\n", output);
> + ret = PTR_ERR(bridge->enc);
> + return ERR_PTR(ret);
> + }
> +
> + bridge->enc->possible_crtcs = drm_crtc_mask(&crtc->base);
> +
> + ret = drm_bridge_attach(bridge->enc, &bridge->base, NULL,
> + DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> + if (ret) {
> + drm_err(drm_dev,
> + "Cannot attach bridge for output %u\n", output);
> + return ERR_PTR(ret);
> + }
> +
> + bridge->conn = drm_bridge_connector_init(drm_dev, bridge->enc);
> + if (IS_ERR(bridge->conn)) {
> + drm_err(drm_dev,
> + "Cannot create connector for output %u\n", output);
> + ret = PTR_ERR(bridge->conn);
> + return ERR_PTR(ret);
> + }
> + drm_connector_attach_encoder(bridge->conn, bridge->enc);
> +
> + return bridge;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
> new file mode 100644
> index 0000000000000..4120abafdaed6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.h
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_BRIDGE_H_
> +#define _VS_BRIDGE_H_
> +
> +#include <linux/types.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +
> +struct vs_crtc;
> +
> +enum vs_bridge_output_interface {
> + VSDC_OUTPUT_INTERFACE_DPI = 0,
> + VSDC_OUTPUT_INTERFACE_DP = 1
> +};
> +
> +struct vs_bridge {
> + struct drm_bridge base;
> + struct drm_encoder *enc;
> + struct drm_connector *conn;
> +
> + struct vs_crtc *crtc;
> + struct drm_bridge *next;
> + enum vs_bridge_output_interface intf;
> + u32 output_bus_fmt;
> +};
> +
> +static inline struct vs_bridge *drm_bridge_to_vs_bridge(struct drm_bridge *bridge)
> +{
> + return container_of(bridge, struct vs_bridge, base);
> +}
> +
> +struct vs_bridge *vs_bridge_init(struct drm_device *drm_dev,
> + struct vs_crtc *crtc);
> +#endif /* _VS_BRIDGE_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge_regs.h b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> new file mode 100644
> index 0000000000000..9eb30e4564beb
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_BRIDGE_REGS_H_
> +#define _VS_BRIDGE_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_DISP_PANEL_CONFIG(n) (0x1418 + 0x4 * (n))
> +#define VSDC_DISP_PANEL_CONFIG_DE_EN BIT(0)
> +#define VSDC_DISP_PANEL_CONFIG_DE_POL BIT(1)
> +#define VSDC_DISP_PANEL_CONFIG_DAT_EN BIT(4)
> +#define VSDC_DISP_PANEL_CONFIG_DAT_POL BIT(5)
> +#define VSDC_DISP_PANEL_CONFIG_CLK_EN BIT(8)
> +#define VSDC_DISP_PANEL_CONFIG_CLK_POL BIT(9)
> +#define VSDC_DISP_PANEL_CONFIG_RUNNING BIT(12)
> +#define VSDC_DISP_PANEL_CONFIG_GAMMA BIT(13)
> +#define VSDC_DISP_PANEL_CONFIG_YUV BIT(16)
> +
> +#define VSDC_DISP_DPI_CONFIG(n) (0x14B8 + 0x4 * (n))
> +#define VSDC_DISP_DPI_CONFIG_FMT_MASK GENMASK(2, 0)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB565 (0)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB666 (3)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB888 (5)
> +#define VSDC_DISP_DPI_CONFIG_FMT_RGB101010 (6)
> +
> +#define VSDC_DISP_PANEL_START 0x1CCC
> +#define VSDC_DISP_PANEL_START_RUNNING(n) BIT(n)
> +#define VSDC_DISP_PANEL_START_MULTI_DISP_SYNC BIT(3)
> +
> +#define VSDC_DISP_DP_CONFIG(n) (0x1CD0 + 0x4 * (n))
> +#define VSDC_DISP_DP_CONFIG_DP_EN BIT(3)
> +#define VSDC_DISP_DP_CONFIG_FMT_MASK GENMASK(2, 0)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB565 (0)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB666 (1)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB888 (2)
> +#define VSDC_DISP_DP_CONFIG_FMT_RGB101010 (3)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_MASK GENMASK(7, 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY8 (2 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV8 (4 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYVY10 (8 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_YUV10 (10 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY8 (12 << 4)
> +#define VSDC_DISP_DP_CONFIG_YUV_FMT_UYYVYY10 (13 << 4)
> +
> +#define VSDC_DISP_PANEL_CONFIG_EX(n) (0x2518 + 0x4 * (n))
> +#define VSDC_DISP_PANEL_CONFIG_EX_COMMIT BIT(0)
> +
> +#endif /* _VS_BRIDGE_REGS_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c
> new file mode 100644
> index 0000000000000..bd5304a25368a
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
> @@ -0,0 +1,217 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_print.h>
> +#include <drm/drm_managed.h>
> +
> +#include "vs_crtc_regs.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_plane.h"
> +
> +static void vs_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state,
> +  crtc);
> + struct drm_pending_vblank_event *event = crtc_state->event;
> +
> + DRM_DEBUG_DRIVER("Flushing CRTC %u vblank events\n", vcrtc->id);
> +
> + if (event) {
> + crtc_state->event = NULL;
> +
> + spin_lock_irq(&crtc->dev->event_lock);
> + if (drm_crtc_vblank_get(crtc) == 0)
> + drm_crtc_arm_vblank_event(crtc, event);
> + else
> + drm_crtc_send_vblank_event(crtc, event);
> + spin_unlock_irq(&crtc->dev->event_lock);
> + }
> +}
> +
> +static void vs_crtc_atomic_disable(struct drm_crtc *crtc,
> +   struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Disabling CRTC %u\n", output);
> +
> + drm_crtc_vblank_off(crtc);
> +
> + clk_disable_unprepare(dc->pix_clk[output]);
> +}
> +
> +static void vs_crtc_atomic_enable(struct drm_crtc *crtc,
> +     struct drm_atomic_state *state)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Enabling CRTC %u\n", output);
> +
> + WARN_ON(clk_prepare_enable(dc->pix_clk[output]));
> +
> + drm_crtc_vblank_on(crtc);
> +}
> +
> +static void vs_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> + struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> +
> + DRM_DEBUG_DRIVER("Setting mode on CRTC %u\n", output);
> +
> + regmap_write(dc->regs, VSDC_DISP_HSIZE(output),
> +     VSDC_DISP_HSIZE_DISP(mode->hdisplay) |
> +     VSDC_DISP_HSIZE_TOTAL(mode->htotal));
> + regmap_write(dc->regs, VSDC_DISP_VSIZE(output),
> +     VSDC_DISP_VSIZE_DISP(mode->vdisplay) |
> +     VSDC_DISP_VSIZE_TOTAL(mode->vtotal));
> + regmap_write(dc->regs, VSDC_DISP_HSYNC(output),
> +     VSDC_DISP_HSYNC_START(mode->hsync_start) |
> +     VSDC_DISP_HSYNC_END(mode->hsync_end) |
> +     VSDC_DISP_HSYNC_EN);
> + if (!(mode->flags & DRM_MODE_FLAG_PHSYNC))
> + regmap_set_bits(dc->regs, VSDC_DISP_HSYNC(output),
> + VSDC_DISP_HSYNC_POL);
> + regmap_write(dc->regs, VSDC_DISP_VSYNC(output),
> +     VSDC_DISP_VSYNC_START(mode->vsync_start) |
> +     VSDC_DISP_VSYNC_END(mode->vsync_end) |
> +     VSDC_DISP_VSYNC_EN);
> + if (!(mode->flags & DRM_MODE_FLAG_PVSYNC))
> + regmap_set_bits(dc->regs, VSDC_DISP_VSYNC(output),
> + VSDC_DISP_VSYNC_POL);
> +
> + WARN_ON(clk_set_rate(dc->pix_clk[output], mode->crtc_clock * 1000));
> +}
> +
> +static enum drm_mode_status
> +vs_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> + long rate;
> +
> + if (mode->htotal > 0x7FFF)
> + return MODE_BAD_HVALUE;
> + if (mode->vtotal > 0x7FFF)
> + return MODE_BAD_VVALUE;
> +
> + rate = clk_round_rate(dc->pix_clk[output], mode->clock * 1000);
> + if (rate <= 0)
> + return MODE_CLOCK_RANGE;
> +
> + return MODE_OK;
> +}
> +
> +static bool vs_crtc_mode_fixup(struct drm_crtc *crtc,
> +       const struct drm_display_mode *m,
> +       struct drm_display_mode *adjusted_mode)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> + unsigned int output = vcrtc->id;
> + long clk_rate;
> +
> + drm_mode_set_crtcinfo(adjusted_mode, 0);
> +
> + /* Feedback the pixel clock to crtc_clock */
> + clk_rate = adjusted_mode->crtc_clock * 1000;
> + clk_rate = clk_round_rate(dc->pix_clk[output], clk_rate);
> + if (clk_rate <= 0)
> + return false;
> +
> + adjusted_mode->crtc_clock = clk_rate / 1000;
> +
> + return true;
> +}
> +
> +static const struct drm_crtc_helper_funcs vs_crtc_helper_funcs = {
> + .atomic_flush = vs_crtc_atomic_flush,
> + .atomic_enable = vs_crtc_atomic_enable,
> + .atomic_disable = vs_crtc_atomic_disable,
> + .mode_set_nofb = vs_crtc_mode_set_nofb,
> + .mode_valid = vs_crtc_mode_valid,
> + .mode_fixup = vs_crtc_mode_fixup,
> +};
> +
> +static int vs_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Enabling VBLANK on CRTC %u\n", vcrtc->id);
> + regmap_set_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
> +
> + return 0;
> +}
> +
> +static void vs_crtc_disable_vblank(struct drm_crtc *crtc)
> +{
> + struct vs_crtc *vcrtc = drm_crtc_to_vs_crtc(crtc);
> + struct vs_dc *dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Disabling VBLANK on CRTC %u\n", vcrtc->id);
> + regmap_clear_bits(dc->regs, VSDC_TOP_IRQ_EN, VSDC_TOP_IRQ_VSYNC(vcrtc->id));
> +}
> +
> +static const struct drm_crtc_funcs vs_crtc_funcs = {
> + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> + .page_flip = drm_atomic_helper_page_flip,
> + .reset = drm_atomic_helper_crtc_reset,
> + .set_config = drm_atomic_helper_set_config,
> + .enable_vblank = vs_crtc_enable_vblank,
> + .disable_vblank = vs_crtc_disable_vblank,
> +};
> +
> +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
> +     unsigned int output)
> +{
> + struct vs_crtc *vcrtc;
> + struct drm_plane *primary;
> + int ret;
> +
> + vcrtc = drmm_kzalloc(drm_dev, sizeof(*vcrtc), GFP_KERNEL);
> + if (!vcrtc)
> + return ERR_PTR(-ENOMEM);
> + vcrtc->dc = dc;
> + vcrtc->id = output;
> +
> + /* Create our primary plane */
> + primary = vs_primary_plane_init(drm_dev, dc);
> + if (IS_ERR(primary)) {
> + drm_err(drm_dev, "Couldn't create the primary plane\n");
> + return ERR_PTR(PTR_ERR(primary));
> + }
> +
> + ret = drmm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> + primary,
> + NULL,
> + &vs_crtc_funcs,
> + NULL);
> + if (ret) {
> + drm_err(drm_dev, "Couldn't initialize CRTC\n");
> + return ERR_PTR(ret);
> + }
> +
> + drm_crtc_helper_add(&vcrtc->base, &vs_crtc_helper_funcs);
> +
> + return vcrtc;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.h b/drivers/gpu/drm/verisilicon/vs_crtc.h
> new file mode 100644
> index 0000000000000..6f862d609b984
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_CRTC_H_
> +#define _VS_CRTC_H_
> +
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_vblank.h>
> +
> +struct vs_dc;
> +
> +struct vs_crtc {
> + struct drm_crtc base;
> +
> + struct vs_dc *dc;
> + unsigned int id;
> +};
> +
> +static inline struct vs_crtc *drm_crtc_to_vs_crtc(struct drm_crtc *crtc)
> +{
> + return container_of(crtc, struct vs_crtc, base);
> +}
> +
> +struct vs_crtc *vs_crtc_init(struct drm_device *drm_dev, struct vs_dc *dc,
> +     unsigned int output);
> +
> +#endif /* _VS_CRTC_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc_regs.h b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> new file mode 100644
> index 0000000000000..c7930e817635c
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc_regs.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_CRTC_REGS_H_
> +#define _VS_CRTC_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_DISP_DITHER_CONFIG(n) (0x1410 + 0x4 * (n))
> +
> +#define VSDC_DISP_DITHER_TABLE_LOW(n) (0x1420 + 0x4 * (n))
> +#define VSDC_DISP_DITHER_TABLE_LOW_DEFAULT 0x7B48F3C0
> +
> +#define VSDC_DISP_DITHER_TABLE_HIGH(n) (0x1428 + 0x4 * (n))
> +#define VSDC_DISP_DITHER_TABLE_HIGH_DEFAULT 0x596AD1E2
> +
> +#define VSDC_DISP_HSIZE(n) (0x1430 + 0x4 * (n))
> +#define VSDC_DISP_HSIZE_DISP_MASK GENMASK(14, 0)
> +#define VSDC_DISP_HSIZE_DISP(v) ((v) << 0)
> +#define VSDC_DISP_HSIZE_TOTAL_MASK GENMASK(30, 16)
> +#define VSDC_DISP_HSIZE_TOTAL(v) ((v) << 16)
> +
> +#define VSDC_DISP_HSYNC(n) (0x1438 + 0x4 * (n))
> +#define VSDC_DISP_HSYNC_START_MASK GENMASK(14, 0)
> +#define VSDC_DISP_HSYNC_START(v) ((v) << 0)
> +#define VSDC_DISP_HSYNC_END_MASK GENMASK(29, 15)
> +#define VSDC_DISP_HSYNC_END(v) ((v) << 15)
> +#define VSDC_DISP_HSYNC_EN BIT(30)
> +#define VSDC_DISP_HSYNC_POL BIT(31)
> +
> +#define VSDC_DISP_VSIZE(n) (0x1440 + 0x4 * (n))
> +#define VSDC_DISP_VSIZE_DISP_MASK GENMASK(14, 0)
> +#define VSDC_DISP_VSIZE_DISP(v) ((v) << 0)
> +#define VSDC_DISP_VSIZE_TOTAL_MASK GENMASK(30, 16)
> +#define VSDC_DISP_VSIZE_TOTAL(v) ((v) << 16)
> +
> +#define VSDC_DISP_VSYNC(n) (0x1448 + 0x4 * (n))
> +#define VSDC_DISP_VSYNC_START_MASK GENMASK(14, 0)
> +#define VSDC_DISP_VSYNC_START(v) ((v) << 0)
> +#define VSDC_DISP_VSYNC_END_MASK GENMASK(29, 15)
> +#define VSDC_DISP_VSYNC_END(v) ((v) << 15)
> +#define VSDC_DISP_VSYNC_EN BIT(30)
> +#define VSDC_DISP_VSYNC_POL BIT(31)
> +
> +#define VSDC_DISP_CURRENT_LOCATION(n) (0x1450 + 0x4 * (n))
> +
> +#define VSDC_DISP_GAMMA_INDEX(n) (0x1458 + 0x4 * (n))
> +
> +#define VSDC_DISP_GAMMA_DATA(n) (0x1460 + 0x4 * (n))
> +
> +#define VSDC_DISP_IRQ_STA 0x147C
> +
> +#define VSDC_DISP_IRQ_EN 0x1480
> +
> +#endif /* _VS_CRTC_REGS_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
> new file mode 100644
> index 0000000000000..a413479c6cfff
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> @@ -0,0 +1,205 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_drm.h"
> +#include "vs_hwdb.h"
> +
> +static const struct regmap_config vs_dc_regmap_cfg = {
> + .reg_bits = 32,
> + .val_bits = 32,
> + .reg_stride = sizeof(u32),
> + /* VSDC_OVL_CONFIG_EX(1) */
> + .max_register = 0x2544,
> +};
> +
> +static const struct of_device_id vs_dc_driver_dt_match[] = {
> + { .compatible = "verisilicon,dc" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, vs_dc_driver_dt_match);
> +
> +static irqreturn_t vs_dc_irq_handler(int irq, void *private)
> +{
> + struct vs_dc *dc = private;
> + u32 irqs;
> +
> + regmap_read(dc->regs, VSDC_TOP_IRQ_ACK, &irqs);
> +
> + return vs_drm_handle_irq(dc, irqs);
> +}
> +
> +static int vs_dc_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vs_dc *dc;
> + void __iomem *regs;
> + unsigned int outputs, i;
> + /* pix0/pix1 */
> + char pixclk_name[5];
> + int irq, ret;
> +
> + if (!dev->of_node) {
> + dev_err(dev, "can't find DC devices\n");
> + return -ENODEV;
> + }
> +
> + outputs = of_graph_get_port_count(dev->of_node);
> + if (!outputs) {
> + dev_err(dev, "can't find DC downstream ports\n");
> + return -ENODEV;
> + }
> + if (outputs > VSDC_MAX_OUTPUTS) {
> + dev_err(dev, "too many DC downstream ports than possible\n");
> + return -EINVAL;
> + }
> +
> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret) {
> + dev_err(dev, "No suitable DMA available\n");
> + return ret;
> + }
> +
> + dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
> + if (!dc)
> + return -ENOMEM;
> +
> + dc->outputs = outputs;
> +
> + dc->rsts[0].id = "core";
> + dc->rsts[1].id = "axi";
> + dc->rsts[2].id = "ahb";
> +
> + ret = devm_reset_control_bulk_get_optional_shared(dev, VSDC_RESET_COUNT,
> +  dc->rsts);
> + if (ret) {
> + dev_err(dev, "can't get reset lines\n");
> + return ret;
> + }
> +
> + dc->core_clk = devm_clk_get_enabled(dev, "core");
> + if (IS_ERR(dc->core_clk)) {
> + dev_err(dev, "can't get core clock\n");
> + return PTR_ERR(dc->core_clk);
> + }
> +
> + dc->axi_clk = devm_clk_get_enabled(dev, "axi");
> + if (IS_ERR(dc->axi_clk)) {
> + dev_err(dev, "can't get axi clock\n");
> + return PTR_ERR(dc->axi_clk);
> + }
> +
> + dc->ahb_clk = devm_clk_get_enabled(dev, "ahb");
> + if (IS_ERR(dc->ahb_clk)) {
> + dev_err(dev, "can't get ahb clock\n");
> + return PTR_ERR(dc->ahb_clk);
> + }
> +
> + for (i = 0; i < outputs; i++) {
> + snprintf(pixclk_name, sizeof(pixclk_name), "pix%u", i);
> + dc->pix_clk[i] = devm_clk_get(dev, pixclk_name);
> + if (IS_ERR(dc->pix_clk[i])) {
> + dev_err(dev, "can't get pixel clk %u\n", i);
> + return PTR_ERR(dc->pix_clk[i]);
> + }
> + }
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + dev_err(dev, "can't get irq\n");
> + return irq;
> + }
> +
> + ret = reset_control_bulk_deassert(VSDC_RESET_COUNT, dc->rsts);
> + if (ret) {
> + dev_err(dev, "can't deassert reset lines\n");
> + return ret;
> + }
> +
> + regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(regs)) {
> + dev_err(dev, "can't map registers");
> + ret = PTR_ERR(regs);
> + goto err_rst_assert;
> + }
> +
> + dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
> + if (IS_ERR(dc->regs)) {
> + ret = PTR_ERR(dc->regs);
> + goto err_rst_assert;
> + }
> +
> + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
> + if (ret)
> + goto err_rst_assert;
> +
> + dev_info(dev, "DC%x rev %x customer %x\n", dc->identity.model,
> + dc->identity.revision, dc->identity.customer_id);
> +
> + if (outputs > dc->identity.display_count) {
> + dev_err(dev, "too many downstream ports than HW capability\n");
> + ret = -EINVAL;
> + goto err_rst_assert;
> + }
> +
> + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> +       dev_name(dev), dc);
> + if (ret) {
> + dev_err(dev, "can't request irq\n");
> + goto err_rst_assert;
> + }
> +
> + dev_set_drvdata(dev, dc);
> +
> + ret = vs_drm_initialize(dc, pdev);
> + if (ret)
> + goto err_rst_assert;
> +
> + return 0;
> +
> +err_rst_assert:
> + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> + return ret;
> +}
> +
> +static void vs_dc_remove(struct platform_device *pdev)
> +{
> + struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
> +
> + vs_drm_finalize(dc);
> +
> + dev_set_drvdata(&pdev->dev, NULL);
> +
> + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> +}
> +
> +static void vs_dc_shutdown(struct platform_device *pdev)
> +{
> + struct vs_dc *dc = dev_get_drvdata(&pdev->dev);
> +
> + vs_drm_shutdown_handler(dc);
> +}
> +
> +struct platform_driver vs_dc_platform_driver = {
> + .probe = vs_dc_probe,
> + .remove = vs_dc_remove,
> + .shutdown = vs_dc_shutdown,
> + .driver = {
> + .name = "verisilicon-dc",
> + .of_match_table = vs_dc_driver_dt_match,
> + },
> +};
> +
> +module_platform_driver(vs_dc_platform_driver);
> +
> +MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
> +MODULE_DESCRIPTION("Verisilicon display controller driver");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc.h b/drivers/gpu/drm/verisilicon/vs_dc.h
> new file mode 100644
> index 0000000000000..5e071501b1c38
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_DC_H_
> +#define _VS_DC_H_
> +
> +#include <linux/clk.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_device.h>
> +
> +#include "vs_hwdb.h"
> +
> +#define VSDC_MAX_OUTPUTS 2
> +#define VSDC_RESET_COUNT 3
> +
> +struct vs_drm_dev;
> +struct vs_crtc;
> +
> +struct vs_dc {
> + struct regmap *regs;
> + struct clk *core_clk;
> + struct clk *axi_clk;
> + struct clk *ahb_clk;
> + struct clk *pix_clk[VSDC_MAX_OUTPUTS];
> + struct reset_control_bulk_data rsts[VSDC_RESET_COUNT];
> +
> + struct vs_drm_dev *drm_dev;
> + struct vs_chip_identity identity;
> + unsigned int outputs;
> +};
> +
> +#endif /* _VS_DC_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> new file mode 100644
> index 0000000000000..50509bbbff08f
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc_top_regs.h
> @@ -0,0 +1,27 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_DC_TOP_H_
> +#define _VS_DC_TOP_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_TOP_RST 0x0000
> +
> +#define VSDC_TOP_IRQ_ACK 0x0010
> +#define VSDC_TOP_IRQ_VSYNC(n) BIT(n)
> +
> +#define VSDC_TOP_IRQ_EN 0x0014
> +
> +#define VSDC_TOP_CHIP_MODEL 0x0020
> +
> +#define VSDC_TOP_CHIP_REV 0x0024
> +
> +#define VSDC_TOP_CHIP_CUSTOMER_ID 0x0030
> +
> +#endif /* _VS_DC_TOP_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_drm.c b/drivers/gpu/drm/verisilicon/vs_drm.c
> new file mode 100644
> index 0000000000000..f356d7832c449
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_drm.c
> @@ -0,0 +1,177 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/aperture.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/console.h>
> +
> +#include <drm/clients/drm_client_setup.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_fbdev_dma.h>
> +#include <drm/drm_gem_dma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_vblank.h>
> +
> +#include "vs_bridge.h"
> +#include "vs_crtc.h"
> +#include "vs_dc.h"
> +#include "vs_dc_top_regs.h"
> +#include "vs_drm.h"
> +
> +#define DRIVER_NAME "verisilicon"
> +#define DRIVER_DESC "Verisilicon DC-series display controller driver"
> +#define DRIVER_MAJOR 1
> +#define DRIVER_MINOR 0
> +
> +static int vs_gem_dumb_create(struct drm_file *file_priv,
> +      struct drm_device *drm,
> +      struct drm_mode_create_dumb *args)
> +{
> + /* The hardware wants 128B-aligned pitches for linear buffers. */
> + args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 128);
> +
> + return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
> +}
> +
> +DEFINE_DRM_GEM_FOPS(vs_drm_driver_fops);
> +
> +static const struct drm_driver vs_drm_driver = {
> + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> + .fops = &vs_drm_driver_fops,
> + .name = DRIVER_NAME,
> + .desc = DRIVER_DESC,
> + .major = DRIVER_MAJOR,
> + .minor = DRIVER_MINOR,
> +
> + /* GEM Operations */
> + DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(vs_gem_dumb_create),
> + DRM_FBDEV_DMA_DRIVER_OPS,
> +};
> +
> +static const struct drm_mode_config_funcs vs_mode_config_funcs = {
> + .fb_create = drm_gem_fb_create,
> + .atomic_check = drm_atomic_helper_check,
> + .atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static struct drm_mode_config_helper_funcs vs_mode_config_helper_funcs = {
> + .atomic_commit_tail = drm_atomic_helper_commit_tail,
> +};
> +
> +static void vs_mode_config_init(struct drm_device *drm)
> +{
> + drm_mode_config_reset(drm);
> +
> + drm->mode_config.min_width = 0;
> + drm->mode_config.min_height = 0;
> + drm->mode_config.max_width = 8192;
> + drm->mode_config.max_height = 8192;
> + drm->mode_config.funcs = &vs_mode_config_funcs;
> + drm->mode_config.helper_private = &vs_mode_config_helper_funcs;
> +}
> +
> +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct vs_drm_dev *vdrm;
> + struct drm_device *drm;
> + struct vs_crtc *crtc;
> + struct vs_bridge *bridge;
> + unsigned int i;
> + int ret;
> +
> + vdrm = devm_drm_dev_alloc(dev, &vs_drm_driver, struct vs_drm_dev, base);
> + if (IS_ERR(vdrm))
> + return PTR_ERR(vdrm);
> +
> + drm = &vdrm->base;
> + vdrm->dc = dc;
> + dc->drm_dev = vdrm;
> +
> + ret = drmm_mode_config_init(drm);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < dc->outputs; i++) {
> + crtc = vs_crtc_init(drm, dc, i);
> + if (IS_ERR(crtc))
> + return PTR_ERR(crtc);
> +
> + bridge = vs_bridge_init(drm, crtc);
> + if (IS_ERR(bridge))
> + return PTR_ERR(bridge);
> +
> + vdrm->crtcs[i] = crtc;
> + }
> +
> + ret = drm_vblank_init(drm, dc->outputs);
> + if (ret)
> + return ret;
> +
> + /* Remove early framebuffers (ie. simplefb) */
> + ret = aperture_remove_all_conflicting_devices(DRIVER_NAME);
> + if (ret)
> + return ret;
> +
> + vs_mode_config_init(drm);
> +
> + /* Enable connectors polling */
> + drm_kms_helper_poll_init(drm);
> +
> + ret = drm_dev_register(drm, 0);
> + if (ret)
> + goto err_fini_poll;
> +
> + drm_client_setup(drm, NULL);
> +
> + return 0;
> +
> +err_fini_poll:
> + drm_kms_helper_poll_fini(drm);
> + return ret;
> +}
> +
> +void vs_drm_finalize(struct vs_dc *dc)
> +{
> + struct vs_drm_dev *vdrm = dc->drm_dev;
> + struct drm_device *drm = &vdrm->base;
> +
> + drm_dev_unregister(drm);
> + drm_kms_helper_poll_fini(drm);
> + drm_atomic_helper_shutdown(drm);
> + dc->drm_dev = NULL;
> +}
> +
> +void vs_drm_shutdown_handler(struct vs_dc *dc)
> +{
> + struct vs_drm_dev *vdrm = dc->drm_dev;
> +
> + drm_atomic_helper_shutdown(&vdrm->base);
> +}
> +
> +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < dc->outputs; i++) {
> + if (irqs & VSDC_TOP_IRQ_VSYNC(i)) {
> + irqs &= ~VSDC_TOP_IRQ_VSYNC(i);
> + if (dc->drm_dev->crtcs[i])
> + drm_crtc_handle_vblank(&dc->drm_dev->crtcs[i]->base);
> + }
> + }
> +
> + if (irqs)
> + pr_warn("Unknown Verisilicon DC interrupt 0x%x fired!\n", irqs);
> +
> + return IRQ_HANDLED;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_drm.h b/drivers/gpu/drm/verisilicon/vs_drm.h
> new file mode 100644
> index 0000000000000..bbcd2e527deb6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_drm.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_DRM_H_
> +#define _VS_DRM_H_
> +
> +#include <linux/irqreturn.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +#include <drm/drm_device.h>
> +
> +struct vs_dc;
> +
> +struct vs_drm_dev {
> + struct drm_device base;
> +
> + struct vs_dc *dc;
> + struct vs_crtc *crtcs[VSDC_MAX_OUTPUTS];
> +};
> +
> +int vs_drm_initialize(struct vs_dc *dc, struct platform_device *pdev);
> +void vs_drm_finalize(struct vs_dc *dc);
> +void vs_drm_shutdown_handler(struct vs_dc *dc);
> +irqreturn_t vs_drm_handle_irq(struct vs_dc *dc, u32 irqs);
> +
> +#endif /* _VS_DRM_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.c b/drivers/gpu/drm/verisilicon/vs_hwdb.c
> new file mode 100644
> index 0000000000000..4a87e5d4701f3
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.c
> @@ -0,0 +1,150 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/errno.h>
> +
> +#include <drm/drm_fourcc.h>
> +
> +#include "vs_dc_top_regs.h"
> +#include "vs_hwdb.h"
> +
> +static const u32 vs_formats_array_no_yuv444[] = {
> + DRM_FORMAT_XRGB4444,
> + DRM_FORMAT_XBGR4444,
> + DRM_FORMAT_RGBX4444,
> + DRM_FORMAT_BGRX4444,
> + DRM_FORMAT_ARGB4444,
> + DRM_FORMAT_ABGR4444,
> + DRM_FORMAT_RGBA4444,
> + DRM_FORMAT_BGRA4444,
> + DRM_FORMAT_XRGB1555,
> + DRM_FORMAT_XBGR1555,
> + DRM_FORMAT_RGBX5551,
> + DRM_FORMAT_BGRX5551,
> + DRM_FORMAT_ARGB1555,
> + DRM_FORMAT_ABGR1555,
> + DRM_FORMAT_RGBA5551,
> + DRM_FORMAT_BGRA5551,
> + DRM_FORMAT_RGB565,
> + DRM_FORMAT_BGR565,
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_XBGR8888,
> + DRM_FORMAT_RGBX8888,
> + DRM_FORMAT_BGRX8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_ABGR8888,
> + DRM_FORMAT_RGBA8888,
> + DRM_FORMAT_BGRA8888,
> + DRM_FORMAT_ARGB2101010,
> + DRM_FORMAT_ABGR2101010,
> + DRM_FORMAT_RGBA1010102,
> + DRM_FORMAT_BGRA1010102,
> + /* TODO: non-RGB formats */
> +};
> +
> +static const u32 vs_formats_array_with_yuv444[] = {
> + DRM_FORMAT_XRGB4444,
> + DRM_FORMAT_XBGR4444,
> + DRM_FORMAT_RGBX4444,
> + DRM_FORMAT_BGRX4444,
> + DRM_FORMAT_ARGB4444,
> + DRM_FORMAT_ABGR4444,
> + DRM_FORMAT_RGBA4444,
> + DRM_FORMAT_BGRA4444,
> + DRM_FORMAT_XRGB1555,
> + DRM_FORMAT_XBGR1555,
> + DRM_FORMAT_RGBX5551,
> + DRM_FORMAT_BGRX5551,
> + DRM_FORMAT_ARGB1555,
> + DRM_FORMAT_ABGR1555,
> + DRM_FORMAT_RGBA5551,
> + DRM_FORMAT_BGRA5551,
> + DRM_FORMAT_RGB565,
> + DRM_FORMAT_BGR565,
> + DRM_FORMAT_XRGB8888,
> + DRM_FORMAT_XBGR8888,
> + DRM_FORMAT_RGBX8888,
> + DRM_FORMAT_BGRX8888,
> + DRM_FORMAT_ARGB8888,
> + DRM_FORMAT_ABGR8888,
> + DRM_FORMAT_RGBA8888,
> + DRM_FORMAT_BGRA8888,
> + DRM_FORMAT_ARGB2101010,
> + DRM_FORMAT_ABGR2101010,
> + DRM_FORMAT_RGBA1010102,
> + DRM_FORMAT_BGRA1010102,
> + /* TODO: non-RGB formats */
> +};
> +
> +static const struct vs_formats vs_formats_no_yuv444 = {
> + .array = vs_formats_array_no_yuv444,
> + .num = ARRAY_SIZE(vs_formats_array_no_yuv444)
> +};
> +
> +static const struct vs_formats vs_formats_with_yuv444 = {
> + .array = vs_formats_array_with_yuv444,
> + .num = ARRAY_SIZE(vs_formats_array_with_yuv444)
> +};
> +
> +static struct vs_chip_identity vs_chip_identities[] = {
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = ~0U,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5721,
> + .customer_id = 0x30B,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = 0x310,
> +
> + .display_count = 2,
> + .formats = &vs_formats_with_yuv444,
> + },
> + {
> + .model = 0x8200,
> + .revision = 0x5720,
> + .customer_id = 0x311,
> +
> + .display_count = 2,
> + .formats = &vs_formats_no_yuv444,
> + },
> +};
> +
> +int vs_fill_chip_identity(struct regmap *regs,
> +  struct vs_chip_identity *ident)
> +{
> + u32 model;
> + u32 revision;
> + u32 customer_id;
> + int i;
> +
> + regmap_read(regs, VSDC_TOP_CHIP_MODEL, &model);
> + regmap_read(regs, VSDC_TOP_CHIP_REV, &revision);
> + regmap_read(regs, VSDC_TOP_CHIP_CUSTOMER_ID, &customer_id);
> +
> + for (i = 0; i < ARRAY_SIZE(vs_chip_identities); i++) {
> + if (vs_chip_identities[i].model == model &&
> +    vs_chip_identities[i].revision == revision &&
> +    (vs_chip_identities[i].customer_id == customer_id ||
> + vs_chip_identities[i].customer_id == ~0U)) {
> + memcpy(ident, &vs_chip_identities[i], sizeof(*ident));
> + ident->customer_id = customer_id;
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_hwdb.h b/drivers/gpu/drm/verisilicon/vs_hwdb.h
> new file mode 100644
> index 0000000000000..92192e4fa0862
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_hwdb.h
> @@ -0,0 +1,29 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#ifndef _VS_HWDB_H_
> +#define _VS_HWDB_H_
> +
> +#include <linux/regmap.h>
> +#include <linux/types.h>
> +
> +struct vs_formats {
> + const u32 *array;
> + unsigned int num;
> +};
> +
> +struct vs_chip_identity {
> + u32 model;
> + u32 revision;
> + u32 customer_id;
> +
> + u32 display_count;
> + const struct vs_formats *formats;
> +};
> +
> +int vs_fill_chip_identity(struct regmap *regs,
> +  struct vs_chip_identity *ident);
> +
> +#endif /* _VS_HWDB_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_plane.c b/drivers/gpu/drm/verisilicon/vs_plane.c
> new file mode 100644
> index 0000000000000..f3c9963b6a4ea
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_plane.c
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/errno.h>
> +
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_print.h>
> +
> +#include "vs_plane.h"
> +
> +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format)
> +{
> + switch (drm_format) {
> + case DRM_FORMAT_XRGB4444:
> + case DRM_FORMAT_RGBX4444:
> + case DRM_FORMAT_XBGR4444:
> + case DRM_FORMAT_BGRX4444:
> + vs_format->color = VSDC_COLOR_FORMAT_X4R4G4B4;
> + break;
> + case DRM_FORMAT_ARGB4444:
> + case DRM_FORMAT_RGBA4444:
> + case DRM_FORMAT_ABGR4444:
> + case DRM_FORMAT_BGRA4444:
> + vs_format->color = VSDC_COLOR_FORMAT_A4R4G4B4;
> + break;
> + case DRM_FORMAT_XRGB1555:
> + case DRM_FORMAT_RGBX5551:
> + case DRM_FORMAT_XBGR1555:
> + case DRM_FORMAT_BGRX5551:
> + vs_format->color = VSDC_COLOR_FORMAT_X1R5G5B5;
> + break;
> + case DRM_FORMAT_ARGB1555:
> + case DRM_FORMAT_RGBA5551:
> + case DRM_FORMAT_ABGR1555:
> + case DRM_FORMAT_BGRA5551:
> + vs_format->color = VSDC_COLOR_FORMAT_A1R5G5B5;
> + break;
> + case DRM_FORMAT_RGB565:
> + case DRM_FORMAT_BGR565:
> + vs_format->color = VSDC_COLOR_FORMAT_R5G6B5;
> + break;
> + case DRM_FORMAT_XRGB8888:
> + case DRM_FORMAT_RGBX8888:
> + case DRM_FORMAT_XBGR8888:
> + case DRM_FORMAT_BGRX8888:
> + vs_format->color = VSDC_COLOR_FORMAT_X8R8G8B8;
> + break;
> + case DRM_FORMAT_ARGB8888:
> + case DRM_FORMAT_RGBA8888:
> + case DRM_FORMAT_ABGR8888:
> + case DRM_FORMAT_BGRA8888:
> + vs_format->color = VSDC_COLOR_FORMAT_A8R8G8B8;
> + break;
> + case DRM_FORMAT_ARGB2101010:
> + case DRM_FORMAT_RGBA1010102:
> + case DRM_FORMAT_ABGR2101010:
> + case DRM_FORMAT_BGRA1010102:
> + vs_format->color = VSDC_COLOR_FORMAT_A2R10G10B10;
> + break;
> + default:
> + DRM_WARN("Unexpected drm format!\n");
> + }
> +
> + switch (drm_format) {
> + case DRM_FORMAT_RGBX4444:
> + case DRM_FORMAT_RGBA4444:
> + case DRM_FORMAT_RGBX5551:
> + case DRM_FORMAT_RGBA5551:
> + case DRM_FORMAT_RGBX8888:
> + case DRM_FORMAT_RGBA8888:
> + case DRM_FORMAT_RGBA1010102:
> + vs_format->swizzle = VSDC_SWIZZLE_RGBA;
> + break;
> + case DRM_FORMAT_XBGR4444:
> + case DRM_FORMAT_ABGR4444:
> + case DRM_FORMAT_XBGR1555:
> + case DRM_FORMAT_ABGR1555:
> + case DRM_FORMAT_BGR565:
> + case DRM_FORMAT_XBGR8888:
> + case DRM_FORMAT_ABGR8888:
> + case DRM_FORMAT_ABGR2101010:
> + vs_format->swizzle = VSDC_SWIZZLE_ABGR;
> + break;
> + case DRM_FORMAT_BGRX4444:
> + case DRM_FORMAT_BGRA4444:
> + case DRM_FORMAT_BGRX5551:
> + case DRM_FORMAT_BGRA5551:
> + case DRM_FORMAT_BGRX8888:
> + case DRM_FORMAT_BGRA8888:
> + case DRM_FORMAT_BGRA1010102:
> + vs_format->swizzle = VSDC_SWIZZLE_BGRA;
> + break;
> + default:
> + /* N/A for YUV formats */
> + vs_format->swizzle = VSDC_SWIZZLE_ARGB;
> + }
> +
> + /* N/A for non-YUV formats */
> + vs_format->uv_swizzle = false;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_plane.h b/drivers/gpu/drm/verisilicon/vs_plane.h
> new file mode 100644
> index 0000000000000..3595267c89b53
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_plane.h
> @@ -0,0 +1,68 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_PLANE_H_
> +#define _VS_PLANE_H_
> +
> +#include <linux/types.h>
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_plane.h>
> +
> +#define VSDC_MAKE_PLANE_SIZE(w, h) (((w) & 0x7fff) | (((h) & 0x7fff) << 15))
> +#define VSDC_MAKE_PLANE_POS(x, y) (((x) & 0x7fff) | (((y) & 0x7fff) << 15))
> +
> +struct vs_dc;
> +
> +enum vs_color_format {
> + VSDC_COLOR_FORMAT_X4R4G4B4,
> + VSDC_COLOR_FORMAT_A4R4G4B4,
> + VSDC_COLOR_FORMAT_X1R5G5B5,
> + VSDC_COLOR_FORMAT_A1R5G5B5,
> + VSDC_COLOR_FORMAT_R5G6B5,
> + VSDC_COLOR_FORMAT_X8R8G8B8,
> + VSDC_COLOR_FORMAT_A8R8G8B8,
> + VSDC_COLOR_FORMAT_YUY2,
> + VSDC_COLOR_FORMAT_UYVY,
> + VSDC_COLOR_FORMAT_INDEX8,
> + VSDC_COLOR_FORMAT_MONOCHROME,
> + VSDC_COLOR_FORMAT_YV12 = 0xf,
> + VSDC_COLOR_FORMAT_A8,
> + VSDC_COLOR_FORMAT_NV12,
> + VSDC_COLOR_FORMAT_NV16,
> + VSDC_COLOR_FORMAT_RG16,
> + VSDC_COLOR_FORMAT_R8,
> + VSDC_COLOR_FORMAT_NV12_10BIT,
> + VSDC_COLOR_FORMAT_A2R10G10B10,
> + VSDC_COLOR_FORMAT_NV16_10BIT,
> + VSDC_COLOR_FORMAT_INDEX1,
> + VSDC_COLOR_FORMAT_INDEX2,
> + VSDC_COLOR_FORMAT_INDEX4,
> + VSDC_COLOR_FORMAT_P010,
> + VSDC_COLOR_FORMAT_YUV444,
> + VSDC_COLOR_FORMAT_YUV444_10BIT
> +};
> +
> +enum vs_swizzle {
> + VSDC_SWIZZLE_ARGB,
> + VSDC_SWIZZLE_RGBA,
> + VSDC_SWIZZLE_ABGR,
> + VSDC_SWIZZLE_BGRA,
> +};
> +
> +struct vs_format {
> + enum vs_color_format color;
> + enum vs_swizzle swizzle;
> + bool uv_swizzle;
> +};
> +
> +void drm_format_to_vs_format(u32 drm_format, struct vs_format *vs_format);
> +
> +struct drm_plane *vs_primary_plane_init(struct drm_device *dev, struct vs_dc *dc);
> +
> +#endif /* _VS_PLANE_H_ */
> diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane.c b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> new file mode 100644
> index 0000000000000..5b03783d4f284
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> @@ -0,0 +1,157 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + */
> +
> +#include <linux/regmap.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_fb_dma_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_gem_dma_helper.h>
> +#include <drm/drm_modeset_helper_vtables.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_print.h>
> +
> +#include "vs_crtc.h"
> +#include "vs_plane.h"
> +#include "vs_dc.h"
> +#include "vs_primary_plane_regs.h"
> +
> +static int vs_primary_plane_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_state *state)
> +{
> + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
> + plane);
> + struct drm_crtc *crtc = new_plane_state->crtc;
> + struct drm_crtc_state *crtc_state;
> +
> + if (!crtc)
> + return 0;
> +
> + crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> + if (WARN_ON(!crtc_state))
> + return -EINVAL;
> +
> + return drm_atomic_helper_check_plane_state(new_plane_state,
> +   crtc_state,
> +   DRM_PLANE_NO_SCALING,
> +   DRM_PLANE_NO_SCALING,
> +   false, true);
> +}
> +
> +
> +static void vs_primary_plane_atomic_update(struct drm_plane *plane,
> +   struct drm_atomic_state *atomic_state)
> +{
> + struct drm_plane_state *state = drm_atomic_get_new_plane_state(atomic_state,
> +       plane);
> + struct drm_framebuffer *fb = state->fb;
> + struct drm_crtc *crtc = state->crtc;
> + struct drm_gem_dma_object *gem;
> + struct vs_dc *dc;
> + struct vs_crtc *vcrtc;
> + struct vs_format fmt;
> + unsigned int output, bpp;
> + dma_addr_t dma_addr;
> +
> + if (!crtc)
> + return;
> +
> + vcrtc = drm_crtc_to_vs_crtc(crtc);
> + output = vcrtc->id;
> + dc = vcrtc->dc;
> +
> + DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
> +
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> +   VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK,
> +   VSDC_FB_CONFIG_EX_DISPLAY_ID(output));
> +
> + if (!state->visible || !fb) {
> + regmap_write(dc->regs, VSDC_FB_CONFIG(output), 0);
> + regmap_write(dc->regs, VSDC_FB_CONFIG_EX(output), 0);
> + goto commit;
> + } else {
> + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> + VSDC_FB_CONFIG_EX_FB_EN);
> + }
> +
> + drm_format_to_vs_format(state->fb->format->format, &fmt);
> +
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_FMT_MASK,
> +   VSDC_FB_CONFIG_FMT(fmt.color));
> + regmap_update_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_SWIZZLE_MASK,
> +   VSDC_FB_CONFIG_SWIZZLE(fmt.swizzle));
> + regmap_assign_bits(dc->regs, VSDC_FB_CONFIG(output),
> +   VSDC_FB_CONFIG_UV_SWIZZLE_EN, fmt.uv_swizzle);
> +
> + /* Get the physical address of the buffer in memory */
> + gem = drm_fb_dma_get_gem_obj(fb, 0);
> +
> + /* Compute the start of the displayed memory */
> + bpp = fb->format->cpp[0];
> + dma_addr = gem->dma_addr + fb->offsets[0];
> +
> + /* Fixup framebuffer address for src coordinates */
> + dma_addr += (state->src.x1 >> 16) * bpp;
> + dma_addr += (state->src.y1 >> 16) * fb->pitches[0];
> +
> + regmap_write(dc->regs, VSDC_FB_ADDRESS(output),
> +     lower_32_bits(dma_addr));
> + regmap_write(dc->regs, VSDC_FB_STRIDE(output),
> +     fb->pitches[0]);
> +
> + regmap_write(dc->regs, VSDC_FB_TOP_LEFT(output),
> +     VSDC_MAKE_PLANE_POS(state->crtc_x, state->crtc_y));
> + regmap_write(dc->regs, VSDC_FB_BOTTOM_RIGHT(output),
> +     VSDC_MAKE_PLANE_POS(state->crtc_x + state->crtc_w,
> + state->crtc_y + state->crtc_h));
> + regmap_write(dc->regs, VSDC_FB_SIZE(output),
> +     VSDC_MAKE_PLANE_SIZE(state->crtc_w, state->crtc_h));
> +
> + regmap_write(dc->regs, VSDC_FB_BLEND_CONFIG(output),
> +     VSDC_FB_BLEND_CONFIG_BLEND_DISABLE);
> +commit:
> + regmap_set_bits(dc->regs, VSDC_FB_CONFIG_EX(output),
> + VSDC_FB_CONFIG_EX_COMMIT);
> +}
> +
> +static const struct drm_plane_helper_funcs vs_primary_plane_helper_funcs = {
> + .atomic_check = vs_primary_plane_atomic_check,
> + .atomic_update = vs_primary_plane_atomic_update,
> +};
> +
> +static const struct drm_plane_funcs vs_primary_plane_funcs = {
> + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> + .disable_plane = drm_atomic_helper_disable_plane,
> + .reset = drm_atomic_helper_plane_reset,
> + .update_plane = drm_atomic_helper_update_plane,
> +};
> +
> +struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc)
> +{
> + struct drm_plane *plane;
> +
> + plane = drmm_universal_plane_alloc(drm_dev, struct drm_plane, dev, 0,
> +   &vs_primary_plane_funcs,
> +   dc->identity.formats->array,
> +   dc->identity.formats->num,
> +   NULL,
> +   DRM_PLANE_TYPE_PRIMARY,
> +   NULL);
> +
> + if (IS_ERR(plane))
> + return plane;
> +
> + drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs);
> +
> + return plane;
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> new file mode 100644
> index 0000000000000..cbb125c46b390
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane_regs.h
> @@ -0,0 +1,53 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on vs_dc_hw.h, which is:
> + *   Copyright (C) 2023 VeriSilicon Holdings Co., Ltd.
> + */
> +
> +#ifndef _VS_PRIMARY_PLANE_REGS_H_
> +#define _VS_PRIMARY_PLANE_REGS_H_
> +
> +#include <linux/bits.h>
> +
> +#define VSDC_FB_ADDRESS(n) (0x1400 + 0x4 * (n))
> +
> +#define VSDC_FB_STRIDE(n) (0x1408 + 0x4 * (n))
> +
> +#define VSDC_FB_CONFIG(n) (0x1518 + 0x4 * (n))
> +#define VSDC_FB_CONFIG_CLEAR_EN BIT(8)
> +#define VSDC_FB_CONFIG_ROT_MASK GENMASK(13, 11)
> +#define VSDC_FB_CONFIG_ROT(v) ((v) << 11)
> +#define VSDC_FB_CONFIG_YUV_SPACE_MASK GENMASK(16, 14)
> +#define VSDC_FB_CONFIG_YUV_SPACE(v) ((v) << 14)
> +#define VSDC_FB_CONFIG_TILE_MODE_MASK GENMASK(21, 17)
> +#define VSDC_FB_CONFIG_TILE_MODE(v) ((v) << 14)
> +#define VSDC_FB_CONFIG_SCALE_EN BIT(22)
> +#define VSDC_FB_CONFIG_SWIZZLE_MASK GENMASK(24, 23)
> +#define VSDC_FB_CONFIG_SWIZZLE(v) ((v) << 23)
> +#define VSDC_FB_CONFIG_UV_SWIZZLE_EN BIT(25)
> +#define VSDC_FB_CONFIG_FMT_MASK GENMASK(31, 26)
> +#define VSDC_FB_CONFIG_FMT(v) ((v) << 26)
> +
> +#define VSDC_FB_SIZE(n) (0x1810 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_SIZE(w, h) */
> +
> +#define VSDC_FB_CONFIG_EX(n) (0x1CC0 + 0x4 * (n))
> +#define VSDC_FB_CONFIG_EX_COMMIT BIT(12)
> +#define VSDC_FB_CONFIG_EX_FB_EN BIT(13)
> +#define VSDC_FB_CONFIG_EX_ZPOS_MASK GENMASK(18, 16)
> +#define VSDC_FB_CONFIG_EX_ZPOS(v) ((v) << 16)
> +#define VSDC_FB_CONFIG_EX_DISPLAY_ID_MASK GENMASK(19, 19)
> +#define VSDC_FB_CONFIG_EX_DISPLAY_ID(v) ((v) << 19)
> +
> +#define VSDC_FB_TOP_LEFT(n) (0x24D8 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
> +
> +#define VSDC_FB_BOTTOM_RIGHT(n) (0x24E0 + 0x4 * (n))
> +/* Fill with value generated with VSDC_MAKE_PLANE_POS(x, y) */
> +
> +#define VSDC_FB_BLEND_CONFIG(n) (0x2510 + 0x4 * (n))
> +#define VSDC_FB_BLEND_CONFIG_BLEND_DISABLE BIT(1)
> +
> +#endif /* _VS_PRIMARY_PLANE_REGS_H_ */
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-24 16:12   ` [PATCH v4 5/9] drm/bridge: add a driver for T-Head " Icenowy Zheng
@ 2025-12-25  9:46     ` Han Gao (Revy)
  2025-12-25 11:07     ` Andy Yan
  1 sibling, 0 replies; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:46 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:12, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller (paired
> with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from the
> display controller.
> 
> Add a driver for this controller utilizing the common DesignWare HDMI
> code in the kernel.
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> No changes in v3, v4.
> 
> Changes in v2:
> - Created a new function to set PHY parameters and refactored the
>  control flow of the configure_phy callback.
> 
> MAINTAINERS                             |   1 +
> drivers/gpu/drm/bridge/Kconfig          |  10 ++
> drivers/gpu/drm/bridge/Makefile         |   1 +
> drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173 ++++++++++++++++++++++++
> 4 files changed, 185 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 5b11839cba9de..fc04fc007a054 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -22556,6 +22556,7 @@ F: Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml
> F: arch/riscv/boot/dts/thead/
> F: drivers/clk/thead/clk-th1520-ap.c
> F: drivers/firmware/thead,th1520-aon.c
> +F: drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> F: drivers/mailbox/mailbox-th1520.c
> F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
> F: drivers/pinctrl/pinctrl-th1520.c
> diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> index a250afd8d6622..8e19f5fb9ad7c 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -335,6 +335,16 @@ config DRM_THINE_THC63LVD1024
> help
>  Thine THC63LVD1024 LVDS/parallel converter driver.
> 
> +config DRM_THEAD_TH1520_DW_HDMI
> + tristate "T-Head TH1520 DesignWare HDMI bridge"
> + depends on OF
> + depends on COMMON_CLK
> + depends on ARCH_THEAD || COMPILE_TEST
> + select DRM_DW_HDMI
> + help
> +  Choose this to enable support for the internal HDMI bridge found
> +  on the T-Head TH1520 SoC.
> +
> config DRM_TOSHIBA_TC358762
> tristate "TC358762 DSI/DPI bridge"
> depends on OF
> diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> index c7dc03182e592..085b5db45d6fd 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -28,6 +28,7 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o
> obj-$(CONFIG_DRM_SII9234) += sii9234.o
> obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
> obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o
> +obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o
> obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
> obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o
> obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
> diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> new file mode 100644
> index 0000000000000..389eead5f1c45
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> @@ -0,0 +1,173 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
> + *
> + * Based on rcar_dw_hdmi.c, which is:
> + *   Copyright (C) 2016 Renesas Electronics Corporation
> + * Based on imx8mp-hdmi-tx.c, which is:
> + *   Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +
> +#include <drm/bridge/dw_hdmi.h>
> +#include <drm/drm_modes.h>
> +
> +#define TH1520_HDMI_PHY_OPMODE_PLLCFG 0x06 /* Mode of operation and PLL dividers */
> +#define TH1520_HDMI_PHY_CKSYMTXCTRL 0x09 /* Clock Symbol and Transmitter Control Register */
> +#define TH1520_HDMI_PHY_VLEVCTRL 0x0e /* Voltage Level Control Register */
> +#define TH1520_HDMI_PHY_PLLCURRGMPCTRL 0x10 /* PLL current and Gmp (conductance) */
> +#define TH1520_HDMI_PHY_PLLDIVCTRL 0x11 /* PLL dividers */
> +#define TH1520_HDMI_PHY_TXTERM 0x19 /* Transmission Termination Register */
> +
> +struct th1520_hdmi_phy_params {
> + unsigned long mpixelclock;
> + u16 opmode_pllcfg;
> + u16 pllcurrgmpctrl;
> + u16 plldivctrl;
> + u16 cksymtxctrl;
> + u16 vlevctrl;
> + u16 txterm;
> +};
> +
> +static const struct th1520_hdmi_phy_params th1520_hdmi_phy_params[] = {
> + { 35500000,  0x0003, 0x0283, 0x0628, 0x8088, 0x01a0, 0x0007 },
> + { 44900000,  0x0003, 0x0285, 0x0228, 0x8088, 0x01a0, 0x0007 },
> + { 71000000,  0x0002, 0x1183, 0x0614, 0x8088, 0x01a0, 0x0007 },
> + { 90000000,  0x0002, 0x1142, 0x0214, 0x8088, 0x01a0, 0x0007 },
> + { 121750000, 0x0001, 0x20c0, 0x060a, 0x8088, 0x01a0, 0x0007 },
> + { 165000000, 0x0001, 0x2080, 0x020a, 0x8088, 0x01a0, 0x0007 },
> + { 198000000, 0x0000, 0x3040, 0x0605, 0x83c8, 0x0120, 0x0004 },
> + { 297000000, 0x0000, 0x3041, 0x0205, 0x81dc, 0x0200, 0x0005 },
> + { 371250000, 0x0640, 0x3041, 0x0205, 0x80f6, 0x0140, 0x0000 },
> + { 495000000, 0x0640, 0x3080, 0x0005, 0x80f6, 0x0140, 0x0000 },
> + { 594000000, 0x0640, 0x3080, 0x0005, 0x80fa, 0x01e0, 0x0004 },
> +};
> +
> +struct th1520_hdmi {
> + struct dw_hdmi_plat_data plat_data;
> + struct dw_hdmi *dw_hdmi;
> + struct clk *pixclk;
> + struct reset_control *mainrst, *prst;
> +};
> +
> +static enum drm_mode_status
> +th1520_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
> +       const struct drm_display_info *info,
> +       const struct drm_display_mode *mode)
> +{
> + /*
> + * The maximum supported clock frequency is 594 MHz, as shown in the PHY
> + * parameters table.
> + */
> + if (mode->clock > 594000)
> + return MODE_CLOCK_HIGH;
> +
> + return MODE_OK;
> +}
> +
> +static void th1520_hdmi_phy_set_params(struct dw_hdmi *hdmi,
> + const struct th1520_hdmi_phy_params *params)
> +{
> + dw_hdmi_phy_i2c_write(hdmi, params->opmode_pllcfg,
> +      TH1520_HDMI_PHY_OPMODE_PLLCFG);
> + dw_hdmi_phy_i2c_write(hdmi, params->pllcurrgmpctrl,
> +      TH1520_HDMI_PHY_PLLCURRGMPCTRL);
> + dw_hdmi_phy_i2c_write(hdmi, params->plldivctrl,
> +      TH1520_HDMI_PHY_PLLDIVCTRL);
> + dw_hdmi_phy_i2c_write(hdmi, params->vlevctrl,
> +      TH1520_HDMI_PHY_VLEVCTRL);
> + dw_hdmi_phy_i2c_write(hdmi, params->cksymtxctrl,
> +      TH1520_HDMI_PHY_CKSYMTXCTRL);
> + dw_hdmi_phy_i2c_write(hdmi, params->txterm,
> +      TH1520_HDMI_PHY_TXTERM);
> +}
> +
> +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
> +     unsigned long mpixelclock)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < ARRAY_SIZE(th1520_hdmi_phy_params); i++) {
> + if (mpixelclock <= th1520_hdmi_phy_params[i].mpixelclock) {
> + th1520_hdmi_phy_set_params(hdmi,
> +   &th1520_hdmi_phy_params[i]);
> + return 0;
> + }
> + }
> +
> + return -EINVAL;
> +}
> +
> +static int th1520_dw_hdmi_probe(struct platform_device *pdev)
> +{
> + struct th1520_hdmi *hdmi;
> + struct dw_hdmi_plat_data *plat_data;
> + struct device *dev = &pdev->dev;
> +
> + hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
> + if (!hdmi)
> + return -ENOMEM;
> +
> + plat_data = &hdmi->plat_data;
> +
> + hdmi->pixclk = devm_clk_get_enabled(dev, "pix");
> + if (IS_ERR(hdmi->pixclk))
> + return dev_err_probe(dev, PTR_ERR(hdmi->pixclk),
> +     "Unable to get pixel clock\n");
> +
> + hdmi->mainrst = devm_reset_control_get_exclusive_deasserted(dev, "main");
> + if (IS_ERR(hdmi->mainrst))
> + return dev_err_probe(dev, PTR_ERR(hdmi->mainrst),
> +     "Unable to get main reset\n");
> +
> + hdmi->prst = devm_reset_control_get_exclusive_deasserted(dev, "apb");
> + if (IS_ERR(hdmi->prst))
> + return dev_err_probe(dev, PTR_ERR(hdmi->prst),
> +     "Unable to get apb reset\n");
> +
> + plat_data->output_port = 1;
> + plat_data->mode_valid = th1520_hdmi_mode_valid;
> + plat_data->configure_phy = th1520_hdmi_phy_configure;
> + plat_data->priv_data = hdmi;
> +
> + hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data);
> + if (IS_ERR(hdmi))
> + return PTR_ERR(hdmi);
> +
> + platform_set_drvdata(pdev, hdmi);
> +
> + return 0;
> +}
> +
> +static void th1520_dw_hdmi_remove(struct platform_device *pdev)
> +{
> + struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
> +
> + dw_hdmi_remove(hdmi);
> +}
> +
> +static const struct of_device_id th1520_dw_hdmi_of_table[] = {
> + { .compatible = "thead,th1520-dw-hdmi" },
> + { /* Sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, th1520_dw_hdmi_of_table);
> +
> +static struct platform_driver th1520_dw_hdmi_platform_driver = {
> + .probe = th1520_dw_hdmi_probe,
> + .remove = th1520_dw_hdmi_remove,
> + .driver = {
> + .name = "th1520-dw-hdmi",
> + .of_match_table = th1520_dw_hdmi_of_table,
> + },
> +};
> +
> +module_platform_driver(th1520_dw_hdmi_platform_driver);
> +
> +MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
> +MODULE_DESCRIPTION("T-Head TH1520 HDMI Encoder Driver");
> +MODULE_LICENSE("GPL");
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes
  2025-12-24 16:12   ` [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
@ 2025-12-25  9:46     ` Han Gao (Revy)
  0 siblings, 0 replies; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:46 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:12, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> T-Head TH1520 SoC contains a Verisilicon DC8200 display controller
> (called DPU in manual) and a Synopsys DesignWare HDMI TX controller.
> 
> Add device tree nodes to them.
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> No changes in v4.
> 
> Changes in v3:
> - Adapting to the changed binding.
> 
> No changes in v2.
> 
> arch/riscv/boot/dts/thead/th1520.dtsi | 70 +++++++++++++++++++++++++++
> 1 file changed, 70 insertions(+)
> 
> diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi
> index bd5d33840884e..5c0a7072f253a 100644
> --- a/arch/riscv/boot/dts/thead/th1520.dtsi
> +++ b/arch/riscv/boot/dts/thead/th1520.dtsi
> @@ -585,6 +585,76 @@ clk_vo: clock-controller@ffef528050 {
> #clock-cells = <1>;
> };
> 
> + hdmi: hdmi@ffef540000 {
> + compatible = "thead,th1520-dw-hdmi";
> + reg = <0xff 0xef540000 0x0 0x40000>;
> + reg-io-width = <4>;
> + interrupts = <111 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk_vo CLK_HDMI_PCLK>,
> + <&clk_vo CLK_HDMI_SFR>,
> + <&clk_vo CLK_HDMI_CEC>,
> + <&clk_vo CLK_HDMI_PIXCLK>;
> + clock-names = "iahb", "isfr", "cec", "pix";
> + resets = <&rst TH1520_RESET_ID_HDMI>,
> + <&rst TH1520_RESET_ID_HDMI_APB>;
> + reset-names = "main", "apb";
> + status = "disabled";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> +
> + hdmi_in: endpoint {
> + remote-endpoint = <&dpu_out_dp1>;
> + };
> + };
> +
> + hdmi_out_port: port@1 {
> + reg = <1>;
> + };
> + };
> + };
> +
> + dpu: display@ffef600000 {
> + compatible = "thead,th1520-dc8200", "verisilicon,dc";
> + reg = <0xff 0xef600000 0x0 0x100000>;
> + interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
> + clocks = <&clk_vo CLK_DPU_CCLK>,
> + <&clk_vo CLK_DPU_ACLK>,
> + <&clk_vo CLK_DPU_HCLK>,
> + <&clk_vo CLK_DPU_PIXELCLK0>,
> + <&clk_vo CLK_DPU_PIXELCLK1>;
> + clock-names = "core", "axi", "ahb", "pix0", "pix1";
> + resets = <&rst TH1520_RESET_ID_DPU_CORE>,
> + <&rst TH1520_RESET_ID_DPU_AXI>,
> + <&rst TH1520_RESET_ID_DPU_AHB>;
> + reset-names = "core", "axi", "ahb";
> + status = "disabled";
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + dpu_port0: port@0 {
> + reg = <0>;
> + };
> +
> + dpu_port1: port@1 {
> + reg = <1>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + dpu_out_dp1: endpoint@1 {
> + reg = <1>;
> + remote-endpoint = <&hdmi_in>;
> + };
> + };
> + };
> + };
> +
> dmac0: dma-controller@ffefc00000 {
> compatible = "snps,axi-dma-1.01a";
> reg = <0xff 0xefc00000 0x0 0x1000>;
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI
  2025-12-24 16:12   ` [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
@ 2025-12-25  9:47     ` Han Gao (Revy)
  0 siblings, 0 replies; 33+ messages in thread
From: Han Gao (Revy) @ 2025-12-25  9:47 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Han Gao (Revy), Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng



> On Dec 25, 2025, at 00:12, Icenowy Zheng <zhengxingda@iscas.ac.cn> wrote:
> 
> Lichee Pi 4A board features a HDMI Type-A connector connected to the
> HDMI TX controller of TH1520 SoC.
> 
> Add a device tree node describing the connector, connect it to the HDMI
> controller, and enable everything on this display pipeline.
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Rebased on top of v6.19-rc1.
> 
> No changes in v2, v3.
> 
> .../boot/dts/thead/th1520-lichee-pi-4a.dts    | 25 +++++++++++++++++++
> 1 file changed, 25 insertions(+)
> 
> diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
> index c58c2085ca92a..7cb7d28683bce 100644
> --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
> +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
> @@ -29,6 +29,17 @@ chosen {
> stdout-path = "serial0:115200n8";
> };
> 
> + hdmi-connector {
> + compatible = "hdmi-connector";
> + type = "a";
> +
> + port {
> + hdmi_con_in: endpoint {
> + remote-endpoint = <&hdmi_out_con>;
> + };
> + };
> + };
> +
> thermal-zones {
> cpu-thermal {
> polling-delay = <1000>;
> @@ -121,6 +132,20 @@ rx-pins {
> };
> };
> 
> +&dpu {
> + status = "okay";
> +};
> +
> +&hdmi {
> + status = "okay";
> +};
> +
> +&hdmi_out_port {
> + hdmi_out_con: endpoint {
> + remote-endpoint = <&hdmi_con_in>;
> + };
> +};
> +
> &uart0 {
> pinctrl-names = "default";
> pinctrl-0 = <&uart0_pins>;
> -- 
> 2.52.0
> 
Tested-by: Han Gao <gaohan@iscas.ac.cn>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re:[PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-24 16:12   ` [PATCH v4 5/9] drm/bridge: add a driver for T-Head " Icenowy Zheng
  2025-12-25  9:46     ` Han Gao (Revy)
@ 2025-12-25 11:07     ` Andy Yan
  2025-12-26 13:27       ` [PATCH " Icenowy Zheng
  1 sibling, 1 reply; 33+ messages in thread
From: Andy Yan @ 2025-12-25 11:07 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski, devicetree,
	Yao Zi, linux-kernel, dri-devel, Han Gao, linux-riscv



Hello Icenowy:

At 2025-12-25 00:12:01, "Icenowy Zheng" <zhengxingda@iscas.ac.cn> wrote:
>From: Icenowy Zheng <uwu@icenowy.me>
>
>T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller (paired
>with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from the
>display controller.
>
>Add a driver for this controller utilizing the common DesignWare HDMI
>code in the kernel.
>
>Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
>Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
>---
>No changes in v3, v4.
>
>Changes in v2:
>- Created a new function to set PHY parameters and refactored the
>  control flow of the configure_phy callback.
>
> MAINTAINERS                             |   1 +
> drivers/gpu/drm/bridge/Kconfig          |  10 ++
> drivers/gpu/drm/bridge/Makefile         |   1 +
> drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173 ++++++++++++++++++++++++
> 4 files changed, 185 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c


As this is a Synopsys DesignWare based IP, maybe it's better to put it under   "drivers/gpu/drm/bridge/synopsys/"
Or just create a dir for thead, I think there will come other display related drivers for thead in the future.

>
>diff --git a/MAINTAINERS b/MAINTAINERS
>index 5b11839cba9de..fc04fc007a054 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -22556,6 +22556,7 @@ F:	Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml
> F:	arch/riscv/boot/dts/thead/
> F:	drivers/clk/thead/clk-th1520-ap.c
> F:	drivers/firmware/thead,th1520-aon.c
>+F:	drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> F:	drivers/mailbox/mailbox-th1520.c
> F:	drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c
> F:	drivers/pinctrl/pinctrl-th1520.c
>diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
>index a250afd8d6622..8e19f5fb9ad7c 100644
>--- a/drivers/gpu/drm/bridge/Kconfig
>+++ b/drivers/gpu/drm/bridge/Kconfig
>@@ -335,6 +335,16 @@ config DRM_THINE_THC63LVD1024
> 	help
> 	  Thine THC63LVD1024 LVDS/parallel converter driver.
> 
>+config DRM_THEAD_TH1520_DW_HDMI
>+	tristate "T-Head TH1520 DesignWare HDMI bridge"
>+	depends on OF
>+	depends on COMMON_CLK
>+	depends on ARCH_THEAD || COMPILE_TEST
>+	select DRM_DW_HDMI
>+	help
>+	  Choose this to enable support for the internal HDMI bridge found
>+	  on the T-Head TH1520 SoC.
>+
> config DRM_TOSHIBA_TC358762
> 	tristate "TC358762 DSI/DPI bridge"
> 	depends on OF
>diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
>index c7dc03182e592..085b5db45d6fd 100644
>--- a/drivers/gpu/drm/bridge/Makefile
>+++ b/drivers/gpu/drm/bridge/Makefile
>@@ -28,6 +28,7 @@ obj-$(CONFIG_DRM_SII902X) += sii902x.o
> obj-$(CONFIG_DRM_SII9234) += sii9234.o
> obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.o
> obj-$(CONFIG_DRM_SOLOMON_SSD2825) += ssd2825.o
>+obj-$(CONFIG_DRM_THEAD_TH1520_DW_HDMI) += th1520-dw-hdmi.o
> obj-$(CONFIG_DRM_THINE_THC63LVD1024) += thc63lvd1024.o
> obj-$(CONFIG_DRM_TOSHIBA_TC358762) += tc358762.o
> obj-$(CONFIG_DRM_TOSHIBA_TC358764) += tc358764.o
>diff --git a/drivers/gpu/drm/bridge/th1520-dw-hdmi.c b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
>new file mode 100644
>index 0000000000000..389eead5f1c45
>--- /dev/null
>+++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
>@@ -0,0 +1,173 @@
>+// SPDX-License-Identifier: GPL-2.0+
>+/*
>+ * Copyright (C) 2025 Icenowy Zheng <uwu@icenowy.me>
>+ *
>+ * Based on rcar_dw_hdmi.c, which is:
>+ *   Copyright (C) 2016 Renesas Electronics Corporation
>+ * Based on imx8mp-hdmi-tx.c, which is:
>+ *   Copyright (C) 2022 Pengutronix, Lucas Stach <kernel@pengutronix.de>
>+ */
>+
>+#include <linux/clk.h>
>+#include <linux/mod_devicetable.h>
>+#include <linux/module.h>
>+#include <linux/platform_device.h>
>+#include <linux/reset.h>
>+
>+#include <drm/bridge/dw_hdmi.h>
>+#include <drm/drm_modes.h>
>+
>+#define TH1520_HDMI_PHY_OPMODE_PLLCFG	0x06	/* Mode of operation and PLL dividers */
>+#define TH1520_HDMI_PHY_CKSYMTXCTRL	0x09	/* Clock Symbol and Transmitter Control Register */
>+#define TH1520_HDMI_PHY_VLEVCTRL	0x0e	/* Voltage Level Control Register */
>+#define TH1520_HDMI_PHY_PLLCURRGMPCTRL	0x10	/* PLL current and Gmp (conductance) */
>+#define TH1520_HDMI_PHY_PLLDIVCTRL	0x11	/* PLL dividers */
>+#define TH1520_HDMI_PHY_TXTERM		0x19	/* Transmission Termination Register */
>+
>+struct th1520_hdmi_phy_params {
>+	unsigned long mpixelclock;
>+	u16 opmode_pllcfg;
>+	u16 pllcurrgmpctrl;
>+	u16 plldivctrl;
>+	u16 cksymtxctrl;
>+	u16 vlevctrl;
>+	u16 txterm;
>+};
>+
>+static const struct th1520_hdmi_phy_params th1520_hdmi_phy_params[] = {
>+	{ 35500000,  0x0003, 0x0283, 0x0628, 0x8088, 0x01a0, 0x0007 },
>+	{ 44900000,  0x0003, 0x0285, 0x0228, 0x8088, 0x01a0, 0x0007 },
>+	{ 71000000,  0x0002, 0x1183, 0x0614, 0x8088, 0x01a0, 0x0007 },
>+	{ 90000000,  0x0002, 0x1142, 0x0214, 0x8088, 0x01a0, 0x0007 },
>+	{ 121750000, 0x0001, 0x20c0, 0x060a, 0x8088, 0x01a0, 0x0007 },
>+	{ 165000000, 0x0001, 0x2080, 0x020a, 0x8088, 0x01a0, 0x0007 },
>+	{ 198000000, 0x0000, 0x3040, 0x0605, 0x83c8, 0x0120, 0x0004 },
>+	{ 297000000, 0x0000, 0x3041, 0x0205, 0x81dc, 0x0200, 0x0005 },
>+	{ 371250000, 0x0640, 0x3041, 0x0205, 0x80f6, 0x0140, 0x0000 },
>+	{ 495000000, 0x0640, 0x3080, 0x0005, 0x80f6, 0x0140, 0x0000 },
>+	{ 594000000, 0x0640, 0x3080, 0x0005, 0x80fa, 0x01e0, 0x0004 },
>+};
>+
>+struct th1520_hdmi {
>+	struct dw_hdmi_plat_data plat_data;
>+	struct dw_hdmi *dw_hdmi;
>+	struct clk *pixclk;
>+	struct reset_control *mainrst, *prst;
>+};
>+
>+static enum drm_mode_status
>+th1520_hdmi_mode_valid(struct dw_hdmi *hdmi, void *data,
>+		       const struct drm_display_info *info,
>+		       const struct drm_display_mode *mode)
>+{
>+	/*
>+	 * The maximum supported clock frequency is 594 MHz, as shown in the PHY
>+	 * parameters table.
>+	 */
>+	if (mode->clock > 594000)
>+		return MODE_CLOCK_HIGH;
>+
>+	return MODE_OK;
>+}
>+
>+static void th1520_hdmi_phy_set_params(struct dw_hdmi *hdmi,
>+				const struct th1520_hdmi_phy_params *params)
>+{
>+	dw_hdmi_phy_i2c_write(hdmi, params->opmode_pllcfg,
>+			      TH1520_HDMI_PHY_OPMODE_PLLCFG);
>+	dw_hdmi_phy_i2c_write(hdmi, params->pllcurrgmpctrl,
>+			      TH1520_HDMI_PHY_PLLCURRGMPCTRL);
>+	dw_hdmi_phy_i2c_write(hdmi, params->plldivctrl,
>+			      TH1520_HDMI_PHY_PLLDIVCTRL);
>+	dw_hdmi_phy_i2c_write(hdmi, params->vlevctrl,
>+			      TH1520_HDMI_PHY_VLEVCTRL);
>+	dw_hdmi_phy_i2c_write(hdmi, params->cksymtxctrl,
>+			      TH1520_HDMI_PHY_CKSYMTXCTRL);
>+	dw_hdmi_phy_i2c_write(hdmi, params->txterm,
>+			      TH1520_HDMI_PHY_TXTERM);
>+}
>+
>+static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
>+				     unsigned long mpixelclock)
>+{
>+	unsigned int i;
>+
>+	for (i = 0; i < ARRAY_SIZE(th1520_hdmi_phy_params); i++) {
>+		if (mpixelclock <= th1520_hdmi_phy_params[i].mpixelclock) {
>+			th1520_hdmi_phy_set_params(hdmi,
>+						   &th1520_hdmi_phy_params[i]);
>+			return 0;
>+		}
>+	}
>+
>+	return -EINVAL;
>+}
>+
>+static int th1520_dw_hdmi_probe(struct platform_device *pdev)
>+{
>+	struct th1520_hdmi *hdmi;
>+	struct dw_hdmi_plat_data *plat_data;
>+	struct device *dev = &pdev->dev;
>+
>+	hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
>+	if (!hdmi)
>+		return -ENOMEM;
>+
>+	plat_data = &hdmi->plat_data;
>+
>+	hdmi->pixclk = devm_clk_get_enabled(dev, "pix");
>+	if (IS_ERR(hdmi->pixclk))
>+		return dev_err_probe(dev, PTR_ERR(hdmi->pixclk),
>+				     "Unable to get pixel clock\n");
>+
>+	hdmi->mainrst = devm_reset_control_get_exclusive_deasserted(dev, "main");
>+	if (IS_ERR(hdmi->mainrst))
>+		return dev_err_probe(dev, PTR_ERR(hdmi->mainrst),
>+				     "Unable to get main reset\n");
>+
>+	hdmi->prst = devm_reset_control_get_exclusive_deasserted(dev, "apb");
>+	if (IS_ERR(hdmi->prst))
>+		return dev_err_probe(dev, PTR_ERR(hdmi->prst),
>+				     "Unable to get apb reset\n");
>+
>+	plat_data->output_port = 1;
>+	plat_data->mode_valid = th1520_hdmi_mode_valid;
>+	plat_data->configure_phy = th1520_hdmi_phy_configure;
>+	plat_data->priv_data = hdmi;
>+
>+	hdmi->dw_hdmi = dw_hdmi_probe(pdev, plat_data);
>+	if (IS_ERR(hdmi))
>+		return PTR_ERR(hdmi);
>+
>+	platform_set_drvdata(pdev, hdmi);
>+
>+	return 0;
>+}
>+
>+static void th1520_dw_hdmi_remove(struct platform_device *pdev)
>+{
>+	struct dw_hdmi *hdmi = platform_get_drvdata(pdev);
>+
>+	dw_hdmi_remove(hdmi);
>+}
>+
>+static const struct of_device_id th1520_dw_hdmi_of_table[] = {
>+	{ .compatible = "thead,th1520-dw-hdmi" },
>+	{ /* Sentinel */ },
>+};
>+MODULE_DEVICE_TABLE(of, th1520_dw_hdmi_of_table);
>+
>+static struct platform_driver th1520_dw_hdmi_platform_driver = {
>+	.probe		= th1520_dw_hdmi_probe,
>+	.remove		= th1520_dw_hdmi_remove,
>+	.driver		= {
>+		.name	= "th1520-dw-hdmi",
>+		.of_match_table = th1520_dw_hdmi_of_table,
>+	},
>+};
>+
>+module_platform_driver(th1520_dw_hdmi_platform_driver);
>+
>+MODULE_AUTHOR("Icenowy Zheng <uwu@icenowy.me>");
>+MODULE_DESCRIPTION("T-Head TH1520 HDMI Encoder Driver");
>+MODULE_LICENSE("GPL");
>-- 
>2.52.0
>
>
>_______________________________________________
>linux-riscv mailing list
>linux-riscv@lists.infradead.org
>http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-25 11:07     ` Andy Yan
@ 2025-12-26 13:27       ` Icenowy Zheng
  2025-12-27  7:03         ` Andy Yan
  0 siblings, 1 reply; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-26 13:27 UTC (permalink / raw)
  To: Andy Yan
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski, devicetree,
	Yao Zi, linux-kernel, dri-devel, Han Gao, linux-riscv

在 2025-12-25星期四的 19:07 +0800,Andy Yan写道:
> 
> 
> Hello Icenowy:
> 
> At 2025-12-25 00:12:01, "Icenowy Zheng" <zhengxingda@iscas.ac.cn>
> wrote:
> > From: Icenowy Zheng <uwu@icenowy.me>
> > 
> > T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller
> > (paired
> > with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from
> > the
> > display controller.
> > 
> > Add a driver for this controller utilizing the common DesignWare
> > HDMI
> > code in the kernel.
> > 
> > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> > ---
> > No changes in v3, v4.
> > 
> > Changes in v2:
> > - Created a new function to set PHY parameters and refactored the
> >  control flow of the configure_phy callback.
> > 
> > MAINTAINERS                             |   1 +
> > drivers/gpu/drm/bridge/Kconfig          |  10 ++
> > drivers/gpu/drm/bridge/Makefile         |   1 +
> > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173
> > ++++++++++++++++++++++++
> > 4 files changed, 185 insertions(+)
> > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> 
> 
> As this is a Synopsys DesignWare based IP, maybe it's better to put
> it under   "drivers/gpu/drm/bridge/synopsys/"
> Or just create a dir for thead, I think there will come other display
> related drivers for thead in the future.

It's not proper to place vendor glues to synopsys/ .

As for T-Head, they stopped their embedded SoC business (sold to
another company) and now they only makes data-center chips, there
should be no T-Head display drivers in the future.

> 
> 
=========== 8< ============


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re:Re: [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-26 13:27       ` [PATCH " Icenowy Zheng
@ 2025-12-27  7:03         ` Andy Yan
  2025-12-27  7:22           ` Icenowy Zheng
  0 siblings, 1 reply; 33+ messages in thread
From: Andy Yan @ 2025-12-27  7:03 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski, devicetree,
	Yao Zi, linux-kernel, dri-devel, Han Gao, linux-riscv



Hello,

在 2025-12-26 21:27:53,"Icenowy Zheng" <zhengxingda@iscas.ac.cn> 写道:
>在 2025-12-25星期四的 19:07 +0800,Andy Yan写道:
>> 
>> 
>> Hello Icenowy:
>> 
>> At 2025-12-25 00:12:01, "Icenowy Zheng" <zhengxingda@iscas.ac.cn>
>> wrote:
>> > From: Icenowy Zheng <uwu@icenowy.me>
>> > 
>> > T-Head TH1520 SoC contains a Synopsys DesignWare HDMI controller
>> > (paired
>> > with DesignWare HDMI TX PHY Gen2) that takes the "DP" output from
>> > the
>> > display controller.
>> > 
>> > Add a driver for this controller utilizing the common DesignWare
>> > HDMI
>> > code in the kernel.
>> > 
>> > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
>> > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
>> > ---
>> > No changes in v3, v4.
>> > 
>> > Changes in v2:
>> > - Created a new function to set PHY parameters and refactored the
>> >  control flow of the configure_phy callback.
>> > 
>> > MAINTAINERS                             |   1 +
>> > drivers/gpu/drm/bridge/Kconfig          |  10 ++
>> > drivers/gpu/drm/bridge/Makefile         |   1 +
>> > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173
>> > ++++++++++++++++++++++++
>> > 4 files changed, 185 insertions(+)
>> > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
>> 
>> 
>> As this is a Synopsys DesignWare based IP, maybe it's better to put
>> it under   "drivers/gpu/drm/bridge/synopsys/"
>> Or just create a dir for thead, I think there will come other display
>> related drivers for thead in the future.
>
>It's not proper to place vendor glues to synopsys/ .


TI has precedents for doing this with Cadence IP, see 

drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.c
drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c 


>
>As for T-Head, they stopped their embedded SoC business (sold to
>another company) and now they only makes data-center chips, there
>should be no T-Head display drivers in the future.
>
>> 
>> 
>=========== 8< ============
>
>
>_______________________________________________
>linux-riscv mailing list
>linux-riscv@lists.infradead.org
>http://lists.infradead.org/mailman/listinfo/linux-riscv

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: Re: [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-27  7:03         ` Andy Yan
@ 2025-12-27  7:22           ` Icenowy Zheng
  2025-12-28 17:41             ` Heiko Stübner
  0 siblings, 1 reply; 33+ messages in thread
From: Icenowy Zheng @ 2025-12-27  7:22 UTC (permalink / raw)
  To: Andy Yan
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner,
	Andrzej Hajda, Neil Armstrong, Robert Foss, Laurent Pinchart,
	Jonas Karlman, Jernej Skrabec, Michal Wilczynski, devicetree,
	Yao Zi, linux-kernel, dri-devel, Han Gao, linux-riscv

在 2025-12-27星期六的 15:03 +0800,Andy Yan写道:
> 
> 
> Hello,
> 
> 在 2025-12-26 21:27:53,"Icenowy Zheng" <zhengxingda@iscas.ac.cn> 写道:
> > 在 2025-12-25星期四的 19:07 +0800,Andy Yan写道:
> > > 
> > > 
> > > Hello Icenowy:
> > > 
> > > At 2025-12-25 00:12:01, "Icenowy Zheng" <zhengxingda@iscas.ac.cn>
> > > wrote:
> > > > From: Icenowy Zheng <uwu@icenowy.me>
> > > > 
> > > > T-Head TH1520 SoC contains a Synopsys DesignWare HDMI
> > > > controller
> > > > (paired
> > > > with DesignWare HDMI TX PHY Gen2) that takes the "DP" output
> > > > from
> > > > the
> > > > display controller.
> > > > 
> > > > Add a driver for this controller utilizing the common
> > > > DesignWare
> > > > HDMI
> > > > code in the kernel.
> > > > 
> > > > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> > > > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> > > > ---
> > > > No changes in v3, v4.
> > > > 
> > > > Changes in v2:
> > > > - Created a new function to set PHY parameters and refactored
> > > > the
> > > >  control flow of the configure_phy callback.
> > > > 
> > > > MAINTAINERS                             |   1 +
> > > > drivers/gpu/drm/bridge/Kconfig          |  10 ++
> > > > drivers/gpu/drm/bridge/Makefile         |   1 +
> > > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173
> > > > ++++++++++++++++++++++++
> > > > 4 files changed, 185 insertions(+)
> > > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > 
> > > 
> > > As this is a Synopsys DesignWare based IP, maybe it's better to
> > > put
> > > it under   "drivers/gpu/drm/bridge/synopsys/"
> > > Or just create a dir for thead, I think there will come other
> > > display
> > > related drivers for thead in the future.
> > 
> > It's not proper to place vendor glues to synopsys/ .
> 
> 
> TI has precedents for doing this with Cadence IP, see 
> 
> drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.c
> drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c 

Well for this kind of things, I prefer to hear from the maintainer of
DW HDMI bridges (although it seems that this has no dedicated
MAINTAINERS entry and get_maintainers just returns people for DRM
DRIVERS FOR BRIDGE CHIPS).

> 
> 
> > 
> > As for T-Head, they stopped their embedded SoC business (sold to
> > another company) and now they only makes data-center chips, there
> > should be no T-Head display drivers in the future.
> > 
> > > 
> > > 
> > =========== 8< ============
> > 
> > 
> > _______________________________________________
> > linux-riscv mailing list
> > linux-riscv@lists.infradead.org
> > http://lists.infradead.org/mailman/listinfo/linux-riscv


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-25  9:35     ` Han Gao (Revy)
@ 2025-12-27 11:08       ` Krzysztof Kozlowski
  0 siblings, 0 replies; 33+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-27 11:08 UTC (permalink / raw)
  To: Han Gao (Revy)
  Cc: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng

On Thu, Dec 25, 2025 at 05:35:34PM +0800, Han Gao (Revy) wrote:
 > +
> > +            dpu_out_dp1: endpoint@1 {
> > +              reg = <1>;
> > +              remote-endpoint = <&hdmi_in>;
> > +            };
> > +          };
> > +        };
> > +      };
> > +    };
> > -- 
> > 2.52.0
> > 
> Tested-by: Han Gao <gaohan@iscas.ac.cn <mailto:gaohan@iscas.ac.cn>>

No, really, how?

Please explain me how can you test a binding (and build process is not
testing, otherwise we all should keep getting tested-by, especially
Rob!)

I don't agree on fake tags.


^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-25  9:45     ` Han Gao (Revy)
@ 2025-12-27 11:09       ` Krzysztof Kozlowski
  2025-12-27 12:08         ` Han Gao
  0 siblings, 1 reply; 33+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-27 11:09 UTC (permalink / raw)
  To: Han Gao (Revy)
  Cc: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng

On Thu, Dec 25, 2025 at 05:45:00PM +0800, Han Gao (Revy) wrote:
> > +            dpu_out_dp1: endpoint@1 {
> > +              reg = <1>;
> > +              remote-endpoint = <&hdmi_in>;
> > +            };
> > +          };
> > +        };
> > +      };
> > +    };
> > -- 
> > 2.52.0
> > 
> 
> Tested-by: Han Gao <gaohan@iscas.ac.cn>

NAK, not true. Otherwise explain me how can you test the bindings.



^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-27 11:09       ` Krzysztof Kozlowski
@ 2025-12-27 12:08         ` Han Gao
  2025-12-28  7:48           ` Krzysztof Kozlowski
  0 siblings, 1 reply; 33+ messages in thread
From: Han Gao @ 2025-12-27 12:08 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng

On Sat, Dec 27, 2025 at 7:09 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On Thu, Dec 25, 2025 at 05:45:00PM +0800, Han Gao (Revy) wrote:
> > > +            dpu_out_dp1: endpoint@1 {
> > > +              reg = <1>;
> > > +              remote-endpoint = <&hdmi_in>;
> > > +            };
> > > +          };
> > > +        };
> > > +      };
> > > +    };
> > > --
> > > 2.52.0
> > >
> >
> > Tested-by: Han Gao <gaohan@iscas.ac.cn>
>
> NAK, not true. Otherwise explain me how can you test the bindings.
>
>
I cherry-picked the patches I gave to Tested-by on my test branch,
compiled and ran them on Lichee Pi 4a.
All the relevant patches were also given to Tested-by.

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-27 12:08         ` Han Gao
@ 2025-12-28  7:48           ` Krzysztof Kozlowski
  2025-12-28  7:54             ` Han Gao
  0 siblings, 1 reply; 33+ messages in thread
From: Krzysztof Kozlowski @ 2025-12-28  7:48 UTC (permalink / raw)
  To: Han Gao
  Cc: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng

On 27/12/2025 13:08, Han Gao wrote:
> On Sat, Dec 27, 2025 at 7:09 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>
>> On Thu, Dec 25, 2025 at 05:45:00PM +0800, Han Gao (Revy) wrote:
>>>> +            dpu_out_dp1: endpoint@1 {
>>>> +              reg = <1>;
>>>> +              remote-endpoint = <&hdmi_in>;
>>>> +            };
>>>> +          };
>>>> +        };
>>>> +      };
>>>> +    };
>>>> --
>>>> 2.52.0
>>>>
>>>
>>> Tested-by: Han Gao <gaohan@iscas.ac.cn>
>>
>> NAK, not true. Otherwise explain me how can you test the bindings.
>>
>>
> I cherry-picked the patches I gave to Tested-by on my test branch,
> compiled and ran them on Lichee Pi 4a.

You cannot "run a binding".



Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-28  7:48           ` Krzysztof Kozlowski
@ 2025-12-28  7:54             ` Han Gao
  0 siblings, 0 replies; 33+ messages in thread
From: Han Gao @ 2025-12-28  7:54 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski, Yao Zi, dri-devel, devicetree, linux-kernel,
	linux-riscv, Icenowy Zheng

On Sun, Dec 28, 2025 at 3:49 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
>
> On 27/12/2025 13:08, Han Gao wrote:
> > On Sat, Dec 27, 2025 at 7:09 PM Krzysztof Kozlowski <krzk@kernel.org> wrote:
> >>
> >> On Thu, Dec 25, 2025 at 05:45:00PM +0800, Han Gao (Revy) wrote:
> >>>> +            dpu_out_dp1: endpoint@1 {
> >>>> +              reg = <1>;
> >>>> +              remote-endpoint = <&hdmi_in>;
> >>>> +            };
> >>>> +          };
> >>>> +        };
> >>>> +      };
> >>>> +    };
> >>>> --
> >>>> 2.52.0
> >>>>
> >>>
> >>> Tested-by: Han Gao <gaohan@iscas.ac.cn>
> >>
> >> NAK, not true. Otherwise explain me how can you test the bindings.
> >>
> >>
> > I cherry-picked the patches I gave to Tested-by on my test branch,
> > compiled and ran them on Lichee Pi 4a.
>
> You cannot "run a binding".

I understand now. My understanding of dt-binding testing was incorrect.
I will not repeat this mistake in Tested-By.

>
>
>
> Best regards,
> Krzysztof

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 5/9] drm/bridge: add a driver for T-Head TH1520 HDMI controller
  2025-12-27  7:22           ` Icenowy Zheng
@ 2025-12-28 17:41             ` Heiko Stübner
  0 siblings, 0 replies; 33+ messages in thread
From: Heiko Stübner @ 2025-12-28 17:41 UTC (permalink / raw)
  To: Andy Yan, Icenowy Zheng
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Drew Fustini, Guo Ren, Fu Wei, Philipp Zabel, Andrzej Hajda,
	Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
	Jernej Skrabec, Michal Wilczynski, devicetree, Yao Zi,
	linux-kernel, dri-devel, Han Gao, linux-riscv

Am Samstag, 27. Dezember 2025, 08:22:05 Mitteleuropäische Normalzeit schrieb Icenowy Zheng:
> 在 2025-12-27星期六的 15:03 +0800,Andy Yan写道:
> > 
> > 
> > Hello,
> > 
> > 在 2025-12-26 21:27:53,"Icenowy Zheng" <zhengxingda@iscas.ac.cn> 写道:
> > > 在 2025-12-25星期四的 19:07 +0800,Andy Yan写道:
> > > > 
> > > > 
> > > > Hello Icenowy:
> > > > 
> > > > At 2025-12-25 00:12:01, "Icenowy Zheng" <zhengxingda@iscas.ac.cn>
> > > > wrote:
> > > > > From: Icenowy Zheng <uwu@icenowy.me>
> > > > > 
> > > > > T-Head TH1520 SoC contains a Synopsys DesignWare HDMI
> > > > > controller
> > > > > (paired
> > > > > with DesignWare HDMI TX PHY Gen2) that takes the "DP" output
> > > > > from
> > > > > the
> > > > > display controller.
> > > > > 
> > > > > Add a driver for this controller utilizing the common
> > > > > DesignWare
> > > > > HDMI
> > > > > code in the kernel.
> > > > > 
> > > > > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> > > > > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> > > > > ---
> > > > > No changes in v3, v4.
> > > > > 
> > > > > Changes in v2:
> > > > > - Created a new function to set PHY parameters and refactored
> > > > > the
> > > > >  control flow of the configure_phy callback.
> > > > > 
> > > > > MAINTAINERS                             |   1 +
> > > > > drivers/gpu/drm/bridge/Kconfig          |  10 ++
> > > > > drivers/gpu/drm/bridge/Makefile         |   1 +
> > > > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 173
> > > > > ++++++++++++++++++++++++
> > > > > 4 files changed, 185 insertions(+)
> > > > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > 
> > > > 
> > > > As this is a Synopsys DesignWare based IP, maybe it's better to
> > > > put
> > > > it under   "drivers/gpu/drm/bridge/synopsys/"
> > > > Or just create a dir for thead, I think there will come other
> > > > display
> > > > related drivers for thead in the future.
> > > 
> > > It's not proper to place vendor glues to synopsys/ .
> > 
> > 
> > TI has precedents for doing this with Cadence IP, see 
> > 
> > drivers/gpu/drm/bridge/cadence/cdns-dsi-j721e.c
> > drivers/gpu/drm/bridge/cadence/cdns-mhdp8546-j721e.c 
> 
> Well for this kind of things, I prefer to hear from the maintainer of
> DW HDMI bridges (although it seems that this has no dedicated
> MAINTAINERS entry and get_maintainers just returns people for DRM
> DRIVERS FOR BRIDGE CHIPS).

I think the best way to resolve this question would be to actively ask
on the dri-devel IRC channel (on the OFTC network), because people
might not even realize this quite interesting question is in here, but
I guess most of the relevant people are on there.

But probably wait after new years, so people can actually return from
all the festivities happening right now.


Heiko



^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520)
  2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
                     ` (8 preceding siblings ...)
  2025-12-24 16:12   ` [PATCH v4 9/9] mailmap: map all Icenowy Zheng's mail addresses Icenowy Zheng
@ 2025-12-29 16:32   ` Michal Wilczynski
  9 siblings, 0 replies; 33+ messages in thread
From: Michal Wilczynski @ 2025-12-29 16:32 UTC (permalink / raw)
  To: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv



On 12/24/25 17:11, Icenowy Zheng wrote:
> This patchset tries to add a driver for Verisilicon DC8200 driver, and
> demonstrates the driver on T-Head TH1520 with its HDMI output.
> 
> This display controller IP is used on StarFive JH7110 too, but as the
> HDMI controller used there isn't as common as the DesignWare one, I
> choose to use TH1520 in this patchset.
> 
> The DC driver is written with other DC-series (mainly DC8000, which is
> known to be used on Eswin EIC7700 SoC) display controllers in mind, and
> uses the identification registers available on all Vivante branded IPs.
> A known exception is DCNano display controller, which is unlikely to be
> supported by this driver because of totally different register map and
> no known identification registers. (P.S. the in-tree loongson DRM driver
> seems to be for some DCNano instances based on the register map.)
> 
> The HDMI controller seems to come with some common PHY by Synopsys, the
> DesignWare HDMI TX 2.0 PHY. By searching a few register names from the
> BSP driver of that PHY, that PHY seems to be used by a in-tree dw-hdmi
> glue, rcar_dw_hdmi -- an updated downstream version of rcar_dw_hdmi
> contains all 6 registers set here in the th1520-dw-hdmi driver. Some
> more suprising thing is that RK3288 uses the same PHY too, but the
> in-tree dw_hdmi-rockchip driver writes the configuration data array in a
> weird way to reuse the HDMI 3D TX PHY configuring function. It might be
> valuable to add common configuring function and configuration data
> definition for this HDMI 2.0 PHY too, but the current driver in this
> patchset simply duplicated most configuration logic from rcar_dw_hdmi
> driver (but with 3 extra configuration registers configured).
> 
> Icenowy Zheng (9):
>   dt-bindings: vendor-prefixes: add verisilicon
>   dt-bindings: display: add verisilicon,dc
>   drm: verisilicon: add a driver for Verisilicon display controllers
>   dt-bindings: display/bridge: add binding for TH1520 HDMI controller
>   drm/bridge: add a driver for T-Head TH1520 HDMI controller
>   riscv: dts: thead: add DPU and HDMI device tree nodes
>   riscv: dts: thead: lichee-pi-4a: enable HDMI
>   MAINTAINERS: assign myself as maintainer for verisilicon DC driver
>   mailmap: map all Icenowy Zheng's mail addresses
> 

I tested this patchset on the Lichee Pi 4A (TH1520). I successfully ran a 
fully hardware accelerated graphics stack using Weston and Zink over the 
HDMI output.

I have documented the reproduction steps and test results here:
[1] https://mwilczynski.dev/posts/riscv-gpu-zink/#building-the-stack-reproduction-guide

Great work, thank you!

Feel free to add to driver and DTS patches (3,5,6,7).
Tested-by: Michal Wilczynski <m.wilczynski@samsung.com>

Best regards,
-- 
Michal Wilczynski <m.wilczynski@samsung.com>

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers
  2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
  2025-12-25  9:36     ` Han Gao (Revy)
  2025-12-25  9:45     ` Han Gao (Revy)
@ 2025-12-31 14:20     ` Luca Ceresoli
  2 siblings, 0 replies; 33+ messages in thread
From: Luca Ceresoli @ 2025-12-31 14:20 UTC (permalink / raw)
  To: Icenowy Zheng, Maarten Lankhorst, Maxime Ripard,
	Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
	Philipp Zabel, Heiko Stuebner, Andrzej Hajda, Neil Armstrong,
	Robert Foss, Laurent Pinchart, Jonas Karlman, Jernej Skrabec,
	Michal Wilczynski
  Cc: Han Gao, Yao Zi, dri-devel, devicetree, linux-kernel, linux-riscv,
	Icenowy Zheng

On Wed Dec 24, 2025 at 5:11 PM CET, Icenowy Zheng wrote:
> From: Icenowy Zheng <uwu@icenowy.me>
>
> This is a from-scratch driver targeting Verisilicon DC-series display
> controllers, which feature self-identification functionality like their
> GC-series GPUs.
>
> Only DC8200 is being supported now, and only the main framebuffer is set
> up (as the DRM primary plane). Support for more DC models and more
> features is my further targets.
>
> As the display controller is delivered to SoC vendors as a whole part,
> this driver does not use component framework and extra bridges inside a
> SoC is expected to be implemented as dedicated bridges (this driver
> properly supports bridge chaining).
>
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>

[...]

> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.h

> +struct vs_bridge {
> +	struct drm_bridge base;
> +	struct drm_encoder *enc;
> +	struct drm_connector *conn;
> +
> +	struct vs_crtc *crtc;
> +	struct drm_bridge *next;

It is a common convention to call this 'next_bridge'. This makes it easier
to understand but also to grep and find similar patterns.

For info, we are working to move to 'struct drm_bridge::next_bridge' [0] as
you can see from example patches like [1]. However this currently applies
only to drivers using of_drm_find_bridge(), so it does not affect your
driver.

I'm sorry I have seen your patch only at v4.

[0] https://gitlab.freedesktop.org/drm/misc/kernel/-/commit/3fdeae134ba956aacbd87d5532c025913c98fc49
[1] https://gitlab.freedesktop.org/drm/misc/kernel/-/commit/8f92a5fcbfe33f86b08f5f74dcc58a41425ea8c0

Luca

--
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
  2025-12-25  9:35     ` Han Gao (Revy)
  2025-12-25  9:45     ` Han Gao (Revy)
@ 2026-01-05 15:46     ` Rob Herring
  2026-01-05 16:11       ` Icenowy Zheng
  2 siblings, 1 reply; 33+ messages in thread
From: Rob Herring @ 2026-01-05 15:46 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Conor Dooley, Drew Fustini,
	Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner, Andrzej Hajda,
	Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
	Jernej Skrabec, Michal Wilczynski, Han Gao, Yao Zi, dri-devel,
	devicetree, linux-kernel, linux-riscv, Icenowy Zheng

On Thu, Dec 25, 2025 at 12:11:58AM +0800, Icenowy Zheng wrote:
> From: Icenowy Zheng <uwu@icenowy.me>
> 
> Verisilicon has a series of display controllers prefixed with DC and
> with self-identification facility like their GC series GPUs.
> 
> Add a device tree binding for it.
> 
> Depends on the specific DC model, it can have either one or two display
> outputs, and each display output could be set to DPI signal or "DP"
> signal (which seems to be some plain parallel bus to HDMI controllers).
> 
> Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> ---
> Changes in v4:
> - Added a comment for "verisilicon,dc" that says the ID/revision is
>   discoverable via registers.
> - Removed clock minItems constraint w/o specific compatible strings.
> 
> Changes in v3:
> - Added SoC-specific compatible string, and arm the binding with clock /
>   port checking for the specific SoC (with a 2-output DC).
> 
> Changes in v2:
> - Fixed misspelt "versilicon" in title.
> - Moved minItems in clock properties to be earlier than items.
> - Re-aligned multi-line clocks and resets in example.
> 
>  .../bindings/display/verisilicon,dc.yaml      | 144 ++++++++++++++++++
>  1 file changed, 144 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> 
> diff --git a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> new file mode 100644
> index 0000000000000..fe64cc1466690
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> @@ -0,0 +1,144 @@
> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Verisilicon DC-series display controllers
> +
> +maintainers:
> +  - Icenowy Zheng <uwu@icenowy.me>
> +
> +properties:
> +  $nodename:
> +    pattern: "^display@[0-9a-f]+$"
> +
> +  compatible:
> +    items:
> +      - enum:
> +          - thead,th1520-dc8200
> +      - const: verisilicon,dc # DC IPs have discoverable ID/revision registers
> +
> +  reg:
> +    maxItems: 1
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  clocks:
> +    items:
> +      - description: DC Core clock
> +      - description: DMA AXI bus clock
> +      - description: Configuration AHB bus clock
> +      - description: Pixel clock of output 0
> +      - description: Pixel clock of output 1
> +
> +  clock-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +      - const: pix0
> +      - const: pix1
> +
> +  resets:
> +    items:
> +      - description: DC Core reset
> +      - description: DMA AXI bus reset
> +      - description: Configuration AHB bus reset
> +
> +  reset-names:
> +    items:
> +      - const: core
> +      - const: axi
> +      - const: ahb
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The first output channel , endpoint 0 should be

No space before comma. Or perhaps should be a period instead.


> +          used for DPI format output and endpoint 1 should be used
> +          for DP format output.
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        description: The second output channel if the DC variant
> +          supports. Follow the same endpoint addressing rule with
> +          the first port.
> +
> +    required:
> +      - port@0
> +
> +required:
> +  - compatible
> +  - reg
> +  - interrupts
> +  - clocks
> +  - clock-names
> +  - ports
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: thead,th1520-dc8200
> +    then:
> +      properties:
> +        clocks:
> +          minItems: 5

That's already implicitly the min. Perhaps you wanted 'minItems: 4' on 
the clocks and clock-names definitions for versions with only 1 output?

> +        ports:
> +          required:
> +            - port@0
> +            - port@1

It is valid to omit these if the output is present, but unused.

> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    #include <dt-bindings/clock/thead,th1520-clk-ap.h>
> +    #include <dt-bindings/reset/thead,th1520-reset.h>
> +    soc {
> +      #address-cells = <2>;
> +      #size-cells = <2>;
> +
> +      display@ffef600000 {
> +        compatible = "thead,th1520-dc8200", "verisilicon,dc";
> +        reg = <0xff 0xef600000 0x0 0x100000>;
> +        interrupts = <93 IRQ_TYPE_LEVEL_HIGH>;
> +        clocks = <&clk_vo CLK_DPU_CCLK>,
> +                 <&clk_vo CLK_DPU_ACLK>,
> +                 <&clk_vo CLK_DPU_HCLK>,
> +                 <&clk_vo CLK_DPU_PIXELCLK0>,
> +                 <&clk_vo CLK_DPU_PIXELCLK1>;
> +        clock-names = "core", "axi", "ahb", "pix0", "pix1";
> +        resets = <&rst TH1520_RESET_ID_DPU_CORE>,
> +                 <&rst TH1520_RESET_ID_DPU_AXI>,
> +                 <&rst TH1520_RESET_ID_DPU_AHB>;
> +        reset-names = "core", "axi", "ahb";
> +
> +        ports {
> +          #address-cells = <1>;
> +          #size-cells = <0>;
> +
> +          port@0 {
> +            reg = <0>;
> +          };
> +
> +          port@1 {
> +            reg = <1>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            dpu_out_dp1: endpoint@1 {
> +              reg = <1>;
> +              remote-endpoint = <&hdmi_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> -- 
> 2.52.0
> 

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2026-01-05 15:46     ` Rob Herring
@ 2026-01-05 16:11       ` Icenowy Zheng
  2026-01-05 16:20         ` Rob Herring
  0 siblings, 1 reply; 33+ messages in thread
From: Icenowy Zheng @ 2026-01-05 16:11 UTC (permalink / raw)
  To: Rob Herring
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Conor Dooley, Drew Fustini,
	Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner, Andrzej Hajda,
	Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
	Jernej Skrabec, Michal Wilczynski, Han Gao, Yao Zi, dri-devel,
	devicetree, linux-kernel, linux-riscv

在 2026-01-05星期一的 09:46 -0600,Rob Herring写道:
> On Thu, Dec 25, 2025 at 12:11:58AM +0800, Icenowy Zheng wrote:
> > From: Icenowy Zheng <uwu@icenowy.me>
> > 
> > Verisilicon has a series of display controllers prefixed with DC
> > and
> > with self-identification facility like their GC series GPUs.
> > 
> > Add a device tree binding for it.
> > 
> > Depends on the specific DC model, it can have either one or two
> > display
> > outputs, and each display output could be set to DPI signal or "DP"
> > signal (which seems to be some plain parallel bus to HDMI
> > controllers).
> > 
> > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> > ---
> > Changes in v4:
> > - Added a comment for "verisilicon,dc" that says the ID/revision is
> >   discoverable via registers.
> > - Removed clock minItems constraint w/o specific compatible
> > strings.
> > 
> > Changes in v3:
> > - Added SoC-specific compatible string, and arm the binding with
> > clock /
> >   port checking for the specific SoC (with a 2-output DC).
> > 
> > Changes in v2:
> > - Fixed misspelt "versilicon" in title.
> > - Moved minItems in clock properties to be earlier than items.
> > - Re-aligned multi-line clocks and resets in example.
> > 
> >  .../bindings/display/verisilicon,dc.yaml      | 144
> > ++++++++++++++++++
> >  1 file changed, 144 insertions(+)
> >  create mode 100644
> > Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > 
> > diff --git
> > a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > new file mode 100644
> > index 0000000000000..fe64cc1466690
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > @@ -0,0 +1,144 @@
> > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Verisilicon DC-series display controllers
> > +
> > +maintainers:
> > +  - Icenowy Zheng <uwu@icenowy.me>
> > +
> > +properties:
> > +  $nodename:
> > +    pattern: "^display@[0-9a-f]+$"
> > +
> > +  compatible:
> > +    items:
> > +      - enum:
> > +          - thead,th1520-dc8200
> > +      - const: verisilicon,dc # DC IPs have discoverable
> > ID/revision registers
> > +
> > +  reg:
> > +    maxItems: 1
> > +
> > +  interrupts:
> > +    maxItems: 1
> > +
> > +  clocks:
> > +    items:
> > +      - description: DC Core clock
> > +      - description: DMA AXI bus clock
> > +      - description: Configuration AHB bus clock
> > +      - description: Pixel clock of output 0
> > +      - description: Pixel clock of output 1
> > +
> > +  clock-names:
> > +    items:
> > +      - const: core
> > +      - const: axi
> > +      - const: ahb
> > +      - const: pix0
> > +      - const: pix1
> > +
> > +  resets:
> > +    items:
> > +      - description: DC Core reset
> > +      - description: DMA AXI bus reset
> > +      - description: Configuration AHB bus reset
> > +
> > +  reset-names:
> > +    items:
> > +      - const: core
> > +      - const: axi
> > +      - const: ahb
> > +
> > +  ports:
> > +    $ref: /schemas/graph.yaml#/properties/ports
> > +
> > +    properties:
> > +      port@0:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: The first output channel , endpoint 0 should
> > be
> 
> No space before comma. Or perhaps should be a period instead.

Well I don't know why I inserted such a space, will remove it.

> 
> 
> > +          used for DPI format output and endpoint 1 should be used
> > +          for DP format output.
> > +
> > +      port@1:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        description: The second output channel if the DC variant
> > +          supports. Follow the same endpoint addressing rule with
> > +          the first port.
> > +
> > +    required:
> > +      - port@0
> > +
> > +required:
> > +  - compatible
> > +  - reg
> > +  - interrupts
> > +  - clocks
> > +  - clock-names
> > +  - ports
> > +
> > +allOf:
> > +  - if:
> > +      properties:
> > +        compatible:
> > +          contains:
> > +            const: thead,th1520-dc8200
> > +    then:
> > +      properties:
> > +        clocks:
> > +          minItems: 5
> 
> That's already implicitly the min. Perhaps you wanted 'minItems: 4'
> on 
> the clocks and clock-names definitions for versions with only 1
> output?

Previously I specified minItems: 4 in the general part, however this
does not play well when some of core/axi/ahb clocks are not present.

> 
> > +        ports:
> > +          required:
> > +            - port@0
> > +            - port@1
> 
> It is valid to omit these if the output is present, but unused.

Well this sounds reasonable, although my driver does not play well if
only a port@1 is defined w/o port@0 .

Considering the previous two snippets, should I just remove this if
part?

> 
> > +
> > +additionalProperties: false
> > +
====== 8< ========

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2026-01-05 16:11       ` Icenowy Zheng
@ 2026-01-05 16:20         ` Rob Herring
  2026-01-13 13:41           ` Icenowy Zheng
  0 siblings, 1 reply; 33+ messages in thread
From: Rob Herring @ 2026-01-05 16:20 UTC (permalink / raw)
  To: Icenowy Zheng
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Conor Dooley, Drew Fustini,
	Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner, Andrzej Hajda,
	Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
	Jernej Skrabec, Michal Wilczynski, Han Gao, Yao Zi, dri-devel,
	devicetree, linux-kernel, linux-riscv

On Mon, Jan 5, 2026 at 10:13 AM Icenowy Zheng <uwu@icenowy.me> wrote:
>
> 在 2026-01-05星期一的 09:46 -0600,Rob Herring写道:
> > On Thu, Dec 25, 2025 at 12:11:58AM +0800, Icenowy Zheng wrote:
> > > From: Icenowy Zheng <uwu@icenowy.me>
> > >
> > > Verisilicon has a series of display controllers prefixed with DC
> > > and
> > > with self-identification facility like their GC series GPUs.
> > >
> > > Add a device tree binding for it.
> > >
> > > Depends on the specific DC model, it can have either one or two
> > > display
> > > outputs, and each display output could be set to DPI signal or "DP"
> > > signal (which seems to be some plain parallel bus to HDMI
> > > controllers).
> > >
> > > Signed-off-by: Icenowy Zheng <uwu@icenowy.me>
> > > Signed-off-by: Icenowy Zheng <zhengxingda@iscas.ac.cn>
> > > ---
> > > Changes in v4:
> > > - Added a comment for "verisilicon,dc" that says the ID/revision is
> > >   discoverable via registers.
> > > - Removed clock minItems constraint w/o specific compatible
> > > strings.
> > >
> > > Changes in v3:
> > > - Added SoC-specific compatible string, and arm the binding with
> > > clock /
> > >   port checking for the specific SoC (with a 2-output DC).
> > >
> > > Changes in v2:
> > > - Fixed misspelt "versilicon" in title.
> > > - Moved minItems in clock properties to be earlier than items.
> > > - Re-aligned multi-line clocks and resets in example.
> > >
> > >  .../bindings/display/verisilicon,dc.yaml      | 144
> > > ++++++++++++++++++
> > >  1 file changed, 144 insertions(+)
> > >  create mode 100644
> > > Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > >
> > > diff --git
> > > a/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > > b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > > new file mode 100644
> > > index 0000000000000..fe64cc1466690
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > > @@ -0,0 +1,144 @@
> > > +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
> > > +%YAML 1.2
> > > +---
> > > +$id: http://devicetree.org/schemas/display/verisilicon,dc.yaml#
> > > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > > +
> > > +title: Verisilicon DC-series display controllers
> > > +
> > > +maintainers:
> > > +  - Icenowy Zheng <uwu@icenowy.me>
> > > +
> > > +properties:
> > > +  $nodename:
> > > +    pattern: "^display@[0-9a-f]+$"
> > > +
> > > +  compatible:
> > > +    items:
> > > +      - enum:
> > > +          - thead,th1520-dc8200
> > > +      - const: verisilicon,dc # DC IPs have discoverable
> > > ID/revision registers
> > > +
> > > +  reg:
> > > +    maxItems: 1
> > > +
> > > +  interrupts:
> > > +    maxItems: 1
> > > +
> > > +  clocks:
> > > +    items:
> > > +      - description: DC Core clock
> > > +      - description: DMA AXI bus clock
> > > +      - description: Configuration AHB bus clock
> > > +      - description: Pixel clock of output 0
> > > +      - description: Pixel clock of output 1
> > > +
> > > +  clock-names:
> > > +    items:
> > > +      - const: core
> > > +      - const: axi
> > > +      - const: ahb
> > > +      - const: pix0
> > > +      - const: pix1
> > > +
> > > +  resets:
> > > +    items:
> > > +      - description: DC Core reset
> > > +      - description: DMA AXI bus reset
> > > +      - description: Configuration AHB bus reset
> > > +
> > > +  reset-names:
> > > +    items:
> > > +      - const: core
> > > +      - const: axi
> > > +      - const: ahb
> > > +
> > > +  ports:
> > > +    $ref: /schemas/graph.yaml#/properties/ports
> > > +
> > > +    properties:
> > > +      port@0:
> > > +        $ref: /schemas/graph.yaml#/properties/port
> > > +        description: The first output channel , endpoint 0 should
> > > be
> >
> > No space before comma. Or perhaps should be a period instead.
>
> Well I don't know why I inserted such a space, will remove it.
>
> >
> >
> > > +          used for DPI format output and endpoint 1 should be used
> > > +          for DP format output.
> > > +
> > > +      port@1:
> > > +        $ref: /schemas/graph.yaml#/properties/port
> > > +        description: The second output channel if the DC variant
> > > +          supports. Follow the same endpoint addressing rule with
> > > +          the first port.
> > > +
> > > +    required:
> > > +      - port@0
> > > +
> > > +required:
> > > +  - compatible
> > > +  - reg
> > > +  - interrupts
> > > +  - clocks
> > > +  - clock-names
> > > +  - ports
> > > +
> > > +allOf:
> > > +  - if:
> > > +      properties:
> > > +        compatible:
> > > +          contains:
> > > +            const: thead,th1520-dc8200
> > > +    then:
> > > +      properties:
> > > +        clocks:
> > > +          minItems: 5
> >
> > That's already implicitly the min. Perhaps you wanted 'minItems: 4'
> > on
> > the clocks and clock-names definitions for versions with only 1
> > output?
>
> Previously I specified minItems: 4 in the general part, however this
> does not play well when some of core/axi/ahb clocks are not present.

I don't understand. That would only make pix1 optional. There of
course is no way we can check that 'clocks' entries are pointing to
the correct clocks.

> > > +        ports:
> > > +          required:
> > > +            - port@0
> > > +            - port@1
> >
> > It is valid to omit these if the output is present, but unused.
>
> Well this sounds reasonable, although my driver does not play well if
> only a port@1 is defined w/o port@0 .

Sounds like your driver should be fixed.

> Considering the previous two snippets, should I just remove this if
> part?

I would, yes.

Rob

^ permalink raw reply	[flat|nested] 33+ messages in thread

* Re: [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc
  2026-01-05 16:20         ` Rob Herring
@ 2026-01-13 13:41           ` Icenowy Zheng
  0 siblings, 0 replies; 33+ messages in thread
From: Icenowy Zheng @ 2026-01-13 13:41 UTC (permalink / raw)
  To: Rob Herring
  Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
	Simona Vetter, Krzysztof Kozlowski, Conor Dooley, Drew Fustini,
	Guo Ren, Fu Wei, Philipp Zabel, Heiko Stuebner, Andrzej Hajda,
	Neil Armstrong, Robert Foss, Laurent Pinchart, Jonas Karlman,
	Jernej Skrabec, Michal Wilczynski, Han Gao, Yao Zi, dri-devel,
	devicetree, linux-kernel, linux-riscv

在 2026-01-05星期一的 10:20 -0600,Rob Herring写道:
========== 8< ==============
> > > > +        ports:
> > > > +          required:
> > > > +            - port@0
> > > > +            - port@1
> > > 
> > > It is valid to omit these if the output is present, but unused.
> > 
> > Well this sounds reasonable, although my driver does not play well
> > if
> > only a port@1 is defined w/o port@0 .
> 
> Sounds like your driver should be fixed.

After this requirement is dropped, should the unconnected port@0 also
be removed from the DT example?

> 
> > Considering the previous two snippets, should I just remove this if
> > part?
> 
> I would, yes.
> 
> Rob


^ permalink raw reply	[flat|nested] 33+ messages in thread

end of thread, other threads:[~2026-01-13 13:43 UTC | newest]

Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <CGME20251224161255eucas1p24c6de486b021f25007ed1ce24468cab2@eucas1p2.samsung.com>
2025-12-24 16:11 ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
2025-12-24 16:11   ` [PATCH v4 1/9] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
2025-12-24 16:11   ` [PATCH v4 2/9] dt-bindings: display: add verisilicon,dc Icenowy Zheng
2025-12-25  9:35     ` Han Gao (Revy)
2025-12-27 11:08       ` Krzysztof Kozlowski
2025-12-25  9:45     ` Han Gao (Revy)
2025-12-27 11:09       ` Krzysztof Kozlowski
2025-12-27 12:08         ` Han Gao
2025-12-28  7:48           ` Krzysztof Kozlowski
2025-12-28  7:54             ` Han Gao
2026-01-05 15:46     ` Rob Herring
2026-01-05 16:11       ` Icenowy Zheng
2026-01-05 16:20         ` Rob Herring
2026-01-13 13:41           ` Icenowy Zheng
2025-12-24 16:11   ` [PATCH v4 3/9] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
2025-12-25  9:36     ` Han Gao (Revy)
2025-12-25  9:45     ` Han Gao (Revy)
2025-12-31 14:20     ` Luca Ceresoli
2025-12-24 16:12   ` [PATCH v4 4/9] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
2025-12-24 16:12   ` [PATCH v4 5/9] drm/bridge: add a driver for T-Head " Icenowy Zheng
2025-12-25  9:46     ` Han Gao (Revy)
2025-12-25 11:07     ` Andy Yan
2025-12-26 13:27       ` [PATCH " Icenowy Zheng
2025-12-27  7:03         ` Andy Yan
2025-12-27  7:22           ` Icenowy Zheng
2025-12-28 17:41             ` Heiko Stübner
2025-12-24 16:12   ` [PATCH v4 6/9] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
2025-12-25  9:46     ` Han Gao (Revy)
2025-12-24 16:12   ` [PATCH v4 7/9] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
2025-12-25  9:47     ` Han Gao (Revy)
2025-12-24 16:12   ` [PATCH v4 8/9] MAINTAINERS: assign myself as maintainer for verisilicon DC driver Icenowy Zheng
2025-12-24 16:12   ` [PATCH v4 9/9] mailmap: map all Icenowy Zheng's mail addresses Icenowy Zheng
2025-12-29 16:32   ` [PATCH v4 0/9] Verisilicon DC8200 driver (and adaption to TH1520) Michal Wilczynski

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox