* [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520)
@ 2025-08-14 16:40 Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 1/8] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
` (7 more replies)
0 siblings, 8 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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).
This patchset depends on a TH1520 clock fix patchset at [1] and a TH1520
VOSYS reset addition patchset at [2]. The reset addition patchset is
just applied to reset/next, so it might appear in linux-next/master or
not.
[1] https://lore.kernel.org/linux-riscv/20250813072702.2176993-1-uwu@icenowy.me/
[2] https://lore.kernel.org/linux-riscv/20250813081716.2181843-1-uwu@icenowy.me/
Icenowy Zheng (8):
dt-bindings: vendor-prefixes: add verisilicon
dt-bindings: display: add versilicon,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 verislicon DC driver
.../display/bridge/thead,th1520-dw-hdmi.yaml | 120 +++++++
.../bindings/display/verisilicon,dc.yaml | 127 +++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
MAINTAINERS | 8 +
.../boot/dts/thead/th1520-lichee-pi-4a.dts | 25 ++
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 | 170 +++++++++
drivers/gpu/drm/verisilicon/Kconfig | 15 +
drivers/gpu/drm/verisilicon/Makefile | 5 +
drivers/gpu/drm/verisilicon/vs_bridge.c | 330 ++++++++++++++++++
drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
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 | 233 +++++++++++++
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 | 166 +++++++++
.../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
30 files changed, 2352 insertions(+)
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.50.1
^ permalink raw reply [flat|nested] 42+ messages in thread
* [RFC PATCH 1/8] dt-bindings: vendor-prefixes: add verisilicon
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc Icenowy Zheng
` (6 subsequent siblings)
7 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
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 77160cd47f540..215c6b71b9717 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -1654,6 +1654,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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 1/8] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-14 22:04 ` Rob Herring
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
` (5 subsequent siblings)
7 siblings, 2 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
.../bindings/display/verisilicon,dc.yaml | 127 ++++++++++++++++++
1 file changed, 127 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..2f71a811786aa
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
@@ -0,0 +1,127 @@
+# 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:
+ const: verisilicon,dc
+
+ 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
+ minItems: 4
+
+ clock-names:
+ items:
+ - const: core
+ - const: axi
+ - const: ahb
+ - const: pix0
+ - const: pix1
+ minItems: 4
+
+ 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 and used. Follow the same endpoint addressing
+ rule with the first port.
+
+ required:
+ - port@0
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - ports
+
+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 = "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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 1/8] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-15 9:05 ` Philipp Zabel
` (4 more replies)
2025-08-14 16:40 ` [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
` (4 subsequent siblings)
7 siblings, 5 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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 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>
---
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 | 330 ++++++++++++++++++
drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
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 | 233 +++++++++++++
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 | 166 +++++++++
.../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
source "drivers/gpu/drm/imagination/Kconfig"
+source "drivers/gpu/drm/verisilicon/Kconfig"
+
config DRM_HYPERV
tristate "DRM Support for Hyper-V synthetic video device"
depends on DRM && PCI && HYPERV
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 4dafbdc8f86ac..32ed4cf9df1bd 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -231,6 +231,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..c8caf31fac7d6
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
@@ -0,0 +1,330 @@
+// 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_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
+ { 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);
+ struct drm_connector *conn = conn_state->connector;
+ u32 *output_fmts;
+ unsigned int i;
+
+ if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
+ *num_output_fmts = 1;
+ 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) {
+ if (conn->display_info.num_bus_formats &&
+ conn->display_info.bus_formats)
+ output_fmts[0] = conn->display_info.bus_formats[0];
+ else
+ output_fmts[0] = 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)
+ break;
+
+ return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
+}
+
+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);
+ 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;
+ }
+ WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
+ 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;
+ }
+
+ 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;
+
+ intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
+ output);
+ if (intf == -ENODEV) {
+ dev_info(drm_dev->dev, "Skipping output %u\n", output);
+ return NULL;
+ }
+
+ bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL);
+ if (!bridge)
+ return ERR_PTR(-ENOMEM);
+
+ bridge->crtc = crtc;
+ bridge->intf = intf;
+ bridge->base.funcs = &vs_bridge_funcs;
+
+ next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
+ output, intf);
+ if (IS_ERR(next)) {
+ ret = PTR_ERR(next);
+ goto err_free_bridge;
+ }
+
+ bridge->next = next;
+
+ ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
+ (intf == VSDC_OUTPUT_INTERFACE_DPI) ?
+ DRM_MODE_ENCODER_DPI :
+ DRM_MODE_ENCODER_NONE);
+ if (ret) {
+ dev_err(drm_dev->dev,
+ "Cannot initialize encoder for output %u\n", output);
+ goto err_free_bridge;
+ }
+
+ 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) {
+ dev_err(drm_dev->dev,
+ "Cannot attach bridge for output %u\n", output);
+ goto err_cleanup_encoder;
+ }
+
+ bridge->conn = drm_bridge_connector_init(drm_dev, &bridge->enc);
+ if (IS_ERR(bridge->conn)) {
+ dev_err(drm_dev->dev,
+ "Cannot create connector for output %u\n", output);
+ ret = PTR_ERR(bridge->conn);
+ goto err_cleanup_encoder;
+ }
+ drm_connector_attach_encoder(bridge->conn, &bridge->enc);
+
+ return bridge;
+
+err_cleanup_encoder:
+ drm_encoder_cleanup(&bridge->enc);
+err_free_bridge:
+ devm_kfree(drm_dev->dev, bridge);
+
+ return ERR_PTR(ret);
+}
diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
new file mode 100644
index 0000000000000..4a8a9eeb739f2
--- /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..d1c91dd1354b4
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
@@ -0,0 +1,47 @@
+/* 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_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..46c4191b82f49
--- /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 "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,
+ .destroy = drm_crtc_cleanup,
+ .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 = devm_kzalloc(drm_dev->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)) {
+ dev_err(drm_dev->dev, "Couldn't create the primary plane\n");
+ return ERR_PTR(PTR_ERR(primary));
+ }
+
+ ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
+ primary,
+ NULL,
+ &vs_crtc_funcs,
+ NULL);
+ if (ret) {
+ dev_err(drm_dev->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..98384559568c4
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_dc.c
@@ -0,0 +1,233 @@
+// 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,
+ .cache_type = REGCACHE_NONE,
+};
+
+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[0].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(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(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(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;
+ }
+
+ ret = clk_prepare_enable(dc->core_clk);
+ if (ret) {
+ dev_err(dev, "can't enable core clock\n");
+ goto err_rst_assert;
+ }
+
+ ret = clk_prepare_enable(dc->axi_clk);
+ if (ret) {
+ dev_err(dev, "can't enable axi clock\n");
+ goto err_core_clk_disable;
+ }
+
+ ret = clk_prepare_enable(dc->ahb_clk);
+ if (ret) {
+ dev_err(dev, "can't enable ahb clock\n");
+ goto err_axi_clk_disable;
+ }
+
+ regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(regs)) {
+ dev_err(dev, "can't map registers");
+ ret = PTR_ERR(regs);
+ goto err_ahb_clk_disable;
+ }
+
+ dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
+ if (IS_ERR(dc->regs)) {
+ ret = PTR_ERR(dc->regs);
+ goto err_ahb_clk_disable;
+ }
+
+ ret = vs_fill_chip_identity(dc->regs, &dc->identity);
+ if (ret)
+ goto err_ahb_clk_disable;
+
+ 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_ahb_clk_disable;
+ }
+
+ 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_ahb_clk_disable;
+ }
+
+ dev_set_drvdata(dev, dc);
+
+ ret = vs_drm_initialize(dc, pdev);
+ if (ret)
+ goto err_ahb_clk_disable;
+
+ return 0;
+
+err_ahb_clk_disable:
+ clk_disable_unprepare(dc->ahb_clk);
+err_axi_clk_disable:
+ clk_disable_unprepare(dc->axi_clk);
+err_core_clk_disable:
+ clk_disable_unprepare(dc->core_clk);
+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);
+
+ clk_disable_unprepare(dc->ahb_clk);
+ clk_disable_unprepare(dc->axi_clk);
+ clk_disable_unprepare(dc->core_clk);
+ 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..25d6e01cc8b71
--- /dev/null
+++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
@@ -0,0 +1,166 @@
+// 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_existing_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;
+
+ DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
+
+ vcrtc = drm_crtc_to_vs_crtc(crtc);
+ output = vcrtc->id;
+ dc = vcrtc->dc;
+
+ 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,
+ .destroy = drm_plane_cleanup,
+ .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;
+ int ret;
+
+ plane = devm_kzalloc(drm_dev->dev, sizeof(*plane), GFP_KERNEL);
+ if (!plane)
+ return ERR_PTR(-ENOMEM);
+
+ ret = drm_universal_plane_init(drm_dev, plane, 0,
+ &vs_primary_plane_funcs,
+ dc->identity.formats->array,
+ dc->identity.formats->num,
+ NULL,
+ DRM_PLANE_TYPE_PRIMARY,
+ NULL);
+
+ if (ret) {
+ devm_kfree(drm_dev->dev, plane);
+ return ERR_PTR(ret);
+ }
+
+ 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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
` (2 preceding siblings ...)
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-15 9:13 ` Krzysztof Kozlowski
2025-08-14 16:40 ` [RFC PATCH 5/8] drm/bridge: add a driver for T-Head " Icenowy Zheng
` (3 subsequent siblings)
7 siblings, 2 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
.../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..3d2b502b08eb1
--- /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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
` (3 preceding siblings ...)
2025-08-14 16:40 ` [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-16 16:24 ` Dmitry Baryshkov
2025-08-14 16:40 ` [RFC PATCH 6/8] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
` (2 subsequent siblings)
7 siblings, 1 reply; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
MAINTAINERS | 1 +
drivers/gpu/drm/bridge/Kconfig | 10 ++
drivers/gpu/drm/bridge/Makefile | 1 +
drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170 ++++++++++++++++++++++++
4 files changed, 182 insertions(+)
create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
diff --git a/MAINTAINERS b/MAINTAINERS
index fe168477caa45..eb84e36ded6d5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
--- a/drivers/gpu/drm/bridge/Kconfig
+++ b/drivers/gpu/drm/bridge/Kconfig
@@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
--- a/drivers/gpu/drm/bridge/Makefile
+++ b/drivers/gpu/drm/bridge/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_SII9234) += sii9234.o
obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
--- /dev/null
+++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
@@ -0,0 +1,170 @@
+// 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 },
+ { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }
+};
+
+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 int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
+ unsigned long mpixelclock)
+{
+ const struct th1520_hdmi_phy_params *params = th1520_hdmi_phy_params;
+
+ for (; params->mpixelclock != ~0UL; ++params) {
+ if (mpixelclock <= params->mpixelclock)
+ break;
+ }
+
+ if (params->mpixelclock == ~0UL)
+ return -EINVAL;
+
+ 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);
+
+ return 0;
+}
+
+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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 6/8] riscv: dts: thead: add DPU and HDMI device tree nodes
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
` (4 preceding siblings ...)
2025-08-14 16:40 ` [RFC PATCH 5/8] drm/bridge: add a driver for T-Head " Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 7/8] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 8/8] MAINTAINERS: assign myself as maintainer for verislicon DC driver Icenowy Zheng
7 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
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 42724bf7e90e0..13a6de742b9f7 100644
--- a/arch/riscv/boot/dts/thead/th1520.dtsi
+++ b/arch/riscv/boot/dts/thead/th1520.dtsi
@@ -513,6 +513,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 = "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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 7/8] riscv: dts: thead: lichee-pi-4a: enable HDMI
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
` (5 preceding siblings ...)
2025-08-14 16:40 ` [RFC PATCH 6/8] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 8/8] MAINTAINERS: assign myself as maintainer for verislicon DC driver Icenowy Zheng
7 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
.../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 4020c727f09e8..3e99f905dc316 100644
--- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
+++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts
@@ -28,6 +28,17 @@ aliases {
chosen {
stdout-path = "serial0:115200n8";
};
+
+ hdmi-connector {
+ compatible = "hdmi-connector";
+ type = "a";
+
+ port {
+ hdmi_con_in: endpoint {
+ remote-endpoint = <&hdmi_out_con>;
+ };
+ };
+ };
};
&padctrl0_apsys {
@@ -54,6 +65,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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* [RFC PATCH 8/8] MAINTAINERS: assign myself as maintainer for verislicon DC driver
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
` (6 preceding siblings ...)
2025-08-14 16:40 ` [RFC PATCH 7/8] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
@ 2025-08-14 16:40 ` Icenowy Zheng
7 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-14 16:40 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
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>
---
MAINTAINERS | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index eb84e36ded6d5..8c604de979680 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -8385,6 +8385,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 <uwu@icenowy.me>
+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.50.1
^ permalink raw reply related [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-14 16:40 ` [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc Icenowy Zheng
@ 2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-14 22:04 ` Rob Herring
1 sibling, 0 replies; 42+ messages in thread
From: Rob Herring (Arm) @ 2025-08-14 19:21 UTC (permalink / raw)
To: Icenowy Zheng
Cc: Guo Ren, David Airlie, Jernej Skrabec, devicetree, Philipp Zabel,
Thomas Zimmermann, Laurent Pinchart, Krzysztof Kozlowski, Fu Wei,
Andrzej Hajda, Yao Zi, Maxime Ripard, Han Gao, Neil Armstrong,
linux-kernel, Conor Dooley, Jonas Karlman, Michal Wilczynski,
linux-riscv, Maarten Lankhorst, Drew Fustini, Heiko Stuebner,
dri-devel, Simona Vetter, Robert Foss
On Fri, 15 Aug 2025 00:40:42 +0800, Icenowy Zheng wrote:
> 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>
> ---
> .../bindings/display/verisilicon,dc.yaml | 127 ++++++++++++++++++
> 1 file changed, 127 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/display/verisilicon,dc.yaml
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
Lexical error: Documentation/devicetree/bindings/display/verisilicon,dc.example.dts:41.28-52 Unexpected 'TH1520_RESET_ID_DPU_CORE'
Lexical error: Documentation/devicetree/bindings/display/verisilicon,dc.example.dts:42.22-45 Unexpected 'TH1520_RESET_ID_DPU_AXI'
Lexical error: Documentation/devicetree/bindings/display/verisilicon,dc.example.dts:43.22-45 Unexpected 'TH1520_RESET_ID_DPU_AHB'
FATAL ERROR: Syntax error parsing input tree
make[2]: *** [scripts/Makefile.dtbs:132: Documentation/devicetree/bindings/display/verisilicon,dc.example.dtb] Error 1
make[2]: *** Waiting for unfinished jobs....
make[1]: *** [/builds/robherring/dt-review-ci/linux/Makefile:1527: dt_binding_check] Error 2
make: *** [Makefile:248: __sub-make] Error 2
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250814164048.2336043-3-uwu@icenowy.me
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller
2025-08-14 16:40 ` [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
@ 2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-15 9:13 ` Krzysztof Kozlowski
1 sibling, 0 replies; 42+ messages in thread
From: Rob Herring (Arm) @ 2025-08-14 19:21 UTC (permalink / raw)
To: Icenowy Zheng
Cc: Krzysztof Kozlowski, David Airlie, Han Gao, Maxime Ripard,
Michal Wilczynski, Philipp Zabel, linux-kernel, Simona Vetter,
Fu Wei, Laurent Pinchart, Robert Foss, dri-devel, devicetree,
Jernej Skrabec, Conor Dooley, Jonas Karlman, Guo Ren,
Neil Armstrong, Maarten Lankhorst, Andrzej Hajda, Heiko Stuebner,
Drew Fustini, linux-riscv, Thomas Zimmermann, Yao Zi
On Fri, 15 Aug 2025 00:40:44 +0800, Icenowy Zheng wrote:
> 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>
> ---
> .../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
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
doc reference errors (make refcheckdocs):
See https://patchwork.ozlabs.org/project/devicetree-bindings/patch/20250814164048.2336043-5-uwu@icenowy.me
The base for the series is generally the latest rc1. A different dependency
should be noted in *this* patch.
If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:
pip3 install dtschema --upgrade
Please check and re-submit after running the above command yourself. Note
that DT_SCHEMA_FILES can be set to your schema file to speed up checking
your schema. However, it must be unset to test all examples with your schema.
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-14 16:40 ` [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
@ 2025-08-14 22:04 ` Rob Herring
2025-08-15 3:42 ` Icenowy Zheng
2025-08-15 9:09 ` Krzysztof Kozlowski
1 sibling, 2 replies; 42+ messages in thread
From: Rob Herring @ 2025-08-14 22:04 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 Fri, Aug 15, 2025 at 12:40:42AM +0800, Icenowy Zheng wrote:
> 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>
> ---
> .../bindings/display/verisilicon,dc.yaml | 127 ++++++++++++++++++
> 1 file changed, 127 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..2f71a811786aa
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> @@ -0,0 +1,127 @@
> +# 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:
> + const: verisilicon,dc
If the clocks or resets varies by platform, then you need an SoC
specific compatible still. If these clocks/resets are straight from the
RTL and any other number of clocks/resets is wrong, then we can stick
with just this compatible.
> +
> + 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
> + minItems: 4
Generally we put this before 'items'.
> +
> + clock-names:
> + items:
> + - const: core
> + - const: axi
> + - const: ahb
> + - const: pix0
> + - const: pix1
> + minItems: 4
> +
> + 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 and used. Follow the same endpoint addressing
> + rule with the first port.
> +
> + required:
> + - port@0
> +
> +required:
> + - compatible
> + - reg
> + - interrupts
> + - clocks
> + - clock-names
> + - ports
> +
> +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 = "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.50.1
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-14 22:04 ` Rob Herring
@ 2025-08-15 3:42 ` Icenowy Zheng
2025-08-15 9:09 ` Krzysztof Kozlowski
1 sibling, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-15 3:42 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
在 2025-08-14星期四的 17:04 -0500,Rob Herring写道:
> On Fri, Aug 15, 2025 at 12:40:42AM +0800, Icenowy Zheng wrote:
> > 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>
> > ---
> > .../bindings/display/verisilicon,dc.yaml | 127
> > ++++++++++++++++++
> > 1 file changed, 127 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..2f71a811786aa
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/display/verisilicon,dc.yaml
> > @@ -0,0 +1,127 @@
> > +# 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:
> > + const: verisilicon,dc
>
> If the clocks or resets varies by platform, then you need an SoC
> specific compatible still. If these clocks/resets are straight from
> the
> RTL and any other number of clocks/resets is wrong, then we can stick
> with just this compatible.
I deduced this 5 clocks pattern based on block diagrams on two SoCs's
manual, see [1] page 528 and [2] page 7 (page 1 shown on the footer).
Well, ironically, neither of BSP device tree of these two SoCs ([3]
[4]) list 5 clocks in the dc8200 device tree node, but the extra clocks
mostly look nonsense (even things like JH7110_SYSCLK_NOC_BUS_DISP_AXI
appeared in [4]).
[1]
https://doc-en.rvspace.org/JH7110/PDF/JH7110_TRM_StarFive_Preliminary_V2.pdf
[2]
https://git.beagleboard.org/beaglev-ahead/beaglev-ahead/-/raw/main/docs/TH1520%20Video%20Output%20User%20Manual.pdf
[3]
https://github.com/revyos/th1520-linux-kernel/blob/th1520-lts/arch/riscv/boot/dts/thead/th1520.dtsi#L1702
[4]
https://github.com/starfive-tech/linux/blob/JH7110_VisionFive2_6.12.y_devel/arch/riscv/boot/dts/starfive/jh7110.dtsi#L1576
>
> > +
> > + 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
> > + minItems: 4
>
> Generally we put this before 'items'.
Sounds reasonable, although I might add explicit maxItems: 5 if put
before items.
>
> > +
> > + clock-names:
> > + items:
> > + - const: core
> > + - const: axi
> > + - const: ahb
> > + - const: pix0
> > + - const: pix1
> > + minItems: 4
> > +
> > + 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 and used. Follow the same endpoint addressing
> > + rule with the first port.
> > +
> > + required:
> > + - port@0
> > +
> > +required:
> > + - compatible
> > + - reg
> > + - interrupts
> > + - clocks
> > + - clock-names
> > + - ports
> > +
> > +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 = "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.50.1
> >
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
@ 2025-08-15 9:05 ` Philipp Zabel
2025-08-15 9:07 ` Icenowy Zheng
2025-08-16 10:04 ` Yao Zi
` (3 subsequent siblings)
4 siblings, 1 reply; 42+ messages in thread
From: Philipp Zabel @ 2025-08-15 9:05 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,
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
On Fr, 2025-08-15 at 00:40 +0800, Icenowy Zheng wrote:
> 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>
> ---
> 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 | 330 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> 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 | 233 +++++++++++++
> 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 | 166 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> 21 files changed, 1819 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/verisilicon/vs_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
> new file mode 100644
> index 0000000000000..98384559568c4
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> @@ -0,0 +1,233 @@
[...]
> +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[0].id = "ahb";
I assume this should be:
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");
Consider using dev_err_probe().
> + return ret;
> + }
[...]
regards
Philipp
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-15 9:05 ` Philipp Zabel
@ 2025-08-15 9:07 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-15 9:07 UTC (permalink / raw)
To: Philipp Zabel, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Drew Fustini, Guo Ren, Fu Wei,
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
在 2025-08-15星期五的 11:05 +0200,Philipp Zabel写道:
> On Fr, 2025-08-15 at 00:40 +0800, Icenowy Zheng wrote:
> > 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>
> > ---
> > 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 | 330
> > ++++++++++++++++++
> > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > 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 | 233 +++++++++++++
> > 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 | 166 +++++++++
> > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > 21 files changed, 1819 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/verisilicon/vs_dc.c
> > b/drivers/gpu/drm/verisilicon/vs_dc.c
> > new file mode 100644
> > index 0000000000000..98384559568c4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > @@ -0,0 +1,233 @@
> [...]
> > +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[0].id = "ahb";
>
> I assume this should be:
>
> dc->rsts[2].id = "ahb";
Sure.
>
> > +
> > + 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");
>
> Consider using dev_err_probe().
Sounds reasonable.
Thanks,
Icenowy
>
> > + return ret;
> > + }
> [...]
>
> regards
> Philipp
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-14 22:04 ` Rob Herring
2025-08-15 3:42 ` Icenowy Zheng
@ 2025-08-15 9:09 ` Krzysztof Kozlowski
2025-08-15 9:53 ` Icenowy Zheng
1 sibling, 1 reply; 42+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-15 9:09 UTC (permalink / raw)
To: Rob Herring, 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 15/08/2025 00:04, Rob Herring wrote:
>> +
>> +maintainers:
>> + - Icenowy Zheng <uwu@icenowy.me>
>> +
>> +properties:
>> + $nodename:
>> + pattern: "^display@[0-9a-f]+$"
>> +
>> + compatible:
>> + const: verisilicon,dc
>
> If the clocks or resets varies by platform, then you need an SoC
> specific compatible still. If these clocks/resets are straight from the
> RTL and any other number of clocks/resets is wrong, then we can stick
> with just this compatible.
Shouldn't we have here always SoC compatible? Can it be ever used alone,
outside of given SoC?
I could imagine now:
items:
- {}
- const: verisilicon,dc
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller
2025-08-14 16:40 ` [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
@ 2025-08-15 9:13 ` Krzysztof Kozlowski
1 sibling, 0 replies; 42+ messages in thread
From: Krzysztof Kozlowski @ 2025-08-15 9:13 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
On 14/08/2025 18:40, Icenowy Zheng wrote:
> +
> +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>,
Please align this with earlier <
Rest looks good so:
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org>
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-15 9:09 ` Krzysztof Kozlowski
@ 2025-08-15 9:53 ` Icenowy Zheng
2025-08-15 16:55 ` Icenowy Zheng
0 siblings, 1 reply; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-15 9:53 UTC (permalink / raw)
To: Krzysztof Kozlowski, 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
在 2025-08-15星期五的 11:09 +0200,Krzysztof Kozlowski写道:
> On 15/08/2025 00:04, Rob Herring wrote:
> > > +
> > > +maintainers:
> > > + - Icenowy Zheng <uwu@icenowy.me>
> > > +
> > > +properties:
> > > + $nodename:
> > > + pattern: "^display@[0-9a-f]+$"
> > > +
> > > + compatible:
> > > + const: verisilicon,dc
> >
> > If the clocks or resets varies by platform, then you need an SoC
> > specific compatible still. If these clocks/resets are straight from
> > the
> > RTL and any other number of clocks/resets is wrong, then we can
> > stick
> > with just this compatible.
>
> Shouldn't we have here always SoC compatible? Can it be ever used
> alone,
> outside of given SoC?
>
> I could imagine now:
>
> items:
> - {}
> - const: verisilicon,dc
I followed the `vivante,gc` situation here, because the registers
before 0x1400 (where real display-related things start) seems to follow
the same scheme with GC-series GPUs, including the identification
registers.
>
>
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc
2025-08-15 9:53 ` Icenowy Zheng
@ 2025-08-15 16:55 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-15 16:55 UTC (permalink / raw)
To: Krzysztof Kozlowski, 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
在 2025-08-15星期五的 17:53 +0800,Icenowy Zheng写道:
> 在 2025-08-15星期五的 11:09 +0200,Krzysztof Kozlowski写道:
> > On 15/08/2025 00:04, Rob Herring wrote:
> > > > +
> > > > +maintainers:
> > > > + - Icenowy Zheng <uwu@icenowy.me>
> > > > +
> > > > +properties:
> > > > + $nodename:
> > > > + pattern: "^display@[0-9a-f]+$"
> > > > +
> > > > + compatible:
> > > > + const: verisilicon,dc
> > >
> > > If the clocks or resets varies by platform, then you need an SoC
> > > specific compatible still. If these clocks/resets are straight
> > > from
> > > the
> > > RTL and any other number of clocks/resets is wrong, then we can
> > > stick
> > > with just this compatible.
> >
> > Shouldn't we have here always SoC compatible? Can it be ever used
> > alone,
> > outside of given SoC?
> >
> > I could imagine now:
> >
> > items:
> > - {}
> > - const: verisilicon,dc
>
> I followed the `vivante,gc` situation here, because the registers
> before 0x1400 (where real display-related things start) seems to
> follow
> the same scheme with GC-series GPUs, including the identification
> registers.
An example here: the customer id (0x0030) register value read out on T-
Head TH1520 is 0x30a, but on StarFive JH6110 it's 0x30e instead.
(Both are DC8200 rev 5720, so the 0x0020 reg is 0x8200 and 0x0024 reg
is 0x5720.)
>
> >
> >
> > Best regards,
> > Krzysztof
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
2025-08-15 9:05 ` Philipp Zabel
@ 2025-08-16 10:04 ` Yao Zi
2025-08-16 16:09 ` Icenowy Zheng
2025-08-16 16:18 ` Dmitry Baryshkov
` (2 subsequent siblings)
4 siblings, 1 reply; 42+ messages in thread
From: Yao Zi @ 2025-08-16 10:04 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, dri-devel, devicetree, linux-kernel, linux-riscv
On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> 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>
Thank you for the great work!
> ---
> 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 | 330 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> 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 | 233 +++++++++++++
> 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 | 166 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> 21 files changed, 1819 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/verisilicon/vs_bridge.c b/drivers/gpu/drm/verisilicon/vs_bridge.c
> new file mode 100644
> index 0000000000000..c8caf31fac7d6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
...
> +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)
> + break;
> +
> + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
You could simplify the return statement by returning early in the loop
if out_fmt matches.
> +}
> +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;
> + }
remote is leaked here, it should be released with of_node_put IMHO.
> + 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;
...
> + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL);
> + if (!bridge)
> + return ERR_PTR(-ENOMEM);
...
> +err_cleanup_encoder:
> + drm_encoder_cleanup(&bridge->enc);
> +err_free_bridge:
> + devm_kfree(drm_dev->dev, bridge);
Though is technically correct, this devm_kfree() is unnecessary.
Resources registered with devres are automatically released when probing
fails[1][2].
> + return ERR_PTR(ret);
> +}
...
> diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c b/drivers/gpu/drm/verisilicon/vs_crtc.c
> new file mode 100644
> index 0000000000000..46c4191b82f49
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
> @@ -0,0 +1,217 @@
...
> +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;
Some round_rate implementations may not return zero or error on an
unsupported clock rate, instead they simply return a value differing a
lot from the requested rate. Thus I think you should also check whether
the resulted rate is acceptable as well, which applies for
vs_crtc_mode_fixup() as well.
> + 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;
> +}
...
> +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 = devm_kzalloc(drm_dev->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)) {
> + dev_err(drm_dev->dev, "Couldn't create the primary plane\n");
> + return ERR_PTR(PTR_ERR(primary));
> + }
Suggest dev_err_probe which handles deferred probing as well.
> +
> + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> + primary,
> + NULL,
> + &vs_crtc_funcs,
> + NULL);
> + if (ret) {
> + dev_err(drm_dev->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_dc.c b/drivers/gpu/drm/verisilicon/vs_dc.c
> new file mode 100644
> index 0000000000000..98384559568c4
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> @@ -0,0 +1,233 @@
> +// 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,
> + .cache_type = REGCACHE_NONE,
I think cache_type = REGCACHE_NONE is the default, thus it should be
okay to omitted it?
> +};
...
> +static int vs_dc_probe(struct platform_device *pdev)
> +{
...
> + dc->core_clk = devm_clk_get(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(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(dev, "ahb");
> + if (IS_ERR(dc->ahb_clk)) {
> + dev_err(dev, "can't get ahb clock\n");
> + return PTR_ERR(dc->ahb_clk);
> + }
I think devm_clk_get_enabled() will help here.
> + 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]);
> + }
> + }
...
> +err_ahb_clk_disable:
> + clk_disable_unprepare(dc->ahb_clk);
> +err_axi_clk_disable:
> + clk_disable_unprepare(dc->axi_clk);
> +err_core_clk_disable:
> + clk_disable_unprepare(dc->core_clk);
And you could get avoid these clk_disable_unprepare(). Also the ones in
the remove callback.
> +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);
> +
> + clk_disable_unprepare(dc->ahb_clk);
> + clk_disable_unprepare(dc->axi_clk);
> + clk_disable_unprepare(dc->core_clk);
> + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> +}
...
> 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..25d6e01cc8b71
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> @@ -0,0 +1,166 @@
> +// 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_existing_crtc_state(state,
> + crtc);
I think these arguments could be merged into a single line.
> + 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);
> +}
...
> +struct drm_plane *vs_primary_plane_init(struct drm_device *drm_dev, struct vs_dc *dc)
> +{
> + struct drm_plane *plane;
> + int ret;
> +
> + plane = devm_kzalloc(drm_dev->dev, sizeof(*plane), GFP_KERNEL);
> + if (!plane)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = drm_universal_plane_init(drm_dev, plane, 0,
> + &vs_primary_plane_funcs,
> + dc->identity.formats->array,
> + dc->identity.formats->num,
> + NULL,
> + DRM_PLANE_TYPE_PRIMARY,
> + NULL);
> +
> + if (ret) {
> + devm_kfree(drm_dev->dev, plane);
Similar to vs_bridge_init's case, this devm_kfree() isn't unnecessary,
either.
> + return ERR_PTR(ret);
> + }
> +
> + drm_plane_helper_add(plane, &vs_primary_plane_helper_funcs);
> +
> + return plane;
> +}
Best regards,
Yao Zi
[1]: https://elixir.bootlin.com/linux/v6.16/source/drivers/base/dd.c#L723
[2]: https://elixir.bootlin.com/linux/v6.16/source/drivers/base/dd.c#L549
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 10:04 ` Yao Zi
@ 2025-08-16 16:09 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 16:09 UTC (permalink / raw)
To: Yao Zi, 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, dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-16星期六的 10:04 +0000,Yao Zi写道:
> On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > 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>
>
> Thank you for the great work!
>
> > ---
> > 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 | 330
> > ++++++++++++++++++
> > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > 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 | 233 +++++++++++++
> > 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 | 166 +++++++++
> > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > 21 files changed, 1819 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/verisilicon/vs_bridge.c
> > b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > new file mode 100644
> > index 0000000000000..c8caf31fac7d6
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
>
> ...
>
> > +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)
> > + break;
> > +
> > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
>
> You could simplify the return statement by returning early in the
> loop
> if out_fmt matches.
Thanks for this tip, this was originally coded in two different
functions, but then extracted to be such a common function.
>
> > +}
>
> > +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;
> > + }
>
> remote is leaked here, it should be released with of_node_put IMHO.
Thanks for this, will fix it.
>
> > + 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;
>
> ...
>
> > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > GFP_KERNEL);
> > + if (!bridge)
> > + return ERR_PTR(-ENOMEM);
>
> ...
>
> > +err_cleanup_encoder:
> > + drm_encoder_cleanup(&bridge->enc);
> > +err_free_bridge:
> > + devm_kfree(drm_dev->dev, bridge);
>
> Though is technically correct, this devm_kfree() is unnecessary.
> Resources registered with devres are automatically released when
> probing
> fails[1][2].
>
> > + return ERR_PTR(ret);
> > +}
>
> ...
>
> > diff --git a/drivers/gpu/drm/verisilicon/vs_crtc.c
> > b/drivers/gpu/drm/verisilicon/vs_crtc.c
> > new file mode 100644
> > index 0000000000000..46c4191b82f49
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_crtc.c
> > @@ -0,0 +1,217 @@
>
> ...
>
> > +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;
>
> Some round_rate implementations may not return zero or error on an
> unsupported clock rate, instead they simply return a value differing
> a
> lot from the requested rate. Thus I think you should also check
> whether
> the resulted rate is acceptable as well, which applies for
> vs_crtc_mode_fixup() as well.
Well I don't know how to decide a round rate is good (sometimes the
clock might be a little off but setting it is still better than
rejecting the output device), so I think this should be considered a
further problem.
>
> > + 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;
> > +}
>
> ...
>
> > +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 = devm_kzalloc(drm_dev->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)) {
> > + dev_err(drm_dev->dev, "Couldn't create the primary
> > plane\n");
> > + return ERR_PTR(PTR_ERR(primary));
> > + }
>
> Suggest dev_err_probe which handles deferred probing as well.
Well I don't think CRTC/Plane initialization will lead to deferred
probing.
>
> > +
> > + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> > + primary,
> > + NULL,
> > + &vs_crtc_funcs,
> > + NULL);
> > + if (ret) {
> > + dev_err(drm_dev->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_dc.c
> > b/drivers/gpu/drm/verisilicon/vs_dc.c
> > new file mode 100644
> > index 0000000000000..98384559568c4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > @@ -0,0 +1,233 @@
> > +// 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,
> > + .cache_type = REGCACHE_NONE,
>
> I think cache_type = REGCACHE_NONE is the default, thus it should be
> okay to omitted it?
Sounds right. Thanks.
>
> > +};
>
> ...
>
> > +static int vs_dc_probe(struct platform_device *pdev)
> > +{
>
> ...
>
> > + dc->core_clk = devm_clk_get(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(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(dev, "ahb");
> > + if (IS_ERR(dc->ahb_clk)) {
> > + dev_err(dev, "can't get ahb clock\n");
> > + return PTR_ERR(dc->ahb_clk);
> > + }
>
> I think devm_clk_get_enabled() will help here.
Thanks for the tip, I don't know why I forgot it here (because I used
devm_clk_get_enabled in th1520-dw-hdmi driver).
>
> > + 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]);
> > + }
> > + }
>
> ...
>
> > +err_ahb_clk_disable:
> > + clk_disable_unprepare(dc->ahb_clk);
> > +err_axi_clk_disable:
> > + clk_disable_unprepare(dc->axi_clk);
> > +err_core_clk_disable:
> > + clk_disable_unprepare(dc->core_clk);
>
> And you could get avoid these clk_disable_unprepare(). Also the ones
> in
> the remove callback.
>
> > +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);
> > +
> > + clk_disable_unprepare(dc->ahb_clk);
> > + clk_disable_unprepare(dc->axi_clk);
> > + clk_disable_unprepare(dc->core_clk);
> > + reset_control_bulk_assert(VSDC_RESET_COUNT, dc->rsts);
> > +}
>
> ...
>
> > 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..25d6e01cc8b71
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> > @@ -0,0 +1,166 @@
> > +// 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_existing_crtc_state(state,
> > + crtc);
>
> I think these arguments could be merged into a single line.
>
> > + 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);
> > +}
>
> ...
>
> > +struct drm_plane *vs_primary_plane_init(struct drm_device
> > *drm_dev, struct vs_dc *dc)
> > +{
> > + struct drm_plane *plane;
> > + int ret;
> > +
> > + plane = devm_kzalloc(drm_dev->dev, sizeof(*plane),
> > GFP_KERNEL);
> > + if (!plane)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + ret = drm_universal_plane_init(drm_dev, plane, 0,
> > + &vs_primary_plane_funcs,
> > + dc->identity.formats->array,
> > + dc->identity.formats->num,
> > + NULL,
> > + DRM_PLANE_TYPE_PRIMARY,
> > + NULL);
> > +
> > + if (ret) {
> > + devm_kfree(drm_dev->dev, plane);
>
> Similar to vs_bridge_init's case, this devm_kfree() isn't
> unnecessary,
> either.
>
> > + return ERR_PTR(ret);
> > + }
> > +
> > + drm_plane_helper_add(plane,
> > &vs_primary_plane_helper_funcs);
> > +
> > + return plane;
> > +}
>
> Best regards,
> Yao Zi
>
> [1]:
> https://elixir.bootlin.com/linux/v6.16/source/drivers/base/dd.c#L723
> [2]:
> https://elixir.bootlin.com/linux/v6.16/source/drivers/base/dd.c#L549
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
2025-08-15 9:05 ` Philipp Zabel
2025-08-16 10:04 ` Yao Zi
@ 2025-08-16 16:18 ` Dmitry Baryshkov
2025-08-16 16:48 ` Icenowy Zheng
2025-08-17 18:39 ` Drew Fustini
2025-08-20 21:21 ` Michal Wilczynski
4 siblings, 1 reply; 42+ messages in thread
From: Dmitry Baryshkov @ 2025-08-16 16:18 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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> 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>
> ---
> 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 | 330 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> 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 | 233 +++++++++++++
> 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 | 166 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
>
> source "drivers/gpu/drm/imagination/Kconfig"
>
> +source "drivers/gpu/drm/verisilicon/Kconfig"
> +
> config DRM_HYPERV
> tristate "DRM Support for Hyper-V synthetic video device"
> depends on DRM && PCI && HYPERV
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -231,6 +231,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..c8caf31fac7d6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> @@ -0,0 +1,330 @@
> +// 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_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { 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);
> + struct drm_connector *conn = conn_state->connector;
> + u32 *output_fmts;
> + unsigned int i;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
This kind of checks looks like there should be a drm_encoder handled by
the same driver. Or maybe it's better to have two sets of funcs
structures, one for the DPI, one for DP.
> + *num_output_fmts = 1;
> + 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) {
> + if (conn->display_info.num_bus_formats &&
> + conn->display_info.bus_formats)
> + output_fmts[0] = conn->display_info.bus_formats[0];
> + else
> + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> + } else {
> + for (i = 0; i < *num_output_fmts; i++)
> + output_fmts[i] = vsdc_dp_supported_fmts[i].linux_fmt;
memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
> + }
> +
> + 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;
> + break;
> +
> + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
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;
You are saving a state value into a non-state variable. There is no
guarantee that this atomic_check() will be followed by the actual
commit. So, either you have to use a struct that extends
drm_bridge_state here or store the output_bus_fmt during
atomic_enable().
> +
> + 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);
> + 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;
> + }
> + WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
This might trigger all static checkers in the universe. It's not really
possible, since you've checked it in the atomic_check(), but...
> + 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);
This deserves a comment in the source file.
> + if (remote) {
> + ret = VSDC_OUTPUT_INTERFACE_DPI;
return here, drop else{}
> + } else {
> + remote = of_graph_get_remote_node(of_node, output,
> + VSDC_OUTPUT_INTERFACE_DP);
> + if (remote)
> + ret = VSDC_OUTPUT_INTERFACE_DP;
return
> + else
> + ret = -ENODEV;
> + }
> +
> + 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;
> +
> + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
> + output);
> + if (intf == -ENODEV) {
> + dev_info(drm_dev->dev, "Skipping output %u\n", output);
> + return NULL;
> + }
> +
> + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL);
devm_drm_bridge_alloc()
> + if (!bridge)
> + return ERR_PTR(-ENOMEM);
> +
> + bridge->crtc = crtc;
> + bridge->intf = intf;
> + bridge->base.funcs = &vs_bridge_funcs;
> +
> + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
> + output, intf);
> + if (IS_ERR(next)) {
> + ret = PTR_ERR(next);
> + goto err_free_bridge;
> + }
> +
> + bridge->next = next;
> +
> + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
Oh, so there is an encoder... Please drop drm_simple_encoder, it's
deprecated, and try moving all the ifs to the encoder funcs.
> + (intf == VSDC_OUTPUT_INTERFACE_DPI) ?
> + DRM_MODE_ENCODER_DPI :
> + DRM_MODE_ENCODER_NONE);
> + if (ret) {
> + dev_err(drm_dev->dev,
> + "Cannot initialize encoder for output %u\n", output);
> + goto err_free_bridge;
> + }
> +
> + 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) {
> + dev_err(drm_dev->dev,
> + "Cannot attach bridge for output %u\n", output);
> + goto err_cleanup_encoder;
> + }
> +
> + bridge->conn = drm_bridge_connector_init(drm_dev, &bridge->enc);
> + if (IS_ERR(bridge->conn)) {
> + dev_err(drm_dev->dev,
> + "Cannot create connector for output %u\n", output);
> + ret = PTR_ERR(bridge->conn);
> + goto err_cleanup_encoder;
> + }
> + drm_connector_attach_encoder(bridge->conn, &bridge->enc);
> +
> + return bridge;
> +
> +err_cleanup_encoder:
> + drm_encoder_cleanup(&bridge->enc);
> +err_free_bridge:
> + devm_kfree(drm_dev->dev, bridge);
> +
> + return ERR_PTR(ret);
> +}
> diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h b/drivers/gpu/drm/verisilicon/vs_bridge.h
> new file mode 100644
> index 0000000000000..4a8a9eeb739f2
> --- /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..d1c91dd1354b4
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> @@ -0,0 +1,47 @@
> +/* 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_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..46c4191b82f49
> --- /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 "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)
lowercase hex, please.
> + 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,
> + .destroy = drm_crtc_cleanup,
> + .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 = devm_kzalloc(drm_dev->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)) {
> + dev_err(drm_dev->dev, "Couldn't create the primary plane\n");
> + return ERR_PTR(PTR_ERR(primary));
> + }
> +
> + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> + primary,
> + NULL,
> + &vs_crtc_funcs,
> + NULL);
> + if (ret) {
> + dev_err(drm_dev->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..98384559568c4
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> @@ -0,0 +1,233 @@
> +// 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,
> + .cache_type = REGCACHE_NONE,
> +};
> +
> +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[0].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(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(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(dev, "ahb");
devm_clk_get_enabled() ?
> + 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;
> + }
> +
> + ret = clk_prepare_enable(dc->core_clk);
> + if (ret) {
> + dev_err(dev, "can't enable core clock\n");
> + goto err_rst_assert;
> + }
> +
> + ret = clk_prepare_enable(dc->axi_clk);
> + if (ret) {
> + dev_err(dev, "can't enable axi clock\n");
> + goto err_core_clk_disable;
> + }
> +
> + ret = clk_prepare_enable(dc->ahb_clk);
> + if (ret) {
> + dev_err(dev, "can't enable ahb clock\n");
> + goto err_axi_clk_disable;
> + }
> +
> + regs = devm_platform_ioremap_resource(pdev, 0);
> + if (IS_ERR(regs)) {
> + dev_err(dev, "can't map registers");
> + ret = PTR_ERR(regs);
> + goto err_ahb_clk_disable;
> + }
> +
> + dc->regs = devm_regmap_init_mmio(dev, regs, &vs_dc_regmap_cfg);
> + if (IS_ERR(dc->regs)) {
> + ret = PTR_ERR(dc->regs);
> + goto err_ahb_clk_disable;
> + }
> +
> + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
I'd say, this should be a part of the DT bindings.
> + if (ret)
> + goto err_ahb_clk_disable;
> +
> + 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_ahb_clk_disable;
> + }
> +
> + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> + dev_name(dev), dc);
Are we ready to handle the IRQ here?
> + if (ret) {
> + dev_err(dev, "can't request irq\n");
> + goto err_ahb_clk_disable;
> + }
> +
> + dev_set_drvdata(dev, dc);
> +
> + ret = vs_drm_initialize(dc, pdev);
> + if (ret)
> + goto err_ahb_clk_disable;
> +
> + return 0;
> +
> +err_ahb_clk_disable:
> + clk_disable_unprepare(dc->ahb_clk);
> +err_axi_clk_disable:
> + clk_disable_unprepare(dc->axi_clk);
> +err_core_clk_disable:
> + clk_disable_unprepare(dc->core_clk);
> +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);
> +
> + clk_disable_unprepare(dc->ahb_clk);
> + clk_disable_unprepare(dc->axi_clk);
> + clk_disable_unprepare(dc->core_clk);
> + 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);
I'd suggest inlining simple wrappers.
> +}
> +
> +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..25d6e01cc8b71
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> @@ -0,0 +1,166 @@
> +// 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_existing_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;
> +
> + DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
> +
> + vcrtc = drm_crtc_to_vs_crtc(crtc);
> + output = vcrtc->id;
> + dc = vcrtc->dc;
> +
> + 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,
> + .destroy = drm_plane_cleanup,
> + .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;
> + int ret;
> +
> + plane = devm_kzalloc(drm_dev->dev, sizeof(*plane), GFP_KERNEL);
> + if (!plane)
> + return ERR_PTR(-ENOMEM);
> +
> + ret = drm_universal_plane_init(drm_dev, plane, 0,
> + &vs_primary_plane_funcs,
> + dc->identity.formats->array,
> + dc->identity.formats->num,
> + NULL,
> + DRM_PLANE_TYPE_PRIMARY,
> + NULL);
> +
> + if (ret) {
> + devm_kfree(drm_dev->dev, plane);
> + return ERR_PTR(ret);
> + }
> +
> + 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.50.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-14 16:40 ` [RFC PATCH 5/8] drm/bridge: add a driver for T-Head " Icenowy Zheng
@ 2025-08-16 16:24 ` Dmitry Baryshkov
2025-08-16 17:10 ` Icenowy Zheng
0 siblings, 1 reply; 42+ messages in thread
From: Dmitry Baryshkov @ 2025-08-16 16:24 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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> 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>
> ---
> MAINTAINERS | 1 +
> drivers/gpu/drm/bridge/Kconfig | 10 ++
> drivers/gpu/drm/bridge/Makefile | 1 +
> drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170 ++++++++++++++++++++++++
> 4 files changed, 182 insertions(+)
> create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index fe168477caa45..eb84e36ded6d5 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
> --- a/drivers/gpu/drm/bridge/Kconfig
> +++ b/drivers/gpu/drm/bridge/Kconfig
> @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> --- a/drivers/gpu/drm/bridge/Makefile
> +++ b/drivers/gpu/drm/bridge/Makefile
> @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
> obj-$(CONFIG_DRM_SII902X) += sii902x.o
> obj-$(CONFIG_DRM_SII9234) += sii9234.o
> obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> --- /dev/null
> +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> @@ -0,0 +1,170 @@
> +// 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 },
> + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 }
> +};
> +
> +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;
We should rewrite DW bridge into HDMI ops. It would help us to get rid
of such functions. With it in place it will be handled by the generic
TMDS clock rate check.
> +
> + return MODE_OK;
> +}
> +
> +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void *data,
> + unsigned long mpixelclock)
> +{
> + const struct th1520_hdmi_phy_params *params = th1520_hdmi_phy_params;
> +
> + for (; params->mpixelclock != ~0UL; ++params) {
> + if (mpixelclock <= params->mpixelclock)
> + break;
for (...) {
if (mpixelclock <= params->mpixelclock)
return th1520_program_phy();
}
return -EINVAL;
> + }
> +
> + if (params->mpixelclock == ~0UL)
> + return -EINVAL;
> +
> + 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);
> +
> + return 0;
> +}
> +
> +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.50.1
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 16:18 ` Dmitry Baryshkov
@ 2025-08-16 16:48 ` Icenowy Zheng
2025-08-16 17:22 ` Icenowy Zheng
2025-08-16 17:45 ` Dmitry Baryshkov
0 siblings, 2 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 16:48 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > 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>
> > ---
> > 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 | 330
> > ++++++++++++++++++
> > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > 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 | 233 +++++++++++++
> > 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 | 166 +++++++++
> > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> >
> > source "drivers/gpu/drm/imagination/Kconfig"
> >
> > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > +
> > config DRM_HYPERV
> > tristate "DRM Support for Hyper-V synthetic video device"
> > depends on DRM && PCI && HYPERV
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -231,6 +231,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..c8caf31fac7d6
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > @@ -0,0 +1,330 @@
> > +// 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_RGB888_1X24, false,
> > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > + { 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);
> > + struct drm_connector *conn = conn_state->connector;
> > + u32 *output_fmts;
> > + unsigned int i;
> > +
> > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
>
> This kind of checks looks like there should be a drm_encoder handled
> by
> the same driver. Or maybe it's better to have two sets of funcs
> structures, one for the DPI, one for DP.
Well these functions used to be for an encoder, however I found that
encoders cannot take part in format negotiation, and at least some
source says encoder is deprecated in this situation and a first bridge
in the bridge chain is better here.
A simple encoder is created by this part of driver, but all its works
are moved to this bridge, similar to what other drivers with bridge
chaining support do.
>
> > + *num_output_fmts = 1;
> > + 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) {
> > + if (conn->display_info.num_bus_formats &&
> > + conn->display_info.bus_formats)
> > + output_fmts[0] = conn-
> > >display_info.bus_formats[0];
> > + else
> > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > + } else {
> > + for (i = 0; i < *num_output_fmts; i++)
> > + output_fmts[i] =
> > vsdc_dp_supported_fmts[i].linux_fmt;
>
> memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
parameter, so memcpy won't work here.
>
> > + }
> > +
> > + 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;
>
> > + break;
> > +
> > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
>
> 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;
>
> You are saving a state value into a non-state variable. There is no
> guarantee that this atomic_check() will be followed by the actual
> commit. So, either you have to use a struct that extends
> drm_bridge_state here or store the output_bus_fmt during
> atomic_enable().
In fact I don't want to save it -- the kernel is quirky here and this
value does not get passed into atomic_enable. I mimicked what other
drivers do. See ingenic_drm_bridge_atomic_check() in ingenic/ingenic-
drm-drv.c and meson_encoder_hdmi_atomic_check() in
meson/meson_encoder_hdmi.c .
>
> > +
> > + 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);
> > + 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;
> > + }
> > + WARN_ON_ONCE(i ==
> > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
>
> This might trigger all static checkers in the universe. It's not
> really
> possible, since you've checked it in the atomic_check(), but...
Sigh I don't know how to properly describe it...
I can only say something really bad happens if the previous
WARN_ON_ONCE is triggered.
>
> > + 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);
>
> This deserves a comment in the source file.
>
> > + if (remote) {
> > + ret = VSDC_OUTPUT_INTERFACE_DPI;
>
> return here, drop else{}
Well a of_node_put() is missing before the final return, and Yao Zi
noted me of it.
>
> > + } else {
> > + remote = of_graph_get_remote_node(of_node, output,
> > +
> > VSDC_OUTPUT_INTERFACE_DP);
> > + if (remote)
> > + ret = VSDC_OUTPUT_INTERFACE_DP;
>
> return
>
> > + else
> > + ret = -ENODEV;
> > + }
> > +
> > + 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;
> > +
> > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > >of_node,
> > + output);
> > + if (intf == -ENODEV) {
> > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > output);
> > + return NULL;
> > + }
> > +
> > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > GFP_KERNEL);
>
> devm_drm_bridge_alloc()
>
> > + if (!bridge)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + bridge->crtc = crtc;
> > + bridge->intf = intf;
> > + bridge->base.funcs = &vs_bridge_funcs;
> > +
> > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev-
> > >of_node,
> > + output, intf);
> > + if (IS_ERR(next)) {
> > + ret = PTR_ERR(next);
> > + goto err_free_bridge;
> > + }
> > +
> > + bridge->next = next;
> > +
> > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
>
> Oh, so there is an encoder... Please drop drm_simple_encoder, it's
> deprecated, and try moving all the ifs to the encoder funcs.
Ah? Is it really deprecated? I can find no source of this deprecation.
In addition, I think many drivers here are using a bridge as a "better
encoder" because of the restriction of current encoder implementation,
and I am doing the same thing. Either encoder functionality should be
improved to on par with bridge, or such dummy encoders with a bridge
should exist, and some helper for creating them should exist. It might
be not drm_simple_encoder_init (because I can understand the
deprecation of other parts of the simple-kms routines, although I see
no formal documentation mentioning it's deprecated, maybe I missed some
newspaper?), but it should exist.
>
> > + (intf ==
> > VSDC_OUTPUT_INTERFACE_DPI) ?
> > + DRM_MODE_ENCODER_DPI :
> > + DRM_MODE_ENCODER_NONE);
> > + if (ret) {
> > + dev_err(drm_dev->dev,
> > + "Cannot initialize encoder for output
> > %u\n", output);
> > + goto err_free_bridge;
> > + }
> > +
> > + 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) {
> > + dev_err(drm_dev->dev,
> > + "Cannot attach bridge for output %u\n",
> > output);
> > + goto err_cleanup_encoder;
> > + }
> > +
> > + bridge->conn = drm_bridge_connector_init(drm_dev, &bridge-
> > >enc);
> > + if (IS_ERR(bridge->conn)) {
> > + dev_err(drm_dev->dev,
> > + "Cannot create connector for output %u\n",
> > output);
> > + ret = PTR_ERR(bridge->conn);
> > + goto err_cleanup_encoder;
> > + }
> > + drm_connector_attach_encoder(bridge->conn, &bridge->enc);
> > +
> > + return bridge;
> > +
> > +err_cleanup_encoder:
> > + drm_encoder_cleanup(&bridge->enc);
> > +err_free_bridge:
> > + devm_kfree(drm_dev->dev, bridge);
> > +
> > + return ERR_PTR(ret);
> > +}
> > diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h
> > b/drivers/gpu/drm/verisilicon/vs_bridge.h
> > new file mode 100644
> > index 0000000000000..4a8a9eeb739f2
> > --- /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..d1c91dd1354b4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> > @@ -0,0 +1,47 @@
> > +/* 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_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..46c4191b82f49
> > --- /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 "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)
>
> lowercase hex, please.
Why? I didn't see any document enforces this.
>
> > + 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,
> > + .destroy = drm_crtc_cleanup,
> > + .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 = devm_kzalloc(drm_dev->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)) {
> > + dev_err(drm_dev->dev, "Couldn't create the primary
> > plane\n");
> > + return ERR_PTR(PTR_ERR(primary));
> > + }
> > +
> > + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> > + primary,
> > + NULL,
> > + &vs_crtc_funcs,
> > + NULL);
> > + if (ret) {
> > + dev_err(drm_dev->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..98384559568c4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > @@ -0,0 +1,233 @@
> > +// 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,
> > + .cache_type = REGCACHE_NONE,
> > +};
> > +
> > +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[0].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(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(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(dev, "ahb");
>
> devm_clk_get_enabled() ?
>
> > + 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;
> > + }
> > +
> > + ret = clk_prepare_enable(dc->core_clk);
> > + if (ret) {
> > + dev_err(dev, "can't enable core clock\n");
> > + goto err_rst_assert;
> > + }
> > +
> > + ret = clk_prepare_enable(dc->axi_clk);
> > + if (ret) {
> > + dev_err(dev, "can't enable axi clock\n");
> > + goto err_core_clk_disable;
> > + }
> > +
> > + ret = clk_prepare_enable(dc->ahb_clk);
> > + if (ret) {
> > + dev_err(dev, "can't enable ahb clock\n");
> > + goto err_axi_clk_disable;
> > + }
> > +
> > + regs = devm_platform_ioremap_resource(pdev, 0);
> > + if (IS_ERR(regs)) {
> > + dev_err(dev, "can't map registers");
> > + ret = PTR_ERR(regs);
> > + goto err_ahb_clk_disable;
> > + }
> > +
> > + dc->regs = devm_regmap_init_mmio(dev, regs,
> > &vs_dc_regmap_cfg);
> > + if (IS_ERR(dc->regs)) {
> > + ret = PTR_ERR(dc->regs);
> > + goto err_ahb_clk_disable;
> > + }
> > +
> > + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
>
> I'd say, this should be a part of the DT bindings.
>
> > + if (ret)
> > + goto err_ahb_clk_disable;
> > +
> > + 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_ahb_clk_disable;
> > + }
> > +
> > + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> > + dev_name(dev), dc);
>
> Are we ready to handle the IRQ here?
>
> > + if (ret) {
> > + dev_err(dev, "can't request irq\n");
> > + goto err_ahb_clk_disable;
> > + }
> > +
> > + dev_set_drvdata(dev, dc);
> > +
> > + ret = vs_drm_initialize(dc, pdev);
> > + if (ret)
> > + goto err_ahb_clk_disable;
> > +
> > + return 0;
> > +
> > +err_ahb_clk_disable:
> > + clk_disable_unprepare(dc->ahb_clk);
> > +err_axi_clk_disable:
> > + clk_disable_unprepare(dc->axi_clk);
> > +err_core_clk_disable:
> > + clk_disable_unprepare(dc->core_clk);
> > +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);
> > +
> > + clk_disable_unprepare(dc->ahb_clk);
> > + clk_disable_unprepare(dc->axi_clk);
> > + clk_disable_unprepare(dc->core_clk);
> > + 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);
>
> I'd suggest inlining simple wrappers.
Well I am going to divider the code to non-DRM things and DRM things
here, so vs_drm_shutdown_handler is in the DRM things part instead.
>
> > +}
> > +
> > +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..25d6e01cc8b71
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> > @@ -0,0 +1,166 @@
> > +// 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_existing_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;
> > +
> > + DRM_DEBUG_DRIVER("Updating output %d primary plane\n",
> > output);
> > +
> > + vcrtc = drm_crtc_to_vs_crtc(crtc);
> > + output = vcrtc->id;
> > + dc = vcrtc->dc;
> > +
> > + 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,
> > + .destroy = drm_plane_cleanup,
> > + .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;
> > + int ret;
> > +
> > + plane = devm_kzalloc(drm_dev->dev, sizeof(*plane),
> > GFP_KERNEL);
> > + if (!plane)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + ret = drm_universal_plane_init(drm_dev, plane, 0,
> > + &vs_primary_plane_funcs,
> > + dc->identity.formats->array,
> > + dc->identity.formats->num,
> > + NULL,
> > + DRM_PLANE_TYPE_PRIMARY,
> > + NULL);
> > +
> > + if (ret) {
> > + devm_kfree(drm_dev->dev, plane);
> > + return ERR_PTR(ret);
> > + }
> > +
> > + 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.50.1
> >
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-16 16:24 ` Dmitry Baryshkov
@ 2025-08-16 17:10 ` Icenowy Zheng
2025-08-18 7:45 ` Troy Mitchell
0 siblings, 1 reply; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 17:10 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> > 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>
> > ---
> > MAINTAINERS | 1 +
> > drivers/gpu/drm/bridge/Kconfig | 10 ++
> > drivers/gpu/drm/bridge/Makefile | 1 +
> > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170
> > ++++++++++++++++++++++++
> > 4 files changed, 182 insertions(+)
> > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index fe168477caa45..eb84e36ded6d5 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
> > --- a/drivers/gpu/drm/bridge/Kconfig
> > +++ b/drivers/gpu/drm/bridge/Kconfig
> > @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> > --- a/drivers/gpu/drm/bridge/Makefile
> > +++ b/drivers/gpu/drm/bridge/Makefile
> > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
> > obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > obj-$(CONFIG_DRM_SII9234) += sii9234.o
> > obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> > --- /dev/null
> > +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > @@ -0,0 +1,170 @@
> > +// 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
> > },
> > + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
> > }
> > +};
> > +
> > +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;
>
> We should rewrite DW bridge into HDMI ops. It would help us to get
> rid
> of such functions. With it in place it will be handled by the generic
> TMDS clock rate check.
Yes, but this mode_valid hook here is part of the private interface of
DW bridge, instead of implementing anything for other parts of DRM
subsystem.
Even if the TMDS clock rate check is utilized by the DW bridge, the
bridge's interface should be modified to expose something to set the
maximum clock rate.
>
> > +
> > + return MODE_OK;
> > +}
> > +
> > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void
> > *data,
> > + unsigned long mpixelclock)
> > +{
> > + const struct th1520_hdmi_phy_params *params =
> > th1520_hdmi_phy_params;
> > +
> > + for (; params->mpixelclock != ~0UL; ++params) {
> > + if (mpixelclock <= params->mpixelclock)
> > + break;
>
> for (...) {
> if (mpixelclock <= params->mpixelclock)
> return th1520_program_phy();
There's no such a function here, and this check isn't used for another
time, so having the matching code and programming code extracted out
can help nothing.
> }
>
> return -EINVAL;
>
> > + }
> > +
> > + if (params->mpixelclock == ~0UL)
> > + return -EINVAL;
> > +
> > + 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);
> > +
> > + return 0;
> > +}
> > +
> > +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.50.1
> >
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 16:48 ` Icenowy Zheng
@ 2025-08-16 17:22 ` Icenowy Zheng
2025-08-16 18:01 ` Icenowy Zheng
2025-08-16 18:10 ` Dmitry Baryshkov
2025-08-16 17:45 ` Dmitry Baryshkov
1 sibling, 2 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 17:22 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-17星期日的 00:48 +0800,Icenowy Zheng写道:
> 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > 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>
> > > ---
> > > 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 | 330
> > > ++++++++++++++++++
> > > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > > 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 | 233
> > > +++++++++++++
> > > 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 | 166 +++++++++
> > > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > > --- a/drivers/gpu/drm/Kconfig
> > > +++ b/drivers/gpu/drm/Kconfig
> > > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> > >
> > > source "drivers/gpu/drm/imagination/Kconfig"
> > >
> > > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > > +
> > > config DRM_HYPERV
> > > tristate "DRM Support for Hyper-V synthetic video device"
> > > depends on DRM && PCI && HYPERV
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -231,6 +231,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..c8caf31fac7d6
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > > @@ -0,0 +1,330 @@
> > > +// 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_RGB888_1X24, false,
> > > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > > + { 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);
> > > + struct drm_connector *conn = conn_state->connector;
> > > + u32 *output_fmts;
> > > + unsigned int i;
> > > +
> > > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> >
> > This kind of checks looks like there should be a drm_encoder
> > handled
> > by
> > the same driver. Or maybe it's better to have two sets of funcs
> > structures, one for the DPI, one for DP.
>
> Well these functions used to be for an encoder, however I found that
> encoders cannot take part in format negotiation, and at least some
> source says encoder is deprecated in this situation and a first
> bridge
> in the bridge chain is better here.
>
> A simple encoder is created by this part of driver, but all its works
> are moved to this bridge, similar to what other drivers with bridge
> chaining support do.
>
> >
> > > + *num_output_fmts = 1;
> > > + 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) {
> > > + if (conn->display_info.num_bus_formats &&
> > > + conn->display_info.bus_formats)
> > > + output_fmts[0] = conn-
> > > > display_info.bus_formats[0];
> > > + else
> > > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > > + } else {
> > > + for (i = 0; i < *num_output_fmts; i++)
> > > + output_fmts[i] =
> > > vsdc_dp_supported_fmts[i].linux_fmt;
> >
> > memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
>
> vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
> parameter, so memcpy won't work here.
>
> >
> > > + }
> > > +
> > > + 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;
> >
> > > + break;
> > > +
> > > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> >
> > 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;
> >
> > You are saving a state value into a non-state variable. There is no
> > guarantee that this atomic_check() will be followed by the actual
> > commit. So, either you have to use a struct that extends
> > drm_bridge_state here or store the output_bus_fmt during
> > atomic_enable().
>
> In fact I don't want to save it -- the kernel is quirky here and this
> value does not get passed into atomic_enable. I mimicked what other
> drivers do. See ingenic_drm_bridge_atomic_check() in ingenic/ingenic-
> drm-drv.c and meson_encoder_hdmi_atomic_check() in
> meson/meson_encoder_hdmi.c .
>
> >
> > > +
> > > + 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);
> > > + 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;
> > > + }
> > > + WARN_ON_ONCE(i ==
> > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> >
> > This might trigger all static checkers in the universe. It's not
> > really
> > possible, since you've checked it in the atomic_check(), but...
>
> Sigh I don't know how to properly describe it...
>
> I can only say something really bad happens if the previous
> WARN_ON_ONCE is triggered.
>
> >
> > > + 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);
> >
> > This deserves a comment in the source file.
> >
> > > + if (remote) {
> > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> >
> > return here, drop else{}
>
> Well a of_node_put() is missing before the final return, and Yao Zi
> noted me of it.
>
> >
> > > + } else {
> > > + remote = of_graph_get_remote_node(of_node,
> > > output,
> > > +
> > > VSDC_OUTPUT_INTERFACE_DP);
> > > + if (remote)
> > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> >
> > return
> >
> > > + else
> > > + ret = -ENODEV;
> > > + }
> > > +
> > > + 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;
> > > +
> > > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > > > of_node,
> > > + output);
> > > + if (intf == -ENODEV) {
> > > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > > output);
> > > + return NULL;
> > > + }
> > > +
> > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > GFP_KERNEL);
> >
> > devm_drm_bridge_alloc()
> >
> > > + if (!bridge)
> > > + return ERR_PTR(-ENOMEM);
> > > +
> > > + bridge->crtc = crtc;
> > > + bridge->intf = intf;
> > > + bridge->base.funcs = &vs_bridge_funcs;
> > > +
> > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev-
> > > > of_node,
> > > + output, intf);
> > > + if (IS_ERR(next)) {
> > > + ret = PTR_ERR(next);
> > > + goto err_free_bridge;
> > > + }
> > > +
> > > + bridge->next = next;
> > > +
> > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> >
> > Oh, so there is an encoder... Please drop drm_simple_encoder, it's
> > deprecated, and try moving all the ifs to the encoder funcs.
>
> Ah? Is it really deprecated? I can find no source of this
> deprecation.
>
> In addition, I think many drivers here are using a bridge as a
> "better
> encoder" because of the restriction of current encoder
> implementation,
> and I am doing the same thing. Either encoder functionality should be
> improved to on par with bridge, or such dummy encoders with a bridge
> should exist, and some helper for creating them should exist. It
> might
> be not drm_simple_encoder_init (because I can understand the
> deprecation of other parts of the simple-kms routines, although I see
> no formal documentation mentioning it's deprecated, maybe I missed
> some
> newspaper?), but it should exist.
I see some practice of passing NULL to drmm_plain_encoder_alloc() from
the adp driver, however looks like this isn't always safe and on my
test of this change on top of verisilicon driver (on top of v6.17-rc1)
I got mysterious oops if the DC driver happens to be probed before the
HDMI controller driver:
```
[ 28.372698] Unable to handle kernel access to user memory without
uaccess routines at virtual address 0000000000000010
[ 28.383596] Current (udev-worker) pgtable: 4K pagesize, 39-bit VAs,
pgdp=0x0000000108532000
[ 28.392180] [0000000000000010] pgd=0000000000000000,
p4d=0000000000000000, pud=0000000000000000
[ 28.401108] Oops [#1]
[ 28.403405] Modules linked in: th1520_dw_hdmi verisilicon_dc(+)
configfs dm_mod
[ 28.410773] CPU: 1 UID: 0 PID: 527 Comm: (udev-worker) Not tainted
6.17.0-rc1+ #92 NONE
[ 28.418890] Hardware name: Sipeed Lichee Pi 4A (DT)
[ 28.423784] epc : drm_mode_config_cleanup+0xc6/0x224
[ 28.428800] ra : drm_mode_config_cleanup+0xa4/0x224
[ 28.433796] epc : ffffffff807d7016 ra : ffffffff807d6ff4 sp :
ffffffc6004f3790
[ 28.441038] gp : ffffffff81c52ae8 tp : ffffffd700873ac0 t0 :
0000000000000040
[ 28.448279] t1 : 0000000000000000 t2 : 0000000000001c91 s0 :
ffffffc6004f3810
[ 28.455514] s1 : ffffffd70847f000 a0 : ffffffd70847e840 a1 :
ffffffd70847f2f8
[ 28.462770] a2 : ffffffff81c94178 a3 : 0000000000000002 a4 :
0000000000000000
[ 28.470027] a5 : 0000000000000000 a6 : 0000000000000436 a7 :
ffffffd70d3eb350
[ 28.477263] s2 : fffffffffffffff8 s3 : ffffffd70847f2d0 s4 :
ffffffd70847f2b8
[ 28.484499] s5 : dead000000000100 s6 : ffffffd70847f018 s7 :
0000000000000000
[ 28.491733] s8 : ffffffc6004f3d40 s9 : ffffffff01d04198 s10:
ffffffc6004f3c80
[ 28.498968] s11: ffffffff01d042c0 t3 : 0000000000000002 t4 :
0000000000000402
[ 28.506200] t5 : ffffffd70847f1f8 t6 : ffffffd70847f200
[ 28.511523] status: 0000000200000120 badaddr: 0000000000000010
cause: 000000000000000d
[ 28.519454] [<ffffffff807d7016>] drm_mode_config_cleanup+0xc6/0x224
[ 28.525754] [<ffffffff807d75f8>]
drm_mode_config_init_release+0xc/0x14
[ 28.532312] [<ffffffff807d5b8e>] drm_managed_release+0x7a/0x100
[ 28.538257] [<ffffffff807c6b9a>] devm_drm_dev_init_release+0x62/0x78
[ 28.544641] [<ffffffff8084877a>] devm_action_release+0xe/0x18
[ 28.550411] [<ffffffff80848b1a>] release_nodes+0x3e/0x94
[ 28.555752] [<ffffffff80849cc6>] devres_release_all+0x72/0xb4
[ 28.561529] [<ffffffff80843d74>] device_unbind_cleanup+0x10/0x58
[ 28.567554] [<ffffffff80844510>] really_probe+0x184/0x30c
[ 28.572973] [<ffffffff808446fc>] __driver_probe_device+0x64/0x10c
[ 28.579086] [<ffffffff80844868>] driver_probe_device+0x2c/0xb4
[ 28.584937] [<ffffffff80844a5a>] __driver_attach+0x9a/0x1a4
[ 28.590530] [<ffffffff80842386>] bus_for_each_dev+0x62/0xb0
[ 28.596130] [<ffffffff80843ea6>] driver_attach+0x1a/0x24
[ 28.601461] [<ffffffff80843662>] bus_add_driver+0xf6/0x200
[ 28.606972] [<ffffffff80845858>] driver_register+0x40/0xdc
[ 28.612478] [<ffffffff80846bac>]
__platform_driver_register+0x1c/0x24
[ 28.618946] [<ffffffff01d0b020>]
vs_dc_platform_driver_init+0x20/0x1000 [verisilicon_dc]
[ 28.627075] [<ffffffff800158c6>] do_one_initcall+0x56/0x1cc
[ 28.632675] [<ffffffff800c8116>] do_init_module+0x52/0x1dc
[ 28.638187] [<ffffffff800c9bd2>] load_module+0x16e2/0x1afc
[ 28.643696] [<ffffffff800ca1c6>] init_module_from_file+0x76/0xb0
[ 28.649726] [<ffffffff800ca436>]
__riscv_sys_finit_module+0x1fe/0x3cc
[ 28.656195] [<ffffffff80cfee1e>] do_trap_ecall_u+0x296/0x370
[ 28.661878] [<ffffffff80d09a56>] handle_exception+0x146/0x152
[ 28.667656] Code: 8993 2d04 b903 0007 8513 ff87 1961 8e63 00f9 7d5c
(6b9c) 9782
[ 28.675233] ---[ end trace 0000000000000000 ]---
```
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 16:48 ` Icenowy Zheng
2025-08-16 17:22 ` Icenowy Zheng
@ 2025-08-16 17:45 ` Dmitry Baryshkov
2025-08-16 17:55 ` Icenowy Zheng
1 sibling, 1 reply; 42+ messages in thread
From: Dmitry Baryshkov @ 2025-08-16 17:45 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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
On Sun, Aug 17, 2025 at 12:48:42AM +0800, Icenowy Zheng wrote:
> 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > 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>
> > > ---
> > > 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 | 330
> > > ++++++++++++++++++
> > > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > > 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 | 233 +++++++++++++
> > > 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 | 166 +++++++++
> > > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > > --- a/drivers/gpu/drm/Kconfig
> > > +++ b/drivers/gpu/drm/Kconfig
> > > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> > >
> > > source "drivers/gpu/drm/imagination/Kconfig"
> > >
> > > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > > +
> > > config DRM_HYPERV
> > > tristate "DRM Support for Hyper-V synthetic video device"
> > > depends on DRM && PCI && HYPERV
> > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > > --- a/drivers/gpu/drm/Makefile
> > > +++ b/drivers/gpu/drm/Makefile
> > > @@ -231,6 +231,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..c8caf31fac7d6
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > > @@ -0,0 +1,330 @@
> > > +// 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_RGB888_1X24, false,
> > > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > > + { 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);
> > > + struct drm_connector *conn = conn_state->connector;
> > > + u32 *output_fmts;
> > > + unsigned int i;
> > > +
> > > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> >
> > This kind of checks looks like there should be a drm_encoder handled
> > by
> > the same driver. Or maybe it's better to have two sets of funcs
> > structures, one for the DPI, one for DP.
>
> Well these functions used to be for an encoder, however I found that
> encoders cannot take part in format negotiation,
You encoder's formats are pretty much fixed. If you allocate encoder
structure and keep formats count / pointer in it, then your root bridge
can query the vs_encoder struct during format negotiation.
> and at least some
> source says encoder is deprecated in this situation and a first bridge
> in the bridge chain is better here.
They are mostly not necessary, but it's still better than having similar
ofs all over the place.
>
> A simple encoder is created by this part of driver, but all its works
> are moved to this bridge, similar to what other drivers with bridge
> chaining support do.
Using non-simple drm_encoder would be a better option here.
>
> >
> > > + *num_output_fmts = 1;
> > > + 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) {
> > > + if (conn->display_info.num_bus_formats &&
> > > + conn->display_info.bus_formats)
> > > + output_fmts[0] = conn-
> > > >display_info.bus_formats[0];
> > > + else
> > > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > > + } else {
> > > + for (i = 0; i < *num_output_fmts; i++)
> > > + output_fmts[i] =
> > > vsdc_dp_supported_fmts[i].linux_fmt;
> >
> > memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
>
> vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
> parameter, so memcpy won't work here.
Ack
>
> >
> > > + }
> > > +
> > > + 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;
> >
> > > + break;
> > > +
> > > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> >
> > 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;
> >
> > You are saving a state value into a non-state variable. There is no
> > guarantee that this atomic_check() will be followed by the actual
> > commit. So, either you have to use a struct that extends
> > drm_bridge_state here or store the output_bus_fmt during
> > atomic_enable().
>
> In fact I don't want to save it -- the kernel is quirky here and this
> value does not get passed into atomic_enable. I mimicked what other
> drivers do. See ingenic_drm_bridge_atomic_check() in ingenic/ingenic-
> drm-drv.c and meson_encoder_hdmi_atomic_check() in
> meson/meson_encoder_hdmi.c .
Please don't follow that pattern. It breaks as soon as userspace submits
an DRM_MODE_ATOMIC_TEST_ONLY commit. It's hard for encoders since they
don't have a state, but bridges have proper drm_bridge_state. Please use
it.
>
> >
> > > +
> > > + 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);
> > > + 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;
> > > + }
> > > + WARN_ON_ONCE(i ==
> > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> >
> > This might trigger all static checkers in the universe. It's not
> > really
> > possible, since you've checked it in the atomic_check(), but...
>
> Sigh I don't know how to properly describe it...
>
> I can only say something really bad happens if the previous
> WARN_ON_ONCE is triggered.
if (WARN_ON_ONCE())
return;
>
> >
> > > + 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);
> >
> > This deserves a comment in the source file.
> >
> > > + if (remote) {
> > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> >
> > return here, drop else{}
>
> Well a of_node_put() is missing before the final return, and Yao Zi
> noted me of it.
You can put the node right after of_graph_get_remote_node(); You don't
use any props from it.
>
> >
> > > + } else {
> > > + remote = of_graph_get_remote_node(of_node, output,
> > > +
> > > VSDC_OUTPUT_INTERFACE_DP);
> > > + if (remote)
> > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> >
> > return
> >
> > > + else
> > > + ret = -ENODEV;
> > > + }
> > > +
> > > + 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;
> > > +
> > > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > > >of_node,
> > > + output);
> > > + if (intf == -ENODEV) {
> > > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > > output);
> > > + return NULL;
> > > + }
> > > +
> > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > GFP_KERNEL);
> >
> > devm_drm_bridge_alloc()
> >
> > > + if (!bridge)
> > > + return ERR_PTR(-ENOMEM);
> > > +
> > > + bridge->crtc = crtc;
> > > + bridge->intf = intf;
> > > + bridge->base.funcs = &vs_bridge_funcs;
> > > +
> > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev-
> > > >of_node,
> > > + output, intf);
> > > + if (IS_ERR(next)) {
> > > + ret = PTR_ERR(next);
> > > + goto err_free_bridge;
> > > + }
> > > +
> > > + bridge->next = next;
> > > +
> > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> >
> > Oh, so there is an encoder... Please drop drm_simple_encoder, it's
> > deprecated, and try moving all the ifs to the encoder funcs.
>
> Ah? Is it really deprecated? I can find no source of this deprecation.
https://lore.kernel.org/dri-devel/20250401094056.32904-3-tzimmermann@suse.de/
> In addition, I think many drivers here are using a bridge as a "better
> encoder" because of the restriction of current encoder implementation,
> and I am doing the same thing. Either encoder functionality should be
> improved to on par with bridge, or such dummy encoders with a bridge
> should exist, and some helper for creating them should exist. It might
> be not drm_simple_encoder_init (because I can understand the
> deprecation of other parts of the simple-kms routines, although I see
> no formal documentation mentioning it's deprecated, maybe I missed some
> newspaper?), but it should exist.
Maybe we should explicitly document the status.
Also, if you use non-simple encoders, you can actually have
functionality there.
>
> >
> > > + (intf ==
> > > VSDC_OUTPUT_INTERFACE_DPI) ?
> > > + DRM_MODE_ENCODER_DPI :
> > > + DRM_MODE_ENCODER_NONE);
> > > + if (ret) {
> > > + dev_err(drm_dev->dev,
> > > + "Cannot initialize encoder for output
> > > %u\n", output);
> > > + goto err_free_bridge;
> > > + }
> > > +
> > > + 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) {
> > > + dev_err(drm_dev->dev,
> > > + "Cannot attach bridge for output %u\n",
> > > output);
> > > + goto err_cleanup_encoder;
> > > + }
> > > +
> > > + bridge->conn = drm_bridge_connector_init(drm_dev, &bridge-
> > > >enc);
> > > + if (IS_ERR(bridge->conn)) {
> > > + dev_err(drm_dev->dev,
> > > + "Cannot create connector for output %u\n",
> > > output);
> > > + ret = PTR_ERR(bridge->conn);
> > > + goto err_cleanup_encoder;
> > > + }
> > > + drm_connector_attach_encoder(bridge->conn, &bridge->enc);
> > > +
> > > + return bridge;
> > > +
> > > +err_cleanup_encoder:
> > > + drm_encoder_cleanup(&bridge->enc);
> > > +err_free_bridge:
> > > + devm_kfree(drm_dev->dev, bridge);
> > > +
> > > + return ERR_PTR(ret);
> > > +}
> > > diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > b/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > new file mode 100644
> > > index 0000000000000..4a8a9eeb739f2
> > > --- /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..d1c91dd1354b4
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> > > @@ -0,0 +1,47 @@
> > > +/* 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_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..46c4191b82f49
> > > --- /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 "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)
> >
> > lowercase hex, please.
>
> Why? I didn't see any document enforces this.
I think, it's a generic suggestion for the sake of readability.
>
> >
> > > + 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,
> > > + .destroy = drm_crtc_cleanup,
> > > + .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 = devm_kzalloc(drm_dev->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)) {
> > > + dev_err(drm_dev->dev, "Couldn't create the primary
> > > plane\n");
> > > + return ERR_PTR(PTR_ERR(primary));
> > > + }
> > > +
> > > + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> > > + primary,
> > > + NULL,
> > > + &vs_crtc_funcs,
> > > + NULL);
> > > + if (ret) {
> > > + dev_err(drm_dev->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..98384559568c4
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > > @@ -0,0 +1,233 @@
> > > +// 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,
> > > + .cache_type = REGCACHE_NONE,
> > > +};
> > > +
> > > +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[0].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(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(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(dev, "ahb");
> >
> > devm_clk_get_enabled() ?
> >
> > > + 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;
> > > + }
> > > +
> > > + ret = clk_prepare_enable(dc->core_clk);
> > > + if (ret) {
> > > + dev_err(dev, "can't enable core clock\n");
> > > + goto err_rst_assert;
> > > + }
> > > +
> > > + ret = clk_prepare_enable(dc->axi_clk);
> > > + if (ret) {
> > > + dev_err(dev, "can't enable axi clock\n");
> > > + goto err_core_clk_disable;
> > > + }
> > > +
> > > + ret = clk_prepare_enable(dc->ahb_clk);
> > > + if (ret) {
> > > + dev_err(dev, "can't enable ahb clock\n");
> > > + goto err_axi_clk_disable;
> > > + }
> > > +
> > > + regs = devm_platform_ioremap_resource(pdev, 0);
> > > + if (IS_ERR(regs)) {
> > > + dev_err(dev, "can't map registers");
> > > + ret = PTR_ERR(regs);
> > > + goto err_ahb_clk_disable;
> > > + }
> > > +
> > > + dc->regs = devm_regmap_init_mmio(dev, regs,
> > > &vs_dc_regmap_cfg);
> > > + if (IS_ERR(dc->regs)) {
> > > + ret = PTR_ERR(dc->regs);
> > > + goto err_ahb_clk_disable;
> > > + }
> > > +
> > > + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
> >
> > I'd say, this should be a part of the DT bindings.
> >
> > > + if (ret)
> > > + goto err_ahb_clk_disable;
> > > +
> > > + 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_ahb_clk_disable;
> > > + }
> > > +
> > > + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> > > + dev_name(dev), dc);
> >
> > Are we ready to handle the IRQ here?
> >
> > > + if (ret) {
> > > + dev_err(dev, "can't request irq\n");
> > > + goto err_ahb_clk_disable;
> > > + }
> > > +
> > > + dev_set_drvdata(dev, dc);
> > > +
> > > + ret = vs_drm_initialize(dc, pdev);
> > > + if (ret)
> > > + goto err_ahb_clk_disable;
> > > +
> > > + return 0;
> > > +
> > > +err_ahb_clk_disable:
> > > + clk_disable_unprepare(dc->ahb_clk);
> > > +err_axi_clk_disable:
> > > + clk_disable_unprepare(dc->axi_clk);
> > > +err_core_clk_disable:
> > > + clk_disable_unprepare(dc->core_clk);
> > > +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);
> > > +
> > > + clk_disable_unprepare(dc->ahb_clk);
> > > + clk_disable_unprepare(dc->axi_clk);
> > > + clk_disable_unprepare(dc->core_clk);
> > > + 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);
> >
> > I'd suggest inlining simple wrappers.
>
> Well I am going to divider the code to non-DRM things and DRM things
> here, so vs_drm_shutdown_handler is in the DRM things part instead.
Ack. It might be my personal preference not to have extra wrappers.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 17:45 ` Dmitry Baryshkov
@ 2025-08-16 17:55 ` Icenowy Zheng
2025-08-16 18:05 ` Icenowy Zheng
0 siblings, 1 reply; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 17:55 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-16星期六的 20:45 +0300,Dmitry Baryshkov写道:
> On Sun, Aug 17, 2025 at 12:48:42AM +0800, Icenowy Zheng wrote:
> > 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > > 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>
> > > > ---
> > > > 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 | 330
> > > > ++++++++++++++++++
> > > > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > > > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > > > 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 | 233
> > > > +++++++++++++
> > > > 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 | 166 +++++++++
> > > > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > > > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > > > --- a/drivers/gpu/drm/Kconfig
> > > > +++ b/drivers/gpu/drm/Kconfig
> > > > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> > > >
> > > > source "drivers/gpu/drm/imagination/Kconfig"
> > > >
> > > > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > > > +
> > > > config DRM_HYPERV
> > > > tristate "DRM Support for Hyper-V synthetic video
> > > > device"
> > > > depends on DRM && PCI && HYPERV
> > > > diff --git a/drivers/gpu/drm/Makefile
> > > > b/drivers/gpu/drm/Makefile
> > > > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > > > --- a/drivers/gpu/drm/Makefile
> > > > +++ b/drivers/gpu/drm/Makefile
> > > > @@ -231,6 +231,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..c8caf31fac7d6
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > > > @@ -0,0 +1,330 @@
> > > > +// 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_RGB888_1X24, false,
> > > > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > > > + { 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);
> > > > + struct drm_connector *conn = conn_state->connector;
> > > > + u32 *output_fmts;
> > > > + unsigned int i;
> > > > +
> > > > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> > >
> > > This kind of checks looks like there should be a drm_encoder
> > > handled
> > > by
> > > the same driver. Or maybe it's better to have two sets of funcs
> > > structures, one for the DPI, one for DP.
> >
> > Well these functions used to be for an encoder, however I found
> > that
> > encoders cannot take part in format negotiation,
>
> You encoder's formats are pretty much fixed. If you allocate encoder
> structure and keep formats count / pointer in it, then your root
> bridge
> can query the vs_encoder struct during format negotiation.
>
> > and at least some
> > source says encoder is deprecated in this situation and a first
> > bridge
> > in the bridge chain is better here.
>
> They are mostly not necessary, but it's still better than having
> similar
> ofs all over the place.
>
> >
> > A simple encoder is created by this part of driver, but all its
> > works
> > are moved to this bridge, similar to what other drivers with bridge
> > chaining support do.
>
> Using non-simple drm_encoder would be a better option here.
>
> >
> > >
> > > > + *num_output_fmts = 1;
> > > > + 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) {
> > > > + if (conn->display_info.num_bus_formats &&
> > > > + conn->display_info.bus_formats)
> > > > + output_fmts[0] = conn-
> > > > > display_info.bus_formats[0];
> > > > + else
> > > > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > > > + } else {
> > > > + for (i = 0; i < *num_output_fmts; i++)
> > > > + output_fmts[i] =
> > > > vsdc_dp_supported_fmts[i].linux_fmt;
> > >
> > > memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
> >
> > vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
> > parameter, so memcpy won't work here.
>
> Ack
>
> >
> > >
> > > > + }
> > > > +
> > > > + 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;
> > >
> > > > + break;
> > > > +
> > > > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> > >
> > > 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;
> > >
> > > You are saving a state value into a non-state variable. There is
> > > no
> > > guarantee that this atomic_check() will be followed by the actual
> > > commit. So, either you have to use a struct that extends
> > > drm_bridge_state here or store the output_bus_fmt during
> > > atomic_enable().
> >
> > In fact I don't want to save it -- the kernel is quirky here and
> > this
> > value does not get passed into atomic_enable. I mimicked what other
> > drivers do. See ingenic_drm_bridge_atomic_check() in
> > ingenic/ingenic-
> > drm-drv.c and meson_encoder_hdmi_atomic_check() in
> > meson/meson_encoder_hdmi.c .
>
> Please don't follow that pattern. It breaks as soon as userspace
> submits
> an DRM_MODE_ATOMIC_TEST_ONLY commit. It's hard for encoders since
> they
> don't have a state, but bridges have proper drm_bridge_state. Please
> use
> it.
Yes I thought it have proper drm_bridge_state, but I cannot understand
why I got always 0 when accessing bridge_state->output_bus_cfg.format
in atomic_enable().
If I follow this pattern, then when this problem is fixed, my driver
can get fixed along with ingenic and meson.
>
> >
> > >
> > > > +
> > > > + 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);
> > > > + 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;
> > > > + }
> > > > + WARN_ON_ONCE(i ==
> > > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> > >
> > > This might trigger all static checkers in the universe. It's not
> > > really
> > > possible, since you've checked it in the atomic_check(), but...
> >
> > Sigh I don't know how to properly describe it...
> >
> > I can only say something really bad happens if the previous
> > WARN_ON_ONCE is triggered.
>
>
> if (WARN_ON_ONCE())
> return;
Sounds reasonable, as it's a UB here.
>
> >
> > >
> > > > + 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);
> > >
> > > This deserves a comment in the source file.
> > >
> > > > + if (remote) {
> > > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> > >
> > > return here, drop else{}
> >
> > Well a of_node_put() is missing before the final return, and Yao Zi
> > noted me of it.
>
> You can put the node right after of_graph_get_remote_node(); You
> don't
> use any props from it.
>
> >
> > >
> > > > + } else {
> > > > + remote = of_graph_get_remote_node(of_node,
> > > > output,
> > > > +
> > > > VSDC_OUTPUT_INTERFACE_DP);
> > > > + if (remote)
> > > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> > >
> > > return
> > >
> > > > + else
> > > > + ret = -ENODEV;
> > > > + }
> > > > +
> > > > + 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;
> > > > +
> > > > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > > > > of_node,
> > > > + output);
> > > > + if (intf == -ENODEV) {
> > > > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > > > output);
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > > GFP_KERNEL);
> > >
> > > devm_drm_bridge_alloc()
> > >
> > > > + if (!bridge)
> > > > + return ERR_PTR(-ENOMEM);
> > > > +
> > > > + bridge->crtc = crtc;
> > > > + bridge->intf = intf;
> > > > + bridge->base.funcs = &vs_bridge_funcs;
> > > > +
> > > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev-
> > > > >dev-
> > > > > of_node,
> > > > + output, intf);
> > > > + if (IS_ERR(next)) {
> > > > + ret = PTR_ERR(next);
> > > > + goto err_free_bridge;
> > > > + }
> > > > +
> > > > + bridge->next = next;
> > > > +
> > > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> > >
> > > Oh, so there is an encoder... Please drop drm_simple_encoder,
> > > it's
> > > deprecated, and try moving all the ifs to the encoder funcs.
> >
> > Ah? Is it really deprecated? I can find no source of this
> > deprecation.
>
> https://lore.kernel.org/dri-devel/20250401094056.32904-3-tzimmermann@suse.de/
>
> > In addition, I think many drivers here are using a bridge as a
> > "better
> > encoder" because of the restriction of current encoder
> > implementation,
> > and I am doing the same thing. Either encoder functionality should
> > be
> > improved to on par with bridge, or such dummy encoders with a
> > bridge
> > should exist, and some helper for creating them should exist. It
> > might
> > be not drm_simple_encoder_init (because I can understand the
> > deprecation of other parts of the simple-kms routines, although I
> > see
> > no formal documentation mentioning it's deprecated, maybe I missed
> > some
> > newspaper?), but it should exist.
>
> Maybe we should explicitly document the status.
>
> Also, if you use non-simple encoders, you can actually have
> functionality there.
Yes I tried it, but then I realized that it's not a good pattern if a
root bridge is present -- they represent the same thing in the
hardware.
>
> >
> > >
> > > > + (intf ==
> > > > VSDC_OUTPUT_INTERFACE_DPI) ?
> > > > + DRM_MODE_ENCODER_DPI :
> > > > + DRM_MODE_ENCODER_NONE);
> > > > + if (ret) {
> > > > + dev_err(drm_dev->dev,
> > > > + "Cannot initialize encoder for output
> > > > %u\n", output);
> > > > + goto err_free_bridge;
> > > > + }
> > > > +
> > > > + 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) {
> > > > + dev_err(drm_dev->dev,
> > > > + "Cannot attach bridge for output %u\n",
> > > > output);
> > > > + goto err_cleanup_encoder;
> > > > + }
> > > > +
> > > > + bridge->conn = drm_bridge_connector_init(drm_dev,
> > > > &bridge-
> > > > > enc);
> > > > + if (IS_ERR(bridge->conn)) {
> > > > + dev_err(drm_dev->dev,
> > > > + "Cannot create connector for output
> > > > %u\n",
> > > > output);
> > > > + ret = PTR_ERR(bridge->conn);
> > > > + goto err_cleanup_encoder;
> > > > + }
> > > > + drm_connector_attach_encoder(bridge->conn, &bridge-
> > > > >enc);
> > > > +
> > > > + return bridge;
> > > > +
> > > > +err_cleanup_encoder:
> > > > + drm_encoder_cleanup(&bridge->enc);
> > > > +err_free_bridge:
> > > > + devm_kfree(drm_dev->dev, bridge);
> > > > +
> > > > + return ERR_PTR(ret);
> > > > +}
> > > > diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > > b/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > > new file mode 100644
> > > > index 0000000000000..4a8a9eeb739f2
> > > > --- /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..d1c91dd1354b4
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> > > > @@ -0,0 +1,47 @@
> > > > +/* 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_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..46c4191b82f49
> > > > --- /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 "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)
> > >
> > > lowercase hex, please.
> >
> > Why? I didn't see any document enforces this.
>
> I think, it's a generic suggestion for the sake of readability.
>
> >
> > >
> > > > + 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,
> > > > + .destroy = drm_crtc_cleanup,
> > > > + .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 = devm_kzalloc(drm_dev->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)) {
> > > > + dev_err(drm_dev->dev, "Couldn't create the
> > > > primary
> > > > plane\n");
> > > > + return ERR_PTR(PTR_ERR(primary));
> > > > + }
> > > > +
> > > > + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc->base,
> > > > + primary,
> > > > + NULL,
> > > > + &vs_crtc_funcs,
> > > > + NULL);
> > > > + if (ret) {
> > > > + dev_err(drm_dev->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..98384559568c4
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > > > @@ -0,0 +1,233 @@
> > > > +// 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,
> > > > + .cache_type = REGCACHE_NONE,
> > > > +};
> > > > +
> > > > +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[0].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(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(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(dev, "ahb");
> > >
> > > devm_clk_get_enabled() ?
> > >
> > > > + 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;
> > > > + }
> > > > +
> > > > + ret = clk_prepare_enable(dc->core_clk);
> > > > + if (ret) {
> > > > + dev_err(dev, "can't enable core clock\n");
> > > > + goto err_rst_assert;
> > > > + }
> > > > +
> > > > + ret = clk_prepare_enable(dc->axi_clk);
> > > > + if (ret) {
> > > > + dev_err(dev, "can't enable axi clock\n");
> > > > + goto err_core_clk_disable;
> > > > + }
> > > > +
> > > > + ret = clk_prepare_enable(dc->ahb_clk);
> > > > + if (ret) {
> > > > + dev_err(dev, "can't enable ahb clock\n");
> > > > + goto err_axi_clk_disable;
> > > > + }
> > > > +
> > > > + regs = devm_platform_ioremap_resource(pdev, 0);
> > > > + if (IS_ERR(regs)) {
> > > > + dev_err(dev, "can't map registers");
> > > > + ret = PTR_ERR(regs);
> > > > + goto err_ahb_clk_disable;
> > > > + }
> > > > +
> > > > + dc->regs = devm_regmap_init_mmio(dev, regs,
> > > > &vs_dc_regmap_cfg);
> > > > + if (IS_ERR(dc->regs)) {
> > > > + ret = PTR_ERR(dc->regs);
> > > > + goto err_ahb_clk_disable;
> > > > + }
> > > > +
> > > > + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
> > >
> > > I'd say, this should be a part of the DT bindings.
> > >
> > > > + if (ret)
> > > > + goto err_ahb_clk_disable;
> > > > +
> > > > + 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_ahb_clk_disable;
> > > > + }
> > > > +
> > > > + ret = devm_request_irq(dev, irq, vs_dc_irq_handler, 0,
> > > > + dev_name(dev), dc);
> > >
> > > Are we ready to handle the IRQ here?
> > >
> > > > + if (ret) {
> > > > + dev_err(dev, "can't request irq\n");
> > > > + goto err_ahb_clk_disable;
> > > > + }
> > > > +
> > > > + dev_set_drvdata(dev, dc);
> > > > +
> > > > + ret = vs_drm_initialize(dc, pdev);
> > > > + if (ret)
> > > > + goto err_ahb_clk_disable;
> > > > +
> > > > + return 0;
> > > > +
> > > > +err_ahb_clk_disable:
> > > > + clk_disable_unprepare(dc->ahb_clk);
> > > > +err_axi_clk_disable:
> > > > + clk_disable_unprepare(dc->axi_clk);
> > > > +err_core_clk_disable:
> > > > + clk_disable_unprepare(dc->core_clk);
> > > > +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);
> > > > +
> > > > + clk_disable_unprepare(dc->ahb_clk);
> > > > + clk_disable_unprepare(dc->axi_clk);
> > > > + clk_disable_unprepare(dc->core_clk);
> > > > + 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);
> > >
> > > I'd suggest inlining simple wrappers.
> >
> > Well I am going to divider the code to non-DRM things and DRM
> > things
> > here, so vs_drm_shutdown_handler is in the DRM things part instead.
>
> Ack. It might be my personal preference not to have extra wrappers.
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 17:22 ` Icenowy Zheng
@ 2025-08-16 18:01 ` Icenowy Zheng
2025-08-16 18:10 ` Dmitry Baryshkov
1 sibling, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 18:01 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-17星期日的 01:22 +0800,Icenowy Zheng写道:
> 在 2025-08-17星期日的 00:48 +0800,Icenowy Zheng写道:
> > 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > > 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>
> > > > ---
> > > > 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 | 330
> > > > ++++++++++++++++++
> > > > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > > > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > > > 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 | 233
> > > > +++++++++++++
> > > > 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 | 166 +++++++++
> > > > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > > > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > > > --- a/drivers/gpu/drm/Kconfig
> > > > +++ b/drivers/gpu/drm/Kconfig
> > > > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> > > >
> > > > source "drivers/gpu/drm/imagination/Kconfig"
> > > >
> > > > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > > > +
> > > > config DRM_HYPERV
> > > > tristate "DRM Support for Hyper-V synthetic video
> > > > device"
> > > > depends on DRM && PCI && HYPERV
> > > > diff --git a/drivers/gpu/drm/Makefile
> > > > b/drivers/gpu/drm/Makefile
> > > > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > > > --- a/drivers/gpu/drm/Makefile
> > > > +++ b/drivers/gpu/drm/Makefile
> > > > @@ -231,6 +231,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..c8caf31fac7d6
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > > > @@ -0,0 +1,330 @@
> > > > +// 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_RGB888_1X24, false,
> > > > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > > > + { 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);
> > > > + struct drm_connector *conn = conn_state->connector;
> > > > + u32 *output_fmts;
> > > > + unsigned int i;
> > > > +
> > > > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> > >
> > > This kind of checks looks like there should be a drm_encoder
> > > handled
> > > by
> > > the same driver. Or maybe it's better to have two sets of funcs
> > > structures, one for the DPI, one for DP.
> >
> > Well these functions used to be for an encoder, however I found
> > that
> > encoders cannot take part in format negotiation, and at least some
> > source says encoder is deprecated in this situation and a first
> > bridge
> > in the bridge chain is better here.
> >
> > A simple encoder is created by this part of driver, but all its
> > works
> > are moved to this bridge, similar to what other drivers with bridge
> > chaining support do.
> >
> > >
> > > > + *num_output_fmts = 1;
> > > > + 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) {
> > > > + if (conn->display_info.num_bus_formats &&
> > > > + conn->display_info.bus_formats)
> > > > + output_fmts[0] = conn-
> > > > > display_info.bus_formats[0];
> > > > + else
> > > > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > > > + } else {
> > > > + for (i = 0; i < *num_output_fmts; i++)
> > > > + output_fmts[i] =
> > > > vsdc_dp_supported_fmts[i].linux_fmt;
> > >
> > > memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
> >
> > vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
> > parameter, so memcpy won't work here.
> >
> > >
> > > > + }
> > > > +
> > > > + 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;
> > >
> > > > + break;
> > > > +
> > > > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> > >
> > > 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;
> > >
> > > You are saving a state value into a non-state variable. There is
> > > no
> > > guarantee that this atomic_check() will be followed by the actual
> > > commit. So, either you have to use a struct that extends
> > > drm_bridge_state here or store the output_bus_fmt during
> > > atomic_enable().
> >
> > In fact I don't want to save it -- the kernel is quirky here and
> > this
> > value does not get passed into atomic_enable. I mimicked what other
> > drivers do. See ingenic_drm_bridge_atomic_check() in
> > ingenic/ingenic-
> > drm-drv.c and meson_encoder_hdmi_atomic_check() in
> > meson/meson_encoder_hdmi.c .
> >
> > >
> > > > +
> > > > + 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);
> > > > + 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;
> > > > + }
> > > > + WARN_ON_ONCE(i ==
> > > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> > >
> > > This might trigger all static checkers in the universe. It's not
> > > really
> > > possible, since you've checked it in the atomic_check(), but...
> >
> > Sigh I don't know how to properly describe it...
> >
> > I can only say something really bad happens if the previous
> > WARN_ON_ONCE is triggered.
> >
> > >
> > > > + 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);
> > >
> > > This deserves a comment in the source file.
> > >
> > > > + if (remote) {
> > > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> > >
> > > return here, drop else{}
> >
> > Well a of_node_put() is missing before the final return, and Yao Zi
> > noted me of it.
> >
> > >
> > > > + } else {
> > > > + remote = of_graph_get_remote_node(of_node,
> > > > output,
> > > > +
> > > > VSDC_OUTPUT_INTERFACE_DP);
> > > > + if (remote)
> > > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> > >
> > > return
> > >
> > > > + else
> > > > + ret = -ENODEV;
> > > > + }
> > > > +
> > > > + 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;
> > > > +
> > > > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > > > > of_node,
> > > > + output);
> > > > + if (intf == -ENODEV) {
> > > > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > > > output);
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > > GFP_KERNEL);
> > >
> > > devm_drm_bridge_alloc()
> > >
> > > > + if (!bridge)
> > > > + return ERR_PTR(-ENOMEM);
> > > > +
> > > > + bridge->crtc = crtc;
> > > > + bridge->intf = intf;
> > > > + bridge->base.funcs = &vs_bridge_funcs;
> > > > +
> > > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev-
> > > > >dev-
> > > > > of_node,
> > > > + output, intf);
> > > > + if (IS_ERR(next)) {
> > > > + ret = PTR_ERR(next);
> > > > + goto err_free_bridge;
> > > > + }
> > > > +
> > > > + bridge->next = next;
> > > > +
> > > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> > >
> > > Oh, so there is an encoder... Please drop drm_simple_encoder,
> > > it's
> > > deprecated, and try moving all the ifs to the encoder funcs.
> >
> > Ah? Is it really deprecated? I can find no source of this
> > deprecation.
> >
> > In addition, I think many drivers here are using a bridge as a
> > "better
> > encoder" because of the restriction of current encoder
> > implementation,
> > and I am doing the same thing. Either encoder functionality should
> > be
> > improved to on par with bridge, or such dummy encoders with a
> > bridge
> > should exist, and some helper for creating them should exist. It
> > might
> > be not drm_simple_encoder_init (because I can understand the
> > deprecation of other parts of the simple-kms routines, although I
> > see
> > no formal documentation mentioning it's deprecated, maybe I missed
> > some
> > newspaper?), but it should exist.
>
> I see some practice of passing NULL to drmm_plain_encoder_alloc()
> from
> the adp driver, however looks like this isn't always safe and on my
> test of this change on top of verisilicon driver (on top of v6.17-
> rc1)
> I got mysterious oops if the DC driver happens to be probed before
> the
> HDMI controller driver:
Well sorry for this disturbance...
It's because I used devm to allocate DRM objects, and get fixed when I
switched them to drmm.
> ```
> [ 28.372698] Unable to handle kernel access to user memory without
> uaccess routines at virtual address 0000000000000010
> [ 28.383596] Current (udev-worker) pgtable: 4K pagesize, 39-bit
> VAs,
> pgdp=0x0000000108532000
> [ 28.392180] [0000000000000010] pgd=0000000000000000,
> p4d=0000000000000000, pud=0000000000000000
> [ 28.401108] Oops [#1]
> [ 28.403405] Modules linked in: th1520_dw_hdmi verisilicon_dc(+)
> configfs dm_mod
> [ 28.410773] CPU: 1 UID: 0 PID: 527 Comm: (udev-worker) Not tainted
> 6.17.0-rc1+ #92 NONE
> [ 28.418890] Hardware name: Sipeed Lichee Pi 4A (DT)
> [ 28.423784] epc : drm_mode_config_cleanup+0xc6/0x224
> [ 28.428800] ra : drm_mode_config_cleanup+0xa4/0x224
> [ 28.433796] epc : ffffffff807d7016 ra : ffffffff807d6ff4 sp :
> ffffffc6004f3790
> [ 28.441038] gp : ffffffff81c52ae8 tp : ffffffd700873ac0 t0 :
> 0000000000000040
> [ 28.448279] t1 : 0000000000000000 t2 : 0000000000001c91 s0 :
> ffffffc6004f3810
> [ 28.455514] s1 : ffffffd70847f000 a0 : ffffffd70847e840 a1 :
> ffffffd70847f2f8
> [ 28.462770] a2 : ffffffff81c94178 a3 : 0000000000000002 a4 :
> 0000000000000000
> [ 28.470027] a5 : 0000000000000000 a6 : 0000000000000436 a7 :
> ffffffd70d3eb350
> [ 28.477263] s2 : fffffffffffffff8 s3 : ffffffd70847f2d0 s4 :
> ffffffd70847f2b8
> [ 28.484499] s5 : dead000000000100 s6 : ffffffd70847f018 s7 :
> 0000000000000000
> [ 28.491733] s8 : ffffffc6004f3d40 s9 : ffffffff01d04198 s10:
> ffffffc6004f3c80
> [ 28.498968] s11: ffffffff01d042c0 t3 : 0000000000000002 t4 :
> 0000000000000402
> [ 28.506200] t5 : ffffffd70847f1f8 t6 : ffffffd70847f200
> [ 28.511523] status: 0000000200000120 badaddr: 0000000000000010
> cause: 000000000000000d
> [ 28.519454] [<ffffffff807d7016>]
> drm_mode_config_cleanup+0xc6/0x224
> [ 28.525754] [<ffffffff807d75f8>]
> drm_mode_config_init_release+0xc/0x14
> [ 28.532312] [<ffffffff807d5b8e>] drm_managed_release+0x7a/0x100
> [ 28.538257] [<ffffffff807c6b9a>]
> devm_drm_dev_init_release+0x62/0x78
> [ 28.544641] [<ffffffff8084877a>] devm_action_release+0xe/0x18
> [ 28.550411] [<ffffffff80848b1a>] release_nodes+0x3e/0x94
> [ 28.555752] [<ffffffff80849cc6>] devres_release_all+0x72/0xb4
> [ 28.561529] [<ffffffff80843d74>] device_unbind_cleanup+0x10/0x58
> [ 28.567554] [<ffffffff80844510>] really_probe+0x184/0x30c
> [ 28.572973] [<ffffffff808446fc>] __driver_probe_device+0x64/0x10c
> [ 28.579086] [<ffffffff80844868>] driver_probe_device+0x2c/0xb4
> [ 28.584937] [<ffffffff80844a5a>] __driver_attach+0x9a/0x1a4
> [ 28.590530] [<ffffffff80842386>] bus_for_each_dev+0x62/0xb0
> [ 28.596130] [<ffffffff80843ea6>] driver_attach+0x1a/0x24
> [ 28.601461] [<ffffffff80843662>] bus_add_driver+0xf6/0x200
> [ 28.606972] [<ffffffff80845858>] driver_register+0x40/0xdc
> [ 28.612478] [<ffffffff80846bac>]
> __platform_driver_register+0x1c/0x24
> [ 28.618946] [<ffffffff01d0b020>]
> vs_dc_platform_driver_init+0x20/0x1000 [verisilicon_dc]
> [ 28.627075] [<ffffffff800158c6>] do_one_initcall+0x56/0x1cc
> [ 28.632675] [<ffffffff800c8116>] do_init_module+0x52/0x1dc
> [ 28.638187] [<ffffffff800c9bd2>] load_module+0x16e2/0x1afc
> [ 28.643696] [<ffffffff800ca1c6>] init_module_from_file+0x76/0xb0
> [ 28.649726] [<ffffffff800ca436>]
> __riscv_sys_finit_module+0x1fe/0x3cc
> [ 28.656195] [<ffffffff80cfee1e>] do_trap_ecall_u+0x296/0x370
> [ 28.661878] [<ffffffff80d09a56>] handle_exception+0x146/0x152
> [ 28.667656] Code: 8993 2d04 b903 0007 8513 ff87 1961 8e63 00f9
> 7d5c
> (6b9c) 9782
> [ 28.675233] ---[ end trace 0000000000000000 ]---
> ```
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 17:55 ` Icenowy Zheng
@ 2025-08-16 18:05 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-16 18:05 UTC (permalink / raw)
To: Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-17星期日的 01:55 +0800,Icenowy Zheng写道:
> 在 2025-08-16星期六的 20:45 +0300,Dmitry Baryshkov写道:
> > On Sun, Aug 17, 2025 at 12:48:42AM +0800, Icenowy Zheng wrote:
> > > 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > > > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > > >
8< Some contents got cut here ============
> > > > >
> >
> > Please don't follow that pattern. It breaks as soon as userspace
> > submits
> > an DRM_MODE_ATOMIC_TEST_ONLY commit. It's hard for encoders since
> > they
> > don't have a state, but bridges have proper drm_bridge_state.
> > Please
> > use
> > it.
>
> Yes I thought it have proper drm_bridge_state, but I cannot
> understand
> why I got always 0 when accessing bridge_state->output_bus_cfg.format
> in atomic_enable().
>
> If I follow this pattern, then when this problem is fixed, my driver
> can get fixed along with ingenic and meson.
Well I check the codebase, and the other encoder (root bridge) driver
that really sets output format, mtk_dpi, does the same thing by saving
the format in struct mtk_dpi.
I now have totally no idea about why this happened...
>
> >
> > >
> > > >
> > > > > +
> > > > > + 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);
> > > > > + 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;
> > > > > + }
> > > > > + WARN_ON_ONCE(i ==
> > > > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> > > >
> > > > This might trigger all static checkers in the universe. It's
> > > > not
> > > > really
> > > > possible, since you've checked it in the atomic_check(), but...
> > >
> > > Sigh I don't know how to properly describe it...
> > >
> > > I can only say something really bad happens if the previous
> > > WARN_ON_ONCE is triggered.
> >
> >
> > if (WARN_ON_ONCE())
> > return;
>
> Sounds reasonable, as it's a UB here.
>
> >
> > >
> > > >
> > > > > + 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);
> > > >
> > > > This deserves a comment in the source file.
> > > >
> > > > > + if (remote) {
> > > > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> > > >
> > > > return here, drop else{}
> > >
> > > Well a of_node_put() is missing before the final return, and Yao
> > > Zi
> > > noted me of it.
> >
> > You can put the node right after of_graph_get_remote_node(); You
> > don't
> > use any props from it.
> >
> > >
> > > >
> > > > > + } else {
> > > > > + remote = of_graph_get_remote_node(of_node,
> > > > > output,
> > > > > +
> > > > > VSDC_OUTPUT_INTERFACE_DP);
> > > > > + if (remote)
> > > > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> > > >
> > > > return
> > > >
> > > > > + else
> > > > > + ret = -ENODEV;
> > > > > + }
> > > > > +
> > > > > + 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;
> > > > > +
> > > > > + intf = vs_bridge_detect_output_interface(drm_dev-
> > > > > >dev-
> > > > > > of_node,
> > > > > + output);
> > > > > + if (intf == -ENODEV) {
> > > > > + dev_info(drm_dev->dev, "Skipping output
> > > > > %u\n",
> > > > > output);
> > > > > + return NULL;
> > > > > + }
> > > > > +
> > > > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > > > GFP_KERNEL);
> > > >
> > > > devm_drm_bridge_alloc()
> > > >
> > > > > + if (!bridge)
> > > > > + return ERR_PTR(-ENOMEM);
> > > > > +
> > > > > + bridge->crtc = crtc;
> > > > > + bridge->intf = intf;
> > > > > + bridge->base.funcs = &vs_bridge_funcs;
> > > > > +
> > > > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev-
> > > > > > dev-
> > > > > > of_node,
> > > > > + output, intf);
> > > > > + if (IS_ERR(next)) {
> > > > > + ret = PTR_ERR(next);
> > > > > + goto err_free_bridge;
> > > > > + }
> > > > > +
> > > > > + bridge->next = next;
> > > > > +
> > > > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> > > >
> > > > Oh, so there is an encoder... Please drop drm_simple_encoder,
> > > > it's
> > > > deprecated, and try moving all the ifs to the encoder funcs.
> > >
> > > Ah? Is it really deprecated? I can find no source of this
> > > deprecation.
> >
> > https://lore.kernel.org/dri-devel/20250401094056.32904-3-tzimmermann@suse.de/
> >
> > > In addition, I think many drivers here are using a bridge as a
> > > "better
> > > encoder" because of the restriction of current encoder
> > > implementation,
> > > and I am doing the same thing. Either encoder functionality
> > > should
> > > be
> > > improved to on par with bridge, or such dummy encoders with a
> > > bridge
> > > should exist, and some helper for creating them should exist. It
> > > might
> > > be not drm_simple_encoder_init (because I can understand the
> > > deprecation of other parts of the simple-kms routines, although I
> > > see
> > > no formal documentation mentioning it's deprecated, maybe I
> > > missed
> > > some
> > > newspaper?), but it should exist.
> >
> > Maybe we should explicitly document the status.
> >
> > Also, if you use non-simple encoders, you can actually have
> > functionality there.
>
> Yes I tried it, but then I realized that it's not a good pattern if a
> root bridge is present -- they represent the same thing in the
> hardware.
>
> >
> > >
> > > >
> > > > > + (intf ==
> > > > > VSDC_OUTPUT_INTERFACE_DPI) ?
> > > > > + DRM_MODE_ENCODER_DPI :
> > > > > + DRM_MODE_ENCODER_NONE);
> > > > > + if (ret) {
> > > > > + dev_err(drm_dev->dev,
> > > > > + "Cannot initialize encoder for output
> > > > > %u\n", output);
> > > > > + goto err_free_bridge;
> > > > > + }
> > > > > +
> > > > > + bridge->enc.possible_crtcs = drm_crtc_mask(&crtc-
> > > > > > base);
> > > > > +
> > > > > + ret = drm_bridge_attach(&bridge->enc, &bridge->base,
> > > > > NULL,
> > > > > + DRM_BRIDGE_ATTACH_NO_CONNECTO
> > > > > R)
> > > > > ;
> > > > > + if (ret) {
> > > > > + dev_err(drm_dev->dev,
> > > > > + "Cannot attach bridge for output
> > > > > %u\n",
> > > > > output);
> > > > > + goto err_cleanup_encoder;
> > > > > + }
> > > > > +
> > > > > + bridge->conn = drm_bridge_connector_init(drm_dev,
> > > > > &bridge-
> > > > > > enc);
> > > > > + if (IS_ERR(bridge->conn)) {
> > > > > + dev_err(drm_dev->dev,
> > > > > + "Cannot create connector for output
> > > > > %u\n",
> > > > > output);
> > > > > + ret = PTR_ERR(bridge->conn);
> > > > > + goto err_cleanup_encoder;
> > > > > + }
> > > > > + drm_connector_attach_encoder(bridge->conn, &bridge-
> > > > > > enc);
> > > > > +
> > > > > + return bridge;
> > > > > +
> > > > > +err_cleanup_encoder:
> > > > > + drm_encoder_cleanup(&bridge->enc);
> > > > > +err_free_bridge:
> > > > > + devm_kfree(drm_dev->dev, bridge);
> > > > > +
> > > > > + return ERR_PTR(ret);
> > > > > +}
> > > > > diff --git a/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > > > b/drivers/gpu/drm/verisilicon/vs_bridge.h
> > > > > new file mode 100644
> > > > > index 0000000000000..4a8a9eeb739f2
> > > > > --- /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..d1c91dd1354b4
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge_regs.h
> > > > > @@ -0,0 +1,47 @@
> > > > > +/* 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_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..46c4191b82f49
> > > > > --- /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 "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)
> > > >
> > > > lowercase hex, please.
> > >
> > > Why? I didn't see any document enforces this.
> >
> > I think, it's a generic suggestion for the sake of readability.
> >
> > >
> > > >
> > > > > + 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,
> > > > > + .destroy = drm_crtc_cleanup,
> > > > > + .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 = devm_kzalloc(drm_dev->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)) {
> > > > > + dev_err(drm_dev->dev, "Couldn't create the
> > > > > primary
> > > > > plane\n");
> > > > > + return ERR_PTR(PTR_ERR(primary));
> > > > > + }
> > > > > +
> > > > > + ret = drm_crtc_init_with_planes(drm_dev, &vcrtc-
> > > > > >base,
> > > > > + primary,
> > > > > + NULL,
> > > > > + &vs_crtc_funcs,
> > > > > + NULL);
> > > > > + if (ret) {
> > > > > + dev_err(drm_dev->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..98384559568c4
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/verisilicon/vs_dc.c
> > > > > @@ -0,0 +1,233 @@
> > > > > +// 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,
> > > > > + .cache_type = REGCACHE_NONE,
> > > > > +};
> > > > > +
> > > > > +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[0].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(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(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(dev, "ahb");
> > > >
> > > > devm_clk_get_enabled() ?
> > > >
> > > > > + 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;
> > > > > + }
> > > > > +
> > > > > + ret = clk_prepare_enable(dc->core_clk);
> > > > > + if (ret) {
> > > > > + dev_err(dev, "can't enable core clock\n");
> > > > > + goto err_rst_assert;
> > > > > + }
> > > > > +
> > > > > + ret = clk_prepare_enable(dc->axi_clk);
> > > > > + if (ret) {
> > > > > + dev_err(dev, "can't enable axi clock\n");
> > > > > + goto err_core_clk_disable;
> > > > > + }
> > > > > +
> > > > > + ret = clk_prepare_enable(dc->ahb_clk);
> > > > > + if (ret) {
> > > > > + dev_err(dev, "can't enable ahb clock\n");
> > > > > + goto err_axi_clk_disable;
> > > > > + }
> > > > > +
> > > > > + regs = devm_platform_ioremap_resource(pdev, 0);
> > > > > + if (IS_ERR(regs)) {
> > > > > + dev_err(dev, "can't map registers");
> > > > > + ret = PTR_ERR(regs);
> > > > > + goto err_ahb_clk_disable;
> > > > > + }
> > > > > +
> > > > > + dc->regs = devm_regmap_init_mmio(dev, regs,
> > > > > &vs_dc_regmap_cfg);
> > > > > + if (IS_ERR(dc->regs)) {
> > > > > + ret = PTR_ERR(dc->regs);
> > > > > + goto err_ahb_clk_disable;
> > > > > + }
> > > > > +
> > > > > + ret = vs_fill_chip_identity(dc->regs, &dc->identity);
> > > >
> > > > I'd say, this should be a part of the DT bindings.
> > > >
> > > > > + if (ret)
> > > > > + goto err_ahb_clk_disable;
> > > > > +
> > > > > + 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_ahb_clk_disable;
> > > > > + }
> > > > > +
> > > > > + ret = devm_request_irq(dev, irq, vs_dc_irq_handler,
> > > > > 0,
> > > > > + dev_name(dev), dc);
> > > >
> > > > Are we ready to handle the IRQ here?
> > > >
> > > > > + if (ret) {
> > > > > + dev_err(dev, "can't request irq\n");
> > > > > + goto err_ahb_clk_disable;
> > > > > + }
> > > > > +
> > > > > + dev_set_drvdata(dev, dc);
> > > > > +
> > > > > + ret = vs_drm_initialize(dc, pdev);
> > > > > + if (ret)
> > > > > + goto err_ahb_clk_disable;
> > > > > +
> > > > > + return 0;
> > > > > +
> > > > > +err_ahb_clk_disable:
> > > > > + clk_disable_unprepare(dc->ahb_clk);
> > > > > +err_axi_clk_disable:
> > > > > + clk_disable_unprepare(dc->axi_clk);
> > > > > +err_core_clk_disable:
> > > > > + clk_disable_unprepare(dc->core_clk);
> > > > > +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);
> > > > > +
> > > > > + clk_disable_unprepare(dc->ahb_clk);
> > > > > + clk_disable_unprepare(dc->axi_clk);
> > > > > + clk_disable_unprepare(dc->core_clk);
> > > > > + 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);
> > > >
> > > > I'd suggest inlining simple wrappers.
> > >
> > > Well I am going to divider the code to non-DRM things and DRM
> > > things
> > > here, so vs_drm_shutdown_handler is in the DRM things part
> > > instead.
> >
> > Ack. It might be my personal preference not to have extra wrappers.
> >
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-16 17:22 ` Icenowy Zheng
2025-08-16 18:01 ` Icenowy Zheng
@ 2025-08-16 18:10 ` Dmitry Baryshkov
1 sibling, 0 replies; 42+ messages in thread
From: Dmitry Baryshkov @ 2025-08-16 18:10 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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
On Sun, Aug 17, 2025 at 01:22:07AM +0800, Icenowy Zheng wrote:
> 在 2025-08-17星期日的 00:48 +0800,Icenowy Zheng写道:
> > 在 2025-08-16星期六的 19:18 +0300,Dmitry Baryshkov写道:
> > > On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > > > 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>
> > > > ---
> > > > 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 | 330
> > > > ++++++++++++++++++
> > > > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > > > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > > > 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 | 233
> > > > +++++++++++++
> > > > 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 | 166 +++++++++
> > > > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > > > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > > > --- a/drivers/gpu/drm/Kconfig
> > > > +++ b/drivers/gpu/drm/Kconfig
> > > > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> > > >
> > > > source "drivers/gpu/drm/imagination/Kconfig"
> > > >
> > > > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > > > +
> > > > config DRM_HYPERV
> > > > tristate "DRM Support for Hyper-V synthetic video device"
> > > > depends on DRM && PCI && HYPERV
> > > > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > > > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > > > --- a/drivers/gpu/drm/Makefile
> > > > +++ b/drivers/gpu/drm/Makefile
> > > > @@ -231,6 +231,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..c8caf31fac7d6
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > > > @@ -0,0 +1,330 @@
> > > > +// 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_RGB888_1X24, false,
> > > > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > > > + { 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);
> > > > + struct drm_connector *conn = conn_state->connector;
> > > > + u32 *output_fmts;
> > > > + unsigned int i;
> > > > +
> > > > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> > >
> > > This kind of checks looks like there should be a drm_encoder
> > > handled
> > > by
> > > the same driver. Or maybe it's better to have two sets of funcs
> > > structures, one for the DPI, one for DP.
> >
> > Well these functions used to be for an encoder, however I found that
> > encoders cannot take part in format negotiation, and at least some
> > source says encoder is deprecated in this situation and a first
> > bridge
> > in the bridge chain is better here.
> >
> > A simple encoder is created by this part of driver, but all its works
> > are moved to this bridge, similar to what other drivers with bridge
> > chaining support do.
> >
> > >
> > > > + *num_output_fmts = 1;
> > > > + 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) {
> > > > + if (conn->display_info.num_bus_formats &&
> > > > + conn->display_info.bus_formats)
> > > > + output_fmts[0] = conn-
> > > > > display_info.bus_formats[0];
> > > > + else
> > > > + output_fmts[0] = MEDIA_BUS_FMT_FIXED;
> > > > + } else {
> > > > + for (i = 0; i < *num_output_fmts; i++)
> > > > + output_fmts[i] =
> > > > vsdc_dp_supported_fmts[i].linux_fmt;
> > >
> > > memcpy(a, b, min(ARRAY_SIZE(), num_output_fmts)) ?
> >
> > vsdc_dp_supported_fmts is a map of linux_fmt to hardware-specific
> > parameter, so memcpy won't work here.
> >
> > >
> > > > + }
> > > > +
> > > > + 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;
> > >
> > > > + break;
> > > > +
> > > > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> > >
> > > 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;
> > >
> > > You are saving a state value into a non-state variable. There is no
> > > guarantee that this atomic_check() will be followed by the actual
> > > commit. So, either you have to use a struct that extends
> > > drm_bridge_state here or store the output_bus_fmt during
> > > atomic_enable().
> >
> > In fact I don't want to save it -- the kernel is quirky here and this
> > value does not get passed into atomic_enable. I mimicked what other
> > drivers do. See ingenic_drm_bridge_atomic_check() in ingenic/ingenic-
> > drm-drv.c and meson_encoder_hdmi_atomic_check() in
> > meson/meson_encoder_hdmi.c .
> >
> > >
> > > > +
> > > > + 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);
> > > > + 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;
> > > > + }
> > > > + WARN_ON_ONCE(i ==
> > > > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > > > + dp_fmt = vsdc_dp_supported_fmts[i].vsdc_fmt;
> > >
> > > This might trigger all static checkers in the universe. It's not
> > > really
> > > possible, since you've checked it in the atomic_check(), but...
> >
> > Sigh I don't know how to properly describe it...
> >
> > I can only say something really bad happens if the previous
> > WARN_ON_ONCE is triggered.
> >
> > >
> > > > + 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);
> > >
> > > This deserves a comment in the source file.
> > >
> > > > + if (remote) {
> > > > + ret = VSDC_OUTPUT_INTERFACE_DPI;
> > >
> > > return here, drop else{}
> >
> > Well a of_node_put() is missing before the final return, and Yao Zi
> > noted me of it.
> >
> > >
> > > > + } else {
> > > > + remote = of_graph_get_remote_node(of_node,
> > > > output,
> > > > +
> > > > VSDC_OUTPUT_INTERFACE_DP);
> > > > + if (remote)
> > > > + ret = VSDC_OUTPUT_INTERFACE_DP;
> > >
> > > return
> > >
> > > > + else
> > > > + ret = -ENODEV;
> > > > + }
> > > > +
> > > > + 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;
> > > > +
> > > > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > > > > of_node,
> > > > + output);
> > > > + if (intf == -ENODEV) {
> > > > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > > > output);
> > > > + return NULL;
> > > > + }
> > > > +
> > > > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > > > GFP_KERNEL);
> > >
> > > devm_drm_bridge_alloc()
> > >
> > > > + if (!bridge)
> > > > + return ERR_PTR(-ENOMEM);
> > > > +
> > > > + bridge->crtc = crtc;
> > > > + bridge->intf = intf;
> > > > + bridge->base.funcs = &vs_bridge_funcs;
> > > > +
> > > > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev-
> > > > > of_node,
> > > > + output, intf);
> > > > + if (IS_ERR(next)) {
> > > > + ret = PTR_ERR(next);
> > > > + goto err_free_bridge;
> > > > + }
> > > > +
> > > > + bridge->next = next;
> > > > +
> > > > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> > >
> > > Oh, so there is an encoder... Please drop drm_simple_encoder, it's
> > > deprecated, and try moving all the ifs to the encoder funcs.
> >
> > Ah? Is it really deprecated? I can find no source of this
> > deprecation.
> >
> > In addition, I think many drivers here are using a bridge as a
> > "better
> > encoder" because of the restriction of current encoder
> > implementation,
> > and I am doing the same thing. Either encoder functionality should be
> > improved to on par with bridge, or such dummy encoders with a bridge
> > should exist, and some helper for creating them should exist. It
> > might
> > be not drm_simple_encoder_init (because I can understand the
> > deprecation of other parts of the simple-kms routines, although I see
> > no formal documentation mentioning it's deprecated, maybe I missed
> > some
> > newspaper?), but it should exist.
>
> I see some practice of passing NULL to drmm_plain_encoder_alloc() from
> the adp driver, however looks like this isn't always safe and on my
> test of this change on top of verisilicon driver (on top of v6.17-rc1)
> I got mysterious oops if the DC driver happens to be probed before the
> HDMI controller driver:
I think you can pass NULL for funcs only if you use drmm_* functions to
handle encoder. This way it gets cleaned before
drm_mode_config_cleanup() gets called.
> [ 28.519454] [<ffffffff807d7016>] drm_mode_config_cleanup+0xc6/0x224
> [ 28.525754] [<ffffffff807d75f8>]
> drm_mode_config_init_release+0xc/0x14
> [ 28.532312] [<ffffffff807d5b8e>] drm_managed_release+0x7a/0x100
> [ 28.538257] [<ffffffff807c6b9a>] devm_drm_dev_init_release+0x62/0x78
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
` (2 preceding siblings ...)
2025-08-16 16:18 ` Dmitry Baryshkov
@ 2025-08-17 18:39 ` Drew Fustini
2025-08-18 7:08 ` Icenowy Zheng
2025-08-20 21:21 ` Michal Wilczynski
4 siblings, 1 reply; 42+ messages in thread
From: Drew Fustini @ 2025-08-17 18:39 UTC (permalink / raw)
To: Icenowy Zheng
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
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 Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> 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>
Thanks for working on this!
[snip]
> 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..25d6e01cc8b71
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
[snip]
> +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;
> +
> + DRM_DEBUG_DRIVER("Updating output %d primary plane\n", output);
clang flagged this when building. I think this needs to be after the
line below that assigns vcrtc->id to output.
> +
> + vcrtc = drm_crtc_to_vs_crtc(crtc);
> + output = vcrtc->id;
Thanks,
Drew
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-17 18:39 ` Drew Fustini
@ 2025-08-18 7:08 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-18 7:08 UTC (permalink / raw)
To: Drew Fustini
Cc: Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, David Airlie,
Simona Vetter, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
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
在 2025-08-17星期日的 11:39 -0700,Drew Fustini写道:
> On Fri, Aug 15, 2025 at 12:40:43AM +0800, Icenowy Zheng wrote:
> > 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>
>
> Thanks for working on this!
>
> [snip]
> > 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..25d6e01cc8b71
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_primary_plane.c
> [snip]
> > +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;
> > +
> > + DRM_DEBUG_DRIVER("Updating output %d primary plane\n",
> > output);
>
> clang flagged this when building. I think this needs to be after the
> line below that assigns vcrtc->id to output.
Oops got silly here...
>
> > +
> > + vcrtc = drm_crtc_to_vs_crtc(crtc);
> > + output = vcrtc->id;
>
> Thanks,
> Drew
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-16 17:10 ` Icenowy Zheng
@ 2025-08-18 7:45 ` Troy Mitchell
2025-08-18 7:47 ` Icenowy Zheng
0 siblings, 1 reply; 42+ messages in thread
From: Troy Mitchell @ 2025-08-18 7:45 UTC (permalink / raw)
To: Icenowy Zheng, Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv, Troy Mitchell
On Sun, Aug 17, 2025 at 01:10:44AM +0800, Icenowy Zheng wrote:
> 在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> > On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> > > 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>
> > > ---
> > > MAINTAINERS | 1 +
> > > drivers/gpu/drm/bridge/Kconfig | 10 ++
> > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170
> > > ++++++++++++++++++++++++
> > > 4 files changed, 182 insertions(+)
> > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > >
> > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > index fe168477caa45..eb84e36ded6d5 100644
> > > --- a/MAINTAINERS
> > > +++ b/MAINTAINERS
> > > @@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
> > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> > > --- a/drivers/gpu/drm/bridge/Makefile
> > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-sii8620.o
> > > obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > > obj-$(CONFIG_DRM_SII9234) += sii9234.o
> > > obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > @@ -0,0 +1,170 @@
> > > +// 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
> > > },
> > > + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000
> > > }
> > > +};
> > > +
> > > +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;
> >
> > We should rewrite DW bridge into HDMI ops. It would help us to get
> > rid
> > of such functions. With it in place it will be handled by the generic
> > TMDS clock rate check.
>
> Yes, but this mode_valid hook here is part of the private interface of
> DW bridge, instead of implementing anything for other parts of DRM
> subsystem.
>
> Even if the TMDS clock rate check is utilized by the DW bridge, the
> bridge's interface should be modified to expose something to set the
> maximum clock rate.
>
> >
> > > +
> > > + return MODE_OK;
> > > +}
> > > +
> > > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi, void
> > > *data,
> > > + unsigned long mpixelclock)
> > > +{
> > > + const struct th1520_hdmi_phy_params *params =
> > > th1520_hdmi_phy_params;
> > > +
> > > + for (; params->mpixelclock != ~0UL; ++params) {
> > > + if (mpixelclock <= params->mpixelclock)
> > > + break;
> >
> > for (...) {
> > if (mpixelclock <= params->mpixelclock)
> > return th1520_program_phy();
>
> There's no such a function here, and this check isn't used for another
> time, so having the matching code and programming code extracted out
> can help nothing.
I think Dmitry meant that the following code should be moved into
a new function, th1520_program_phy().
This makes the code cleaner and also avoids one extra if check.
- Troy
>
> > }
> >
> > return -EINVAL;
> >
> > > + }
> > > +
> > > + if (params->mpixelclock == ~0UL)
> > > + return -EINVAL;
> > > +
> > > + 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);
> > > +
> > > + return 0;
> > > +}
> > > +
> > > +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.50.1
> > >
> >
>
> _______________________________________________
> linux-riscv mailing list
> linux-riscv@lists.infradead.org
> http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-18 7:45 ` Troy Mitchell
@ 2025-08-18 7:47 ` Icenowy Zheng
2025-08-18 7:54 ` Troy Mitchell
2025-08-21 11:38 ` Dmitry Baryshkov
0 siblings, 2 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-18 7:47 UTC (permalink / raw)
To: Troy Mitchell, Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
在 2025-08-18星期一的 15:45 +0800,Troy Mitchell写道:
> On Sun, Aug 17, 2025 at 01:10:44AM +0800, Icenowy Zheng wrote:
> > 在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> > > On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> > > > 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>
> > > > ---
> > > > MAINTAINERS | 1 +
> > > > drivers/gpu/drm/bridge/Kconfig | 10 ++
> > > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170
> > > > ++++++++++++++++++++++++
> > > > 4 files changed, 182 insertions(+)
> > > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > >
> > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > index fe168477caa45..eb84e36ded6d5 100644
> > > > --- a/MAINTAINERS
> > > > +++ b/MAINTAINERS
> > > > @@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
> > > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > > @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> > > > --- a/drivers/gpu/drm/bridge/Makefile
> > > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-
> > > > sii8620.o
> > > > obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > > > obj-$(CONFIG_DRM_SII9234) += sii9234.o
> > > > obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> > > > --- /dev/null
> > > > +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > @@ -0,0 +1,170 @@
> > > > +// 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
> > > > },
> > > > + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
> > > > 0x0000
> > > > }
> > > > +};
> > > > +
> > > > +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;
> > >
> > > We should rewrite DW bridge into HDMI ops. It would help us to
> > > get
> > > rid
> > > of such functions. With it in place it will be handled by the
> > > generic
> > > TMDS clock rate check.
> >
> > Yes, but this mode_valid hook here is part of the private interface
> > of
> > DW bridge, instead of implementing anything for other parts of DRM
> > subsystem.
> >
> > Even if the TMDS clock rate check is utilized by the DW bridge, the
> > bridge's interface should be modified to expose something to set
> > the
> > maximum clock rate.
> >
> > >
> > > > +
> > > > + return MODE_OK;
> > > > +}
> > > > +
> > > > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi,
> > > > void
> > > > *data,
> > > > + unsigned long mpixelclock)
> > > > +{
> > > > + const struct th1520_hdmi_phy_params *params =
> > > > th1520_hdmi_phy_params;
> > > > +
> > > > + for (; params->mpixelclock != ~0UL; ++params) {
> > > > + if (mpixelclock <= params->mpixelclock)
> > > > + break;
> > >
> > > for (...) {
> > > if (mpixelclock <= params->mpixelclock)
> > > return th1520_program_phy();
> >
> > There's no such a function here, and this check isn't used for
> > another
> > time, so having the matching code and programming code extracted
> > out
> > can help nothing.
> I think Dmitry meant that the following code should be moved into
> a new function, th1520_program_phy().
>
> This makes the code cleaner and also avoids one extra if check.
As there's no code reuse, it does not make code cleaner.
>
> - Troy
> >
> > > }
> > >
> > > return -EINVAL;
> > >
> > > > + }
> > > > +
> > > > + if (params->mpixelclock == ~0UL)
> > > > + return -EINVAL;
> > > > +
> > > > + 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);
> > > > +
> > > > + return 0;
> > > > +}
> > > > +
> > > > +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.50.1
> > > >
> > >
> >
> > _______________________________________________
> > linux-riscv mailing list
> > linux-riscv@lists.infradead.org
> > http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-18 7:47 ` Icenowy Zheng
@ 2025-08-18 7:54 ` Troy Mitchell
2025-08-21 11:38 ` Dmitry Baryshkov
1 sibling, 0 replies; 42+ messages in thread
From: Troy Mitchell @ 2025-08-18 7:54 UTC (permalink / raw)
To: Icenowy Zheng, Troy Mitchell, Dmitry Baryshkov
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, Han Gao, Yao Zi,
dri-devel, devicetree, linux-kernel, linux-riscv
On Mon, Aug 18, 2025 at 03:47:28PM +0800, Icenowy Zheng wrote:
> 在 2025-08-18星期一的 15:45 +0800,Troy Mitchell写道:
> > On Sun, Aug 17, 2025 at 01:10:44AM +0800, Icenowy Zheng wrote:
> > > 在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> > > > On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> > > > > 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>
> > > > > ---
[...]
> > > > > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi,
> > > > > void
> > > > > *data,
> > > > > + unsigned long mpixelclock)
> > > > > +{
> > > > > + const struct th1520_hdmi_phy_params *params =
> > > > > th1520_hdmi_phy_params;
> > > > > +
> > > > > + for (; params->mpixelclock != ~0UL; ++params) {
> > > > > + if (mpixelclock <= params->mpixelclock)
> > > > > + break;
> > > >
> > > > for (...) {
> > > > if (mpixelclock <= params->mpixelclock)
> > > > return th1520_program_phy();
> > >
> > > There's no such a function here, and this check isn't used for
> > > another
> > > time, so having the matching code and programming code extracted
> > > out
> > > can help nothing.
> > I think Dmitry meant that the following code should be moved into
> > a new function, th1520_program_phy().
> >
> > This makes the code cleaner and also avoids one extra if check.
>
> As there's no code reuse, it does not make code cleaner.
I respect your opinion, but let's also see what Dmitry has to say.
- Troy
>
> >
> > - Troy
> > >
> > > > }
> > > >
> > > > return -EINVAL;
> > > >
> > > > > + }
> > > > > +
> > > > > + if (params->mpixelclock == ~0UL)
> > > > > + return -EINVAL;
> > > > > +
> > > > > + 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);
> > > > > +
> > > > > + return 0;
> > > > > +}
> > > > > +
> > > > > +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.50.1
> > > > >
> > > >
> > >
> > > _______________________________________________
> > > linux-riscv mailing list
> > > linux-riscv@lists.infradead.org
> > > http://lists.infradead.org/mailman/listinfo/linux-riscv
>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
` (3 preceding siblings ...)
2025-08-17 18:39 ` Drew Fustini
@ 2025-08-20 21:21 ` Michal Wilczynski
2025-08-21 3:48 ` Icenowy Zheng
2025-08-21 9:38 ` Maud Spierings
4 siblings, 2 replies; 42+ messages in thread
From: Michal Wilczynski @ 2025-08-20 21:21 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 8/14/25 18:40, Icenowy Zheng wrote:
> 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>
> ---
> 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 | 330 ++++++++++++++++++
> drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> 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 | 233 +++++++++++++
> 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 | 166 +++++++++
> .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
>
> source "drivers/gpu/drm/imagination/Kconfig"
>
> +source "drivers/gpu/drm/verisilicon/Kconfig"
> +
> config DRM_HYPERV
> tristate "DRM Support for Hyper-V synthetic video device"
> depends on DRM && PCI && HYPERV
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -231,6 +231,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..c8caf31fac7d6
> --- /dev/null
> +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> @@ -0,0 +1,330 @@
> +// 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_RGB888_1X24, false, VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> + { 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);
> + struct drm_connector *conn = conn_state->connector;
> + u32 *output_fmts;
> + unsigned int i;
> +
> + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> + *num_output_fmts = 1;
> + 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) {
> + if (conn->display_info.num_bus_formats &&
> + conn->display_info.bus_formats)
> + output_fmts[0] = conn->display_info.bus_formats[0];
> + else
> + output_fmts[0] = 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)
> + break;
> +
> + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> +}
> +
> +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);
> + 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;
> + }
> + WARN_ON_ONCE(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> + 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;
> + }
> +
> + 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;
> +
> + intf = vs_bridge_detect_output_interface(drm_dev->dev->of_node,
> + output);
> + if (intf == -ENODEV) {
> + dev_info(drm_dev->dev, "Skipping output %u\n", output);
> + return NULL;
> + }
> +
> + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge), GFP_KERNEL);
> + if (!bridge)
> + return ERR_PTR(-ENOMEM);
> +
> + bridge->crtc = crtc;
> + bridge->intf = intf;
> + bridge->base.funcs = &vs_bridge_funcs;
So I am trying to make it work on JH7110 as well, and here is the
problem:
[ 5.564433] ------------[ cut here ]------------
[ 5.569161] refcount_t: addition on 0; use-after-free.
[ 5.574485] WARNING: CPU: 2 PID: 71 at lib/refcount.c:25 refcount_warn_saturate+0x110/0x162
[ 5.574537] Modules linked in:
[ 5.574560] CPU: 2 UID: 0 PID: 71 Comm: kworker/u17:2 Not tainted 6.17.0-rc1-g60ec647ec20c-dirty #77 NONE
[ 5.574596] Hardware name: StarFive VisionFive 2 v1.2A (DT)
[ 5.574613] Workqueue: events_unbound deferred_probe_work_func
[ 5.574654] epc : refcount_warn_saturate+0x110/0x162
[ 5.574682] ra : refcount_warn_saturate+0x110/0x162
[ 5.574710] epc : ffffffff81336608 ra : ffffffff81336608 sp : ffffffc6006e7530
[ 5.574732] gp : ffffffff8514c1c0 tp : ffffffd6c14ba580 t0 : ffffffc6006e7134
[ 5.574753] t1 : fffffffef0a29898 t2 : 5f746e756f636665 s0 : ffffffc6006e7550
[ 5.574774] s1 : ffffffff83c058a8 a0 : 000000000000002a a1 : 0000000000000004
[ 5.574794] a2 : 0000000000000000 a3 : ffffffff801ef586 a4 : 0000000000000000
[ 5.574813] a5 : 0000000000000000 a6 : fffffffef0a29899 a7 : 0000000000000003
[ 5.574833] s2 : ffffffd6c35e68e8 s3 : ffffffd6c35e6990 s4 : ffffffd6c33dc008
[ 5.574854] s5 : 0000000000000000 s6 : 1ffffffad867b801 s7 : 0000000000000000
[ 5.574873] s8 : ffffffd6c35e6990 s9 : 0000000000000000 s10: ffffffd6c3099058
[ 5.574894] s11: 1ffffffad861320b t3 : 1ffffff8c00dce34 t4 : fffffffef0a29898
[ 5.574915] t5 : fffffffef0a29899 t6 : ffffffc6006e6f38
[ 5.574933] status: 0000000200000120 badaddr: 0000000000000000 cause: 0000000000000003
[ 5.574952] [<ffffffff81336608>] refcount_warn_saturate+0x110/0x162
[ 5.574985] [<ffffffff8193a506>] drm_bridge_get+0x66/0x6e
[ 5.575022] [<ffffffff8001f26e>] drm_bridge_attach+0x54/0x5a2
[ 5.575055] [<ffffffff8002740e>] vs_bridge_init+0x3bc/0x4ba
[ 5.575087] [<ffffffff82313026>] vs_drm_initialize+0xe0/0x2f2
[ 5.575126] [<ffffffff80027ca4>] vs_dc_probe+0x758/0x85c
[ 5.575156] [<ffffffff8233123c>] platform_probe+0xac/0x138
[ 5.575186] [<ffffffff8232b2a8>] really_probe+0x164/0x59e
[ 5.575221] [<ffffffff8232b818>] __driver_probe_device+0x136/0x266
[ 5.575257] [<ffffffff8232bb18>] driver_probe_device+0x56/0x214
[ 5.575292] [<ffffffff8232bdf0>] __device_attach_driver+0x11a/0x278
[ 5.575329] [<ffffffff823272d8>] bus_for_each_drv+0xea/0x16e
[ 5.575363] [<ffffffff8232c680>] __device_attach+0x160/0x2d4
[ 5.575398] [<ffffffff8232ca06>] device_initial_probe+0xe/0x16
[ 5.575434] [<ffffffff823291d8>] bus_probe_device+0xfe/0x134
[ 5.575468] [<ffffffff82329c62>] deferred_probe_work_func+0x128/0x1d6
[ 5.575503] [<ffffffff80126aae>] process_one_work+0x5c0/0xe76
[ 5.575543] [<ffffffff80129da6>] worker_thread+0x6d4/0x1316
[ 5.575572] [<ffffffff801429ce>] kthread+0x29a/0x570
[ 5.575608] [<ffffffff8004693e>] ret_from_fork_kernel+0x10/0x9a
[ 5.575643] [<ffffffff835f55f6>] ret_from_fork_kernel_asm+0x16/0x18
[ 5.575682] ---[ end trace 0000000000000000 ]---
When a bridge is allocated with kzalloc, its kref count is initialized
to zero. The drm_bridge_attach() function then calls drm_bridge_get() on
this bridge, which sees the zero refcount and triggers a use-after-free
warning because it assumes the object has already been freed.
To fix this, the bridge's refcount must be initialized with kref_init()
after allocation and before it's attached.
> +
> + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev->of_node,
> + output, intf);
> + if (IS_ERR(next)) {
> + ret = PTR_ERR(next);
> + goto err_free_bridge;
> + }
> +
> + bridge->next = next;
> +
> + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> + (intf == VSDC_OUTPUT_INTERFACE_DPI) ?
> + DRM_MODE_ENCODER_DPI :
> + DRM_MODE_ENCODER_NONE);
> + if (ret) {
> + dev_err(drm_dev->dev,
> + "Cannot initialize encoder for output %u\n", output);
> + goto err_free_bridge;
> + }
> +
> + 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) {
> + dev_err(drm_dev->dev,
> + "Cannot attach bridge for output %u\n", output);
> + goto err_cleanup_encoder;
> + }
> +
> + bridge->conn = drm_bridge_connector_init(drm_dev, &bridge->enc);
> + if (IS_ERR(bridge->conn)) {
> + dev_err(drm_dev->dev,
> + "Cannot create connector for output %u\n", output);
> + ret = PTR_ERR(bridge->conn);
> + goto err_cleanup_encoder;
> + }
> + drm_connector_attach_encoder(bridge->conn, &bridge->enc);
> +
> + return bridge;
> +
> +err_cleanup_encoder:
> + drm_encoder_cleanup(&bridge->enc);
> +err_free_bridge:
> + devm_kfree(drm_dev->dev, bridge);
> +
> + return ERR_PTR(ret);
> +}
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-20 21:21 ` Michal Wilczynski
@ 2025-08-21 3:48 ` Icenowy Zheng
2025-08-21 9:38 ` Maud Spierings
1 sibling, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-21 3:48 UTC (permalink / raw)
To: Michal Wilczynski, 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
在 2025-08-20星期三的 23:21 +0200,Michal Wilczynski写道:
>
>
> On 8/14/25 18:40, Icenowy Zheng wrote:
> > 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>
> > ---
> > 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 | 330
> > ++++++++++++++++++
> > drivers/gpu/drm/verisilicon/vs_bridge.h | 40 +++
> > drivers/gpu/drm/verisilicon/vs_bridge_regs.h | 47 +++
> > 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 | 233 +++++++++++++
> > 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 | 166 +++++++++
> > .../drm/verisilicon/vs_primary_plane_regs.h | 53 +++
> > 21 files changed, 1819 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 f7ea8e895c0c0..33601485ecdba 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -396,6 +396,8 @@ source "drivers/gpu/drm/sprd/Kconfig"
> >
> > source "drivers/gpu/drm/imagination/Kconfig"
> >
> > +source "drivers/gpu/drm/verisilicon/Kconfig"
> > +
> > config DRM_HYPERV
> > tristate "DRM Support for Hyper-V synthetic video device"
> > depends on DRM && PCI && HYPERV
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 4dafbdc8f86ac..32ed4cf9df1bd 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -231,6 +231,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..c8caf31fac7d6
> > --- /dev/null
> > +++ b/drivers/gpu/drm/verisilicon/vs_bridge.c
> > @@ -0,0 +1,330 @@
> > +// 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_RGB888_1X24, false,
> > VSDC_DISP_DP_CONFIG_FMT_RGB888 },
> > + { 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);
> > + struct drm_connector *conn = conn_state->connector;
> > + u32 *output_fmts;
> > + unsigned int i;
> > +
> > + if (vbridge->intf == VSDC_OUTPUT_INTERFACE_DPI)
> > + *num_output_fmts = 1;
> > + 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) {
> > + if (conn->display_info.num_bus_formats &&
> > + conn->display_info.bus_formats)
> > + output_fmts[0] = conn-
> > >display_info.bus_formats[0];
> > + else
> > + output_fmts[0] = 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)
> > + break;
> > +
> > + return !(i == ARRAY_SIZE(vsdc_dp_supported_fmts));
> > +}
> > +
> > +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);
> > + 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;
> > + }
> > + WARN_ON_ONCE(i ==
> > ARRAY_SIZE(vsdc_dp_supported_fmts));
> > + 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;
> > + }
> > +
> > + 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;
> > +
> > + intf = vs_bridge_detect_output_interface(drm_dev->dev-
> > >of_node,
> > + output);
> > + if (intf == -ENODEV) {
> > + dev_info(drm_dev->dev, "Skipping output %u\n",
> > output);
> > + return NULL;
> > + }
> > +
> > + bridge = devm_kzalloc(drm_dev->dev, sizeof(*bridge),
> > GFP_KERNEL);
> > + if (!bridge)
> > + return ERR_PTR(-ENOMEM);
> > +
> > + bridge->crtc = crtc;
> > + bridge->intf = intf;
> > + bridge->base.funcs = &vs_bridge_funcs;
>
> So I am trying to make it work on JH7110 as well, and here is the
> problem:
>
> [ 5.564433] ------------[ cut here ]------------
>
>
> [ 5.569161] refcount_t: addition on 0; use-after-
> free.
>
> [ 5.574485] WARNING: CPU: 2 PID: 71 at lib/refcount.c:25
> refcount_warn_saturate+0x110/0x162
>
> [ 5.574537] Modules linked
> in:
>
>
> [ 5.574560] CPU: 2 UID: 0 PID: 71 Comm: kworker/u17:2 Not tainted
> 6.17.0-rc1-g60ec647ec20c-dirty #77
> NONE
>
> [ 5.574596] Hardware name: StarFive VisionFive 2 v1.2A
> (DT)
>
> [ 5.574613] Workqueue: events_unbound
> deferred_probe_work_func
>
>
> [ 5.574654] epc :
> refcount_warn_saturate+0x110/0x162
>
>
> [ 5.574682] ra :
> refcount_warn_saturate+0x110/0x162
>
>
> [ 5.574710] epc : ffffffff81336608 ra : ffffffff81336608 sp :
> ffffffc6006e7530
>
> [ 5.574732] gp : ffffffff8514c1c0 tp : ffffffd6c14ba580 t0 :
> ffffffc6006e7134
>
> [ 5.574753] t1 : fffffffef0a29898 t2 : 5f746e756f636665 s0 :
> ffffffc6006e7550
>
> [ 5.574774] s1 : ffffffff83c058a8 a0 : 000000000000002a a1 :
> 0000000000000004
>
> [ 5.574794] a2 : 0000000000000000 a3 : ffffffff801ef586 a4 :
> 0000000000000000
>
> [ 5.574813] a5 : 0000000000000000 a6 : fffffffef0a29899 a7 :
> 0000000000000003
>
> [ 5.574833] s2 : ffffffd6c35e68e8 s3 : ffffffd6c35e6990 s4 :
> ffffffd6c33dc008
>
> [ 5.574854] s5 : 0000000000000000 s6 : 1ffffffad867b801 s7 :
> 0000000000000000
>
> [ 5.574873] s8 : ffffffd6c35e6990 s9 : 0000000000000000 s10:
> ffffffd6c3099058
>
> [ 5.574894] s11: 1ffffffad861320b t3 : 1ffffff8c00dce34 t4 :
> fffffffef0a29898
>
> [ 5.574915] t5 : fffffffef0a29899 t6 :
> ffffffc6006e6f38
>
>
> [ 5.574933] status: 0000000200000120 badaddr: 0000000000000000
> cause:
> 0000000000000003
>
> [ 5.574952] [<ffffffff81336608>]
> refcount_warn_saturate+0x110/0x162
>
>
> [ 5.574985] [<ffffffff8193a506>]
> drm_bridge_get+0x66/0x6e
>
>
> [ 5.575022] [<ffffffff8001f26e>]
> drm_bridge_attach+0x54/0x5a2
>
>
> [ 5.575055] [<ffffffff8002740e>]
> vs_bridge_init+0x3bc/0x4ba
>
>
> [ 5.575087] [<ffffffff82313026>]
> vs_drm_initialize+0xe0/0x2f2
>
>
> [ 5.575126] [<ffffffff80027ca4>]
> vs_dc_probe+0x758/0x85c
>
>
> [ 5.575156] [<ffffffff8233123c>]
> platform_probe+0xac/0x138
>
>
> [ 5.575186] [<ffffffff8232b2a8>]
> really_probe+0x164/0x59e
>
>
> [ 5.575221] [<ffffffff8232b818>] __driver_probe_device+0x136/0x266
> [ 5.575257] [<ffffffff8232bb18>] driver_probe_device+0x56/0x214
> [ 5.575292] [<ffffffff8232bdf0>]
> __device_attach_driver+0x11a/0x278
> [ 5.575329] [<ffffffff823272d8>] bus_for_each_drv+0xea/0x16e
> [ 5.575363] [<ffffffff8232c680>] __device_attach+0x160/0x2d4
> [ 5.575398] [<ffffffff8232ca06>] device_initial_probe+0xe/0x16
> [ 5.575434] [<ffffffff823291d8>] bus_probe_device+0xfe/0x134
> [ 5.575468] [<ffffffff82329c62>]
> deferred_probe_work_func+0x128/0x1d6
> [ 5.575503] [<ffffffff80126aae>] process_one_work+0x5c0/0xe76
> [ 5.575543] [<ffffffff80129da6>] worker_thread+0x6d4/0x1316
> [ 5.575572] [<ffffffff801429ce>] kthread+0x29a/0x570
> [ 5.575608] [<ffffffff8004693e>] ret_from_fork_kernel+0x10/0x9a
> [ 5.575643] [<ffffffff835f55f6>]
> ret_from_fork_kernel_asm+0x16/0x18
> [ 5.575682] ---[ end trace 0000000000000000 ]---
>
>
> When a bridge is allocated with kzalloc, its kref count is
> initialized
> to zero. The drm_bridge_attach() function then calls drm_bridge_get()
> on
> this bridge, which sees the zero refcount and triggers a use-after-
> free
> warning because it assumes the object has already been freed.
>
> To fix this, the bridge's refcount must be initialized with
> kref_init()
> after allocation and before it's attached.
This is going to be changed to devm_drm_bridge_alloc() in the next
revision.
>
> > +
> > + next = devm_drm_of_get_bridge(drm_dev->dev, drm_dev->dev-
> > >of_node,
> > + output, intf);
> > + if (IS_ERR(next)) {
> > + ret = PTR_ERR(next);
> > + goto err_free_bridge;
> > + }
> > +
> > + bridge->next = next;
> > +
> > + ret = drm_simple_encoder_init(drm_dev, &bridge->enc,
> > + (intf ==
> > VSDC_OUTPUT_INTERFACE_DPI) ?
> > + DRM_MODE_ENCODER_DPI :
> > + DRM_MODE_ENCODER_NONE);
> > + if (ret) {
> > + dev_err(drm_dev->dev,
> > + "Cannot initialize encoder for output
> > %u\n", output);
> > + goto err_free_bridge;
> > + }
> > +
> > + 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) {
> > + dev_err(drm_dev->dev,
> > + "Cannot attach bridge for output %u\n",
> > output);
> > + goto err_cleanup_encoder;
> > + }
> > +
> > + bridge->conn = drm_bridge_connector_init(drm_dev, &bridge-
> > >enc);
> > + if (IS_ERR(bridge->conn)) {
> > + dev_err(drm_dev->dev,
> > + "Cannot create connector for output %u\n",
> > output);
> > + ret = PTR_ERR(bridge->conn);
> > + goto err_cleanup_encoder;
> > + }
> > + drm_connector_attach_encoder(bridge->conn, &bridge->enc);
> > +
> > + return bridge;
> > +
> > +err_cleanup_encoder:
> > + drm_encoder_cleanup(&bridge->enc);
> > +err_free_bridge:
> > + devm_kfree(drm_dev->dev, bridge);
> > +
> > + return ERR_PTR(ret);
> > +}
>
>
> Best regards,
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-20 21:21 ` Michal Wilczynski
2025-08-21 3:48 ` Icenowy Zheng
@ 2025-08-21 9:38 ` Maud Spierings
2025-08-21 9:50 ` Michal Wilczynski
1 sibling, 1 reply; 42+ messages in thread
From: Maud Spierings @ 2025-08-21 9:38 UTC (permalink / raw)
To: m.wilczynski
Cc: Laurent.pinchart, airlied, andrzej.hajda, conor+dt, devicetree,
dri-devel, fustini, guoren, heiko, jernej.skrabec, jonas, krzk+dt,
linux-kernel, linux-riscv, maarten.lankhorst, mripard,
neil.armstrong, p.zabel, rabenda.cn, rfoss, robh, simona,
tzimmermann, uwu, wefu, ziyao
> So I am trying to make it work on JH7110 as well, and here is the
> problem:
>
> [ 5.564433] ------------[ cut here ]------------
> [ 5.569161] refcount_t: addition on 0; use-after-free.
> [ 5.574485] WARNING: CPU: 2 PID: 71 at lib/refcount.c:25 refcount_warn_saturate+0x110/0x162
> [ 5.574537] Modules linked in:
> [ 5.574560] CPU: 2 UID: 0 PID: 71 Comm: kworker/u17:2 Not tainted 6.17.0-rc1-g60ec647ec20c-dirty #77 NONE
> [ 5.574596] Hardware name: StarFive VisionFive 2 v1.2A (DT)
> [ 5.574613] Workqueue: events_unbound deferred_probe_work_func
> [ 5.574654] epc : refcount_warn_saturate+0x110/0x162
> [ 5.574682] ra : refcount_warn_saturate+0x110/0x162
> [ 5.574710] epc : ffffffff81336608 ra : ffffffff81336608 sp : ffffffc6006e7530
> [ 5.574732] gp : ffffffff8514c1c0 tp : ffffffd6c14ba580 t0 : ffffffc6006e7134
> [ 5.574753] t1 : fffffffef0a29898 t2 : 5f746e756f636665 s0 : ffffffc6006e7550
> [ 5.574774] s1 : ffffffff83c058a8 a0 : 000000000000002a a1 : 0000000000000004
> [ 5.574794] a2 : 0000000000000000 a3 : ffffffff801ef586 a4 : 0000000000000000
> [ 5.574813] a5 : 0000000000000000 a6 : fffffffef0a29899 a7 : 0000000000000003
> [ 5.574833] s2 : ffffffd6c35e68e8 s3 : ffffffd6c35e6990 s4 : ffffffd6c33dc008
> [ 5.574854] s5 : 0000000000000000 s6 : 1ffffffad867b801 s7 : 0000000000000000
> [ 5.574873] s8 : ffffffd6c35e6990 s9 : 0000000000000000 s10: ffffffd6c3099058
> [ 5.574894] s11: 1ffffffad861320b t3 : 1ffffff8c00dce34 t4 : fffffffef0a29898
> [ 5.574915] t5 : fffffffef0a29899 t6 : ffffffc6006e6f38
> [ 5.574933] status: 0000000200000120 badaddr: 0000000000000000 cause: 0000000000000003
> [ 5.574952] [<ffffffff81336608>] refcount_warn_saturate+0x110/0x162
> [ 5.574985] [<ffffffff8193a506>] drm_bridge_get+0x66/0x6e
> [ 5.575022] [<ffffffff8001f26e>] drm_bridge_attach+0x54/0x5a2
> [ 5.575055] [<ffffffff8002740e>] vs_bridge_init+0x3bc/0x4ba
> [ 5.575087] [<ffffffff82313026>] vs_drm_initialize+0xe0/0x2f2
> [ 5.575126] [<ffffffff80027ca4>] vs_dc_probe+0x758/0x85c
> [ 5.575156] [<ffffffff8233123c>] platform_probe+0xac/0x138
> [ 5.575186] [<ffffffff8232b2a8>] really_probe+0x164/0x59e
> [ 5.575221] [<ffffffff8232b818>] __driver_probe_device+0x136/0x266
> [ 5.575257] [<ffffffff8232bb18>] driver_probe_device+0x56/0x214
> [ 5.575292] [<ffffffff8232bdf0>] __device_attach_driver+0x11a/0x278
> [ 5.575329] [<ffffffff823272d8>] bus_for_each_drv+0xea/0x16e
> [ 5.575363] [<ffffffff8232c680>] __device_attach+0x160/0x2d4
> [ 5.575398] [<ffffffff8232ca06>] device_initial_probe+0xe/0x16
> [ 5.575434] [<ffffffff823291d8>] bus_probe_device+0xfe/0x134
> [ 5.575468] [<ffffffff82329c62>] deferred_probe_work_func+0x128/0x1d6
> [ 5.575503] [<ffffffff80126aae>] process_one_work+0x5c0/0xe76
> [ 5.575543] [<ffffffff80129da6>] worker_thread+0x6d4/0x1316
> [ 5.575572] [<ffffffff801429ce>] kthread+0x29a/0x570
> [ 5.575608] [<ffffffff8004693e>] ret_from_fork_kernel+0x10/0x9a
> [ 5.575643] [<ffffffff835f55f6>] ret_from_fork_kernel_asm+0x16/0x18
> [ 5.575682] ---[ end trace 0000000000000000 ]---
>
>
> When a bridge is allocated with kzalloc, its kref count is initialized
> to zero. The drm_bridge_attach() function then calls drm_bridge_get() on
> this bridge, which sees the zero refcount and triggers a use-after-free
> warning because it assumes the object has already been freed.
>
> To fix this, the bridge's refcount must be initialized with kref_init()
> after allocation and before it's attached.
Do you have a tree I can look at? I am very interested in getting this
working on the jh7110. looked at it myself a week ago, but got lost
quite quickly. If you end up upstreaming it I will definetly be there to
test it.
Did you actually manage to get display out over hdmi? The hdmi output
from last years starfive driver gave me some strange output issues that
I'm currently just living with, but would love to see it gone.
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers
2025-08-21 9:38 ` Maud Spierings
@ 2025-08-21 9:50 ` Michal Wilczynski
0 siblings, 0 replies; 42+ messages in thread
From: Michal Wilczynski @ 2025-08-21 9:50 UTC (permalink / raw)
To: Maud Spierings
Cc: Laurent.pinchart, airlied, andrzej.hajda, conor+dt, devicetree,
dri-devel, fustini, guoren, heiko, jernej.skrabec, jonas, krzk+dt,
linux-kernel, linux-riscv, maarten.lankhorst, mripard,
neil.armstrong, p.zabel, rabenda.cn, rfoss, robh, simona,
tzimmermann, uwu, wefu, ziyao
On 8/21/25 11:38, Maud Spierings wrote:
>> So I am trying to make it work on JH7110 as well, and here is the
>> problem:
>>
>> [ 5.564433] ------------[ cut here ]------------ [ 5.569161] refcount_t: addition on 0; use-after-free. [ 5.574485] WARNING: CPU: 2 PID: 71 at lib/refcount.c:25 refcount_warn_saturate+0x110/0x162 [ 5.574537] Modules linked in: [ 5.574560] CPU: 2 UID: 0 PID: 71 Comm: kworker/u17:2 Not tainted 6.17.0-rc1-g60ec647ec20c-dirty #77 NONE [ 5.574596] Hardware name: StarFive
>> VisionFive 2 v1.2A (DT) [ 5.574613] Workqueue: events_unbound deferred_probe_work_func [ 5.574654] epc : refcount_warn_saturate+0x110/0x162 [ 5.574682] ra : refcount_warn_saturate+0x110/0x162 [ 5.574710] epc : ffffffff81336608 ra : ffffffff81336608 sp : ffffffc6006e7530 [ 5.574732] gp : ffffffff8514c1c0 tp : ffffffd6c14ba580 t0 :
>> ffffffc6006e7134 [ 5.574753] t1 : fffffffef0a29898 t2 : 5f746e756f636665 s0 : ffffffc6006e7550 [ 5.574774] s1 : ffffffff83c058a8 a0 : 000000000000002a a1 : 0000000000000004 [ 5.574794] a2 : 0000000000000000 a3 : ffffffff801ef586 a4 : 0000000000000000 [ 5.574813] a5 : 0000000000000000 a6 : fffffffef0a29899 a7 : 0000000000000003 [ 5.574833] s2 : ffffffd6c35e68e8 s3 : ffffffd6c35e6990 s4 :
>> ffffffd6c33dc008 [ 5.574854] s5 : 0000000000000000 s6 : 1ffffffad867b801 s7 : 0000000000000000 [ 5.574873] s8 : ffffffd6c35e6990 s9 : 0000000000000000 s10: ffffffd6c3099058 [ 5.574894] s11: 1ffffffad861320b t3 : 1ffffff8c00dce34 t4 : fffffffef0a29898 [ 5.574915] t5 : fffffffef0a29899 t6 : ffffffc6006e6f38 [ 5.574933] status: 0000000200000120 badaddr: 0000000000000000 cause:
>> 0000000000000003 [ 5.574952] [<ffffffff81336608>] refcount_warn_saturate+0x110/0x162 [ 5.574985] [<ffffffff8193a506>] drm_bridge_get+0x66/0x6e [ 5.575022] [<ffffffff8001f26e>] drm_bridge_attach+0x54/0x5a2 [ 5.575055] [<ffffffff8002740e>] vs_bridge_init+0x3bc/0x4ba [ 5.575087] [<ffffffff82313026>]
>> vs_drm_initialize+0xe0/0x2f2 [ 5.575126] [<ffffffff80027ca4>] vs_dc_probe+0x758/0x85c [ 5.575156] [<ffffffff8233123c>] platform_probe+0xac/0x138 [ 5.575186] [<ffffffff8232b2a8>] really_probe+0x164/0x59e [ 5.575221] [<ffffffff8232b818>] __driver_probe_device+0x136/0x266
>> [ 5.575257] [<ffffffff8232bb18>] driver_probe_device+0x56/0x214
>> [ 5.575292] [<ffffffff8232bdf0>] __device_attach_driver+0x11a/0x278
>> [ 5.575329] [<ffffffff823272d8>] bus_for_each_drv+0xea/0x16e
>> [ 5.575363] [<ffffffff8232c680>] __device_attach+0x160/0x2d4
>> [ 5.575398] [<ffffffff8232ca06>] device_initial_probe+0xe/0x16
>> [ 5.575434] [<ffffffff823291d8>] bus_probe_device+0xfe/0x134
>> [ 5.575468] [<ffffffff82329c62>] deferred_probe_work_func+0x128/0x1d6
>> [ 5.575503] [<ffffffff80126aae>] process_one_work+0x5c0/0xe76
>> [ 5.575543] [<ffffffff80129da6>] worker_thread+0x6d4/0x1316
>> [ 5.575572] [<ffffffff801429ce>] kthread+0x29a/0x570
>> [ 5.575608] [<ffffffff8004693e>] ret_from_fork_kernel+0x10/0x9a
>> [ 5.575643] [<ffffffff835f55f6>] ret_from_fork_kernel_asm+0x16/0x18
>> [ 5.575682] ---[ end trace 0000000000000000 ]---
>>
>>
>> When a bridge is allocated with kzalloc, its kref count is initialized
>> to zero. The drm_bridge_attach() function then calls drm_bridge_get() on
>> this bridge, which sees the zero refcount and triggers a use-after-free
>> warning because it assumes the object has already been freed.
>>
>> To fix this, the bridge's refcount must be initialized with kref_init()
>> after allocation and before it's attached.
>
> Do you have a tree I can look at? I am very interested in getting this working on the jh7110. looked at it myself a week ago, but got lost quite quickly. If you end up upstreaming it I will definetly be there to test it.
I first started working od getting the Keith version working and have
the 'old' tree here [1]. It works with older version of the DC driver.
[1] - https://github.com/mwilczy/linux/commits/dpu_Aug_9_2/
So the above version works, however the display was 'purple-ish', colors
were skewed. I shared the image in the 'Mainline Linux for RISC-V'
telegram chat.
For the current version from Icenowy it's still a bit messy to share
however it includes addition to inno-hdmi driver to make it works a bit
more like dw-hdmi currently - introduce a probe function, not just
bind, so it doesn't use component framework. Plus changes to
clocks/resets that were discussed.
>
> Did you actually manage to get display out over hdmi? The hdmi output from last years starfive driver gave me some strange output issues that I'm currently just living with, but would love to see it gone.
>
On the current version of the DC driver I'm still working on it.
Best regards,
--
Michal Wilczynski <m.wilczynski@samsung.com>
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-18 7:47 ` Icenowy Zheng
2025-08-18 7:54 ` Troy Mitchell
@ 2025-08-21 11:38 ` Dmitry Baryshkov
2025-08-21 13:24 ` Icenowy Zheng
1 sibling, 1 reply; 42+ messages in thread
From: Dmitry Baryshkov @ 2025-08-21 11:38 UTC (permalink / raw)
To: Icenowy Zheng
Cc: Troy Mitchell, 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, Han Gao, Yao Zi, dri-devel, devicetree,
linux-kernel, linux-riscv
On Mon, Aug 18, 2025 at 03:47:28PM +0800, Icenowy Zheng wrote:
> 在 2025-08-18星期一的 15:45 +0800,Troy Mitchell写道:
> > On Sun, Aug 17, 2025 at 01:10:44AM +0800, Icenowy Zheng wrote:
> > > 在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> > > > On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng wrote:
> > > > > 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>
> > > > > ---
> > > > > MAINTAINERS | 1 +
> > > > > drivers/gpu/drm/bridge/Kconfig | 10 ++
> > > > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170
> > > > > ++++++++++++++++++++++++
> > > > > 4 files changed, 182 insertions(+)
> > > > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > >
> > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > index fe168477caa45..eb84e36ded6d5 100644
> > > > > --- a/MAINTAINERS
> > > > > +++ b/MAINTAINERS
> > > > > @@ -21728,6 +21728,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 b9e0ca85226a6..f75e6ad04179f 100644
> > > > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > > > @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> > > > > --- a/drivers/gpu/drm/bridge/Makefile
> > > > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > > > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-
> > > > > sii8620.o
> > > > > obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > > > > obj-$(CONFIG_DRM_SII9234) += sii9234.o
> > > > > obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> > > > > --- /dev/null
> > > > > +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > > @@ -0,0 +1,170 @@
> > > > > +// 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
> > > > > },
> > > > > + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
> > > > > 0x0000
> > > > > }
> > > > > +};
> > > > > +
> > > > > +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;
> > > >
> > > > We should rewrite DW bridge into HDMI ops. It would help us to
> > > > get
> > > > rid
> > > > of such functions. With it in place it will be handled by the
> > > > generic
> > > > TMDS clock rate check.
> > >
> > > Yes, but this mode_valid hook here is part of the private interface
> > > of
> > > DW bridge, instead of implementing anything for other parts of DRM
> > > subsystem.
> > >
> > > Even if the TMDS clock rate check is utilized by the DW bridge, the
> > > bridge's interface should be modified to expose something to set
> > > the
> > > maximum clock rate.
> > >
> > > >
> > > > > +
> > > > > + return MODE_OK;
> > > > > +}
> > > > > +
> > > > > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi,
> > > > > void
> > > > > *data,
> > > > > + unsigned long mpixelclock)
> > > > > +{
> > > > > + const struct th1520_hdmi_phy_params *params =
> > > > > th1520_hdmi_phy_params;
> > > > > +
> > > > > + for (; params->mpixelclock != ~0UL; ++params) {
> > > > > + if (mpixelclock <= params->mpixelclock)
> > > > > + break;
> > > >
> > > > for (...) {
> > > > if (mpixelclock <= params->mpixelclock)
> > > > return th1520_program_phy();
> > >
> > > There's no such a function here, and this check isn't used for
> > > another
> > > time, so having the matching code and programming code extracted
> > > out
> > > can help nothing.
> > I think Dmitry meant that the following code should be moved into
> > a new function, th1520_program_phy().
> >
> > This makes the code cleaner and also avoids one extra if check.
>
> As there's no code reuse, it does not make code cleaner.
It removes necessity for the extra ~0UL entry in the array and removes
the post-loop check whether we found an entry or not.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 42+ messages in thread
* Re: [RFC PATCH 5/8] drm/bridge: add a driver for T-Head TH1520 HDMI controller
2025-08-21 11:38 ` Dmitry Baryshkov
@ 2025-08-21 13:24 ` Icenowy Zheng
0 siblings, 0 replies; 42+ messages in thread
From: Icenowy Zheng @ 2025-08-21 13:24 UTC (permalink / raw)
To: Dmitry Baryshkov
Cc: Troy Mitchell, 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, Han Gao, Yao Zi, dri-devel, devicetree,
linux-kernel, linux-riscv
在 2025-08-21星期四的 14:38 +0300,Dmitry Baryshkov写道:
> On Mon, Aug 18, 2025 at 03:47:28PM +0800, Icenowy Zheng wrote:
> > 在 2025-08-18星期一的 15:45 +0800,Troy Mitchell写道:
> > > On Sun, Aug 17, 2025 at 01:10:44AM +0800, Icenowy Zheng wrote:
> > > > 在 2025-08-16星期六的 19:24 +0300,Dmitry Baryshkov写道:
> > > > > On Fri, Aug 15, 2025 at 12:40:45AM +0800, Icenowy Zheng
> > > > > wrote:
> > > > > > 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>
> > > > > > ---
> > > > > > MAINTAINERS | 1 +
> > > > > > drivers/gpu/drm/bridge/Kconfig | 10 ++
> > > > > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > > > > drivers/gpu/drm/bridge/th1520-dw-hdmi.c | 170
> > > > > > ++++++++++++++++++++++++
> > > > > > 4 files changed, 182 insertions(+)
> > > > > > create mode 100644 drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > > >
> > > > > > diff --git a/MAINTAINERS b/MAINTAINERS
> > > > > > index fe168477caa45..eb84e36ded6d5 100644
> > > > > > --- a/MAINTAINERS
> > > > > > +++ b/MAINTAINERS
> > > > > > @@ -21728,6 +21728,7 @@
> > > > > > F: Documentation/devicetree/bindings/reset/thead,th152
> > > > > > 0-
> > > > > > 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 b9e0ca85226a6..f75e6ad04179f 100644
> > > > > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > > > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > > > > @@ -322,6 +322,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 245e8a27e3fc5..421e445ff1cd9 100644
> > > > > > --- a/drivers/gpu/drm/bridge/Makefile
> > > > > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > > > > @@ -27,6 +27,7 @@ obj-$(CONFIG_DRM_SIL_SII8620) += sil-
> > > > > > sii8620.o
> > > > > > obj-$(CONFIG_DRM_SII902X) += sii902x.o
> > > > > > obj-$(CONFIG_DRM_SII9234) += sii9234.o
> > > > > > obj-$(CONFIG_DRM_SIMPLE_BRIDGE) += simple-bridge.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..f8dddf3cb0cca
> > > > > > --- /dev/null
> > > > > > +++ b/drivers/gpu/drm/bridge/th1520-dw-hdmi.c
> > > > > > @@ -0,0 +1,170 @@
> > > > > > +// 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
> > > > > > },
> > > > > > + { ~0UL, 0x0000, 0x0000, 0x0000, 0x0000,
> > > > > > 0x0000,
> > > > > > 0x0000
> > > > > > }
> > > > > > +};
> > > > > > +
> > > > > > +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;
> > > > >
> > > > > We should rewrite DW bridge into HDMI ops. It would help us
> > > > > to
> > > > > get
> > > > > rid
> > > > > of such functions. With it in place it will be handled by the
> > > > > generic
> > > > > TMDS clock rate check.
> > > >
> > > > Yes, but this mode_valid hook here is part of the private
> > > > interface
> > > > of
> > > > DW bridge, instead of implementing anything for other parts of
> > > > DRM
> > > > subsystem.
> > > >
> > > > Even if the TMDS clock rate check is utilized by the DW bridge,
> > > > the
> > > > bridge's interface should be modified to expose something to
> > > > set
> > > > the
> > > > maximum clock rate.
> > > >
> > > > >
> > > > > > +
> > > > > > + return MODE_OK;
> > > > > > +}
> > > > > > +
> > > > > > +static int th1520_hdmi_phy_configure(struct dw_hdmi *hdmi,
> > > > > > void
> > > > > > *data,
> > > > > > + unsigned long
> > > > > > mpixelclock)
> > > > > > +{
> > > > > > + const struct th1520_hdmi_phy_params *params =
> > > > > > th1520_hdmi_phy_params;
> > > > > > +
> > > > > > + for (; params->mpixelclock != ~0UL; ++params) {
> > > > > > + if (mpixelclock <= params->mpixelclock)
> > > > > > + break;
> > > > >
> > > > > for (...) {
> > > > > if (mpixelclock <= params->mpixelclock)
> > > > > return th1520_program_phy();
> > > >
> > > > There's no such a function here, and this check isn't used for
> > > > another
> > > > time, so having the matching code and programming code
> > > > extracted
> > > > out
> > > > can help nothing.
> > > I think Dmitry meant that the following code should be moved into
> > > a new function, th1520_program_phy().
> > >
> > > This makes the code cleaner and also avoids one extra if check.
> >
> > As there's no code reuse, it does not make code cleaner.
>
> It removes necessity for the extra ~0UL entry in the array and
> removes
> the post-loop check whether we found an entry or not.
We will need a way to end the loop for any possible reason, either with
~0UL or ARRAY_SIZE or any other way, and the post-loop check is just a
regular pattern.
In addition, creating such a function will break the sequence of code,
make it different from the control flow.
Maybe the register-writing code could be moved into the loop, but
creating such a function is just making the code more different to
read.
>
^ permalink raw reply [flat|nested] 42+ messages in thread
end of thread, other threads:[~2025-08-21 13:26 UTC | newest]
Thread overview: 42+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-14 16:40 [RFC PATCH 0/8] Verisilicon DC8200 driver (and adaption to TH1520) Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 1/8] dt-bindings: vendor-prefixes: add verisilicon Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 2/8] dt-bindings: display: add versilicon,dc Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-14 22:04 ` Rob Herring
2025-08-15 3:42 ` Icenowy Zheng
2025-08-15 9:09 ` Krzysztof Kozlowski
2025-08-15 9:53 ` Icenowy Zheng
2025-08-15 16:55 ` Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 3/8] drm: verisilicon: add a driver for Verisilicon display controllers Icenowy Zheng
2025-08-15 9:05 ` Philipp Zabel
2025-08-15 9:07 ` Icenowy Zheng
2025-08-16 10:04 ` Yao Zi
2025-08-16 16:09 ` Icenowy Zheng
2025-08-16 16:18 ` Dmitry Baryshkov
2025-08-16 16:48 ` Icenowy Zheng
2025-08-16 17:22 ` Icenowy Zheng
2025-08-16 18:01 ` Icenowy Zheng
2025-08-16 18:10 ` Dmitry Baryshkov
2025-08-16 17:45 ` Dmitry Baryshkov
2025-08-16 17:55 ` Icenowy Zheng
2025-08-16 18:05 ` Icenowy Zheng
2025-08-17 18:39 ` Drew Fustini
2025-08-18 7:08 ` Icenowy Zheng
2025-08-20 21:21 ` Michal Wilczynski
2025-08-21 3:48 ` Icenowy Zheng
2025-08-21 9:38 ` Maud Spierings
2025-08-21 9:50 ` Michal Wilczynski
2025-08-14 16:40 ` [RFC PATCH 4/8] dt-bindings: display/bridge: add binding for TH1520 HDMI controller Icenowy Zheng
2025-08-14 19:21 ` Rob Herring (Arm)
2025-08-15 9:13 ` Krzysztof Kozlowski
2025-08-14 16:40 ` [RFC PATCH 5/8] drm/bridge: add a driver for T-Head " Icenowy Zheng
2025-08-16 16:24 ` Dmitry Baryshkov
2025-08-16 17:10 ` Icenowy Zheng
2025-08-18 7:45 ` Troy Mitchell
2025-08-18 7:47 ` Icenowy Zheng
2025-08-18 7:54 ` Troy Mitchell
2025-08-21 11:38 ` Dmitry Baryshkov
2025-08-21 13:24 ` Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 6/8] riscv: dts: thead: add DPU and HDMI device tree nodes Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 7/8] riscv: dts: thead: lichee-pi-4a: enable HDMI Icenowy Zheng
2025-08-14 16:40 ` [RFC PATCH 8/8] MAINTAINERS: assign myself as maintainer for verislicon DC driver Icenowy Zheng
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).