* [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support
@ 2026-10-26 6:21 Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
` (23 more replies)
0 siblings, 24 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
On Qualcomm platforms using the Display Co-Processor (DCP), the display
hardware is owned and programmed by firmware. Linux does not access the
display controller, DSI controller, DSI PHY, display clocks, or other
display pipeline registers.
The display pipeline must nevertheless be exposed through the DRM/KMS
interfaces so userspace can perform modesetting, framebuffer updates,
panel control, and page flips.
This series adds a DCP KMS backend to MSM DRM. It represents the
firmware-owned display pipeline and translates DRM operations into HFI
commands.
DCP communicates with Linux over VirtIO and uses the HFI protocol for
command and response handling, capability discovery, and display events.
This series adds:
- Device-tree bindings for the Qualcomm DCP VirtIO transport wrapper,
firmware-visible IOVA ranges, separate transport and framebuffer IOMMU
contexts, IPCC power notification, interrupts, and the external DSI
display graph.
- HFI protocol definitions and packet helpers for capability discovery,
modesetting, plane and CRTC configuration, DSI, and panel control.
- A Qualcomm DCP transport wrapper which initializes the IPCC channel,
sends the required power notification, and registers the DCP VirtIO
device directly. The wrapper also implements the MMIO transport and
keeps VirtIO queue activity ordered with DCP power transitions.
- VirtIO command and response queues. HFI command, response, and
capability buffers are allocated through the DMA API in the address
space visible to DCP firmware.
- Synchronous command tracking, asynchronous response dispatch, timeout
handling, and queue teardown synchronization for safe device removal.
- Capability discovery for displays, CRTCs, mixers, VIG and DMA pipes,
DSI interfaces, and panel resources.
- DRM planes and CRTCs using the standard atomic-state model. DRM state is
translated into HFI properties for layer configuration, mixer limits,
bandwidth and clock properties, frame sequencing, and triggering.
- An MSM KMS backend, DCP encoder, and DSI bridge. The backend reuses the
existing MSM DRM device, GEM and framebuffer handling, atomic commit
infrastructure, encoder, bridge, and connector support.
- Asynchronous VSYNC, frame-scan completion, and error events propagated to
DRM for vblank and page-flip completion.
- IPCC power notifications to wake DCP firmware and coordinate HFI queue
activity across firmware power transitions.
DCP is integrated as an MSM KMS backend rather than a separate DRM device.
This preserves the userspace-visible MSM DRM device and allows Linux to
retain normal DRM/KMS semantics while display hardware remains owned by
firmware.
The initial implementation supports the DSI output described below and
uses resources reported by firmware. Split-pipe, multirect, cursor-pipe,
partial-update, DisplayPort, and writeback support are deferred.
Tested on the Qualcomm Hawi MTP with an NT37801 DSC command-mode panel:
- Display console at 1440x3200.
- Panel initialization and DCS transfers complete successfully.
Device Tree Topology
====================
qcom,dcp-virtio
├── transport IOMMU context
├── firmware-visible iommu-ranges
├── VirtIO and power-notify interrupts
├── dcp-power-tx mailbox
└── dcp-subsystem
├── compatible = "virtio,device34"
├── framebuffer IOMMU context
├── port@0
│ └── endpoint ─────────────── panel endpoint
└── dsi@0
└── panel@0
The DCP INTF endpoint connects directly to the panel endpoint. Firmware
internal paths, including the INTF-to-DSI connection, are not described in
the device tree.
Runtime Architecture
====================
Linux DRM/KMS
└── DCP MSM KMS backend
├── planes, CRTCs, encoder, connector, and DSI bridge
└── DRM atomic state and commit handling
│
▼
DCP VirtIO function driver
│
▼
VirtIO core
│
▼
qcom,dcp-virtio transport wrapper
├── IPCC initialization and power notification
├── MMIO register access
├── VirtIO queue setup and interrupt handling
├── VirtIO device registration
└── suspend/resume power sequencing
│
▼
DCP firmware
├── HFI command and response handling
├── display pipeline and VIG/DMA pipes
├── DSI PHY and controller
└── display clocks and resource handling
Probe and Initialization Order
=============================
qcom,dcp-virtio platform probe
│
▼
Initialize IPCC and send the DCP power notification
│
▼
Access VirtIO MMIO registers and register the virtio_device
│
▼
VirtIO core matches the DCP function driver
│
▼
Initialize HFI and create command/response virtqueues
│
▼
Send DEVICE_INIT and discover firmware capabilities
│
▼
Register the MSM DRM/KMS backend
The transport must complete IPCC initialization and power notification
before it accesses the VirtIO MMIO registers. The DCP function driver is
matched only after the transport registers the virtio_device.
Dependencies
============
- The VPU IOMMU series provides the IOVA handling required by the DCP
VirtIO transport. The Hawi DT series provides the platform nodes and
IOMMU descriptions consumed by this series.
https://lore.kernel.org/all/20260926-vpu_iommu_iova_handling-v5-4-0322ca5dc10c@oss.qualcomm.com/
- The QPSFI VirtIO device ID used by the DCP transport is proposed in the
following virtio-comment discussion:
https://lore.kernel.org/virtio-comment/20261021180031.7175-1-peter.hilber@oss.qualcomm.com/T/#u
This series adds the corresponding device ID to
include/uapi/linux/virtio_ids.h and uses it to bind the DCP VirtIO function
driver.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
Changes in v2:
- Compared with v1, the series is split into smaller patches. (Dmitry)
- Reworked the VirtIO transport integration so the Qualcomm
`qcom,dcp-virtio` wrapper performs the required IPCC power
notification before accessing the VirtIO MMIO registers and
registering the virtio_device. The transport registers the device
directly.
- Reworked the DT topology so the DCP INTF output connects directly to
the panel endpoint. The firmware-internal INTF-to-DSI path is no
described in DT. (Dmitry)
- Updated the DCP VirtIO binding to document the Qualcomm VirtIO
transport and VirtIO MMIO register layout, Qualcomm power-notify
interrupt, IOMMU contexts, mailbox, and firmware-visible IOVA
range. (Dmitry)
- Removed the interrupt-names property and used the defined interrupt
ordering for the VirtIO and power-notify interrupts. (Dmitry)
- Added panel bridge and component integration so panel bridges are
resolved through the DCP DSI host before DRM registration. (Dmitry)
- Tracked CRTCs using firmware display IDs instead of DRM CRTC
iteration order. (Dmitry)
- Moved DSI DCS-buffer mapping into KMS hardware initialization,
after the IOMMU VM became available, with rollback unmapping on
failure and before VM teardown.
- Reworked IPCC resource handling to use managed IRQ and mailbox
cleanup. (Dmitry)
- Moved IRQ-wake handling to the suspend and resume paths. (Dmitry)
- Removed the ipcc_valid state and made IPCC initialization and teardown
follow the channel lifetime. (Dmitry)
- Embedded the HFI capability cache and removed unused capability fields,
feature flags, and callback state. (Dmitry)
- Removed VirtIO config-change capability re-query handling because
capabilities are consumed during initialization.
- Removed the temporary self-component shim and registered DCP DSI hosts
as components. (Dmitry)
- Added optional GPU component matching for the display-only DCP DRM
device. (Dmitry)
- Made HFI packet-ID allocation atomic across wraparound using cmpxchg().
(Dmitry)
- Updated bandwidth aggregation to ignore invisible planes and planes
without framebuffers. (Dmitry)
- Moved DSI topology values into atomic bridge state while keeping static
panel configuration separate from per-commit state. (Dmitry)
- Removed the unused DP catalog and resource path. (Dmitry)
- Removed unused HFI hotplug and error callback plumbing. (Dmitry)
- Simplified HFI buffer release paths for callers already holding the
command lock. (Dmitry)
- Exported the generic MSM GPU-component matching helper for the DCP
backend.
- Replaced deprecated DRM logging macros with DRM device logging helpers.
(Dmitry)
- Removed unused includes and fixed indentation, naming, logging, and
error-handling issues. (Dmitry)
- Removed status in DT bindings and commit subject. (Krzysztof)
- Documented the DSI host DT ABI: DSI hosts use the generic dsi@N node
naming convention, and their reg properties identify the corresponding
DCP output index. (Krzysztof)
- Fixed the Assisted-by trailer order. (Dmitry, Krzysztof)
- Link to v1: https://patch.msgid.link/20261015-jshekhar-drm-msm-dcp-v1-v1-0-48aad0fe4f72@oss.qualcomm.com
To: Rob Clark <robin.clark@oss.qualcomm.com>
To: Dmitry Baryshkov <lumag@kernel.org>
To: Abhinav Kumar <abhinav.kumar@linux.dev>
To: Jessica Zhang <jesszhan0024@gmail.com>
To: Sean Paul <sean@poorly.run>
To: Marijn Suijten <marijn.suijten@somainline.org>
To: David Airlie <airlied@gmail.com>
To: Simona Vetter <simona@ffwll.ch>
To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
To: Maxime Ripard <mripard@kernel.org>
To: Thomas Zimmermann <tzimmermann@suse.de>
To: Rob Herring <robh@kernel.org>
To: Krzysztof Kozlowski <krzk+dt@kernel.org>
To: Conor Dooley <conor+dt@kernel.org>
To: "Michael S. Tsirkin" <mst@redhat.com>
To: Jason Wang <jasowangio@gmail.com>
To: Eugenio Pérez <eperezma@redhat.com>
To: Xuan Zhuo <xuanzhuo@linux.alibaba.com>
To: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
To: Sumit Semwal <sumit.semwal@linaro.org>
To: Christian König <christian.koenig@amd.com>
To: Philipp Stanner <phasta@kernel.org>
To: Bjorn Andersson <andersson@kernel.org>
To: Konrad Dybcio <konradybcio@kernel.org>
To: Abel Vesa <abelvesa@kernel.org>
To: "Joerg Roedel (AMD)" <joro@8bytes.org>
To: Will Deacon <will@kernel.org>
To: Robin Murphy <robin.murphy@arm.com>
To: Flemin Jose <flemin.jose@oss.qualcomm.com>
To: Mahadevan P <mahap@oss.qualcomm.com>
To: Michael S. Tsirkin <mst@redhat.com>
To: Jason Wang <jasowangio@gmail.com>
To: Eugenio Pérez <eperezma@redhat.com>
Cc: virtualization@lists.linux.dev
Cc: linux-arm-msm@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: freedreno@lists.freedesktop.org
Cc: devicetree@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: virtualization@lists.linux.dev
Cc: linux-media@vger.kernel.org
Cc: linaro-mm-sig@lists.linaro.org
Cc: iommu@lists.linux.dev
---
Jayant Shekhar (23):
dt-bindings: display: msm: Add Qualcomm DCP
drm/msm/dcp: Add HFI packet helpers
drm/msm/dcp: Add HFI device definitions
drm/msm/dcp: Add HFI display definitions
drm/msm/dcp: Add HFI panel definitions
drm/msm/dcp: Add HFI VirtIO transport
drm/msm/dcp: Handle asynchronous HFI responses
drm/msm/dcp: Add IPCC power notifications
drm/msm/dcp: Add generic HFI display commands
drm/msm/dcp: Add HFI DSI and panel commands
drm/msm/dcp: Add DCP CRTCs and shared plane state
drm/msm/dcp: Add DCP planes
drm/msm/dcp: Route HFI display events
drm/msm/dcp: Add DCP encoder
drm/msm/dcp: Add DCP DSI host and panel support
drm/msm/dcp: Integrate the DSI bridge with KMS
drm/msm/dcp: Register DCP display pipeline
virtio: Add Qualcomm Platform Services Firmware Interface ID
drm/msm/dcp: Add Qualcomm VirtIO transport wrapper
drm/msm/dcp: Add DCP VirtIO driver
drm/msm: Support KMS backends without a display IRQ
drm/msm: Register the DCP backend
arm64: dts: qcom: hawi: Add DCP support
.../bindings/display/msm/qcom,dcp-dsi.yaml | 79 ++
.../devicetree/bindings/display/msm/qcom,dcp.yaml | 123 +++
.../bindings/virtio/qcom,dcp-virtio.yaml | 132 +++
arch/arm64/boot/dts/qcom/hawi-mtp.dts | 129 +++
arch/arm64/boot/dts/qcom/hawi.dtsi | 40 +
drivers/gpu/drm/msm/Kconfig | 25 +
drivers/gpu/drm/msm/Makefile | 22 +
drivers/gpu/drm/msm/dcp/dcp_crtc.c | 739 +++++++++++++
drivers/gpu/drm/msm/dcp/dcp_crtc.h | 104 ++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 686 ++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 59 +
drivers/gpu/drm/msm/dcp/dcp_drv.c | 191 ++++
drivers/gpu/drm/msm/dcp/dcp_drv.h | 46 +
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 +++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h | 76 ++
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 94 ++
drivers/gpu/drm/msm/dcp/dcp_encoder.c | 29 +
drivers/gpu/drm/msm/dcp/dcp_encoder.h | 16 +
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 275 +++++
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 1155 ++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c | 191 ++++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c | 252 +++++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c | 64 ++
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c | 366 +++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c | 63 ++
drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h | 48 +
drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 173 +++
drivers/gpu/drm/msm/dcp/dcp_ipcc.h | 42 +
drivers/gpu/drm/msm/dcp/dcp_plane.c | 1056 ++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_plane.h | 36 +
drivers/gpu/drm/msm/dcp/dcp_plane_state.h | 37 +
drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++
drivers/gpu/drm/msm/dcp/dcp_virtio.h | 25 +
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h | 78 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h | 105 ++
.../drm/msm/dcp/hfi/hfi_commands_display_events.h | 90 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h | 27 +
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h | 182 +++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h | 61 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h | 464 ++++++++
.../gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h | 247 +++++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h | 287 +++++
.../gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h | 79 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c | 106 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h | 65 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h | 76 ++
.../gpu/drm/msm/dcp/hfi/hfi_properties_device.h | 102 ++
.../gpu/drm/msm/dcp/hfi/hfi_properties_display.h | 480 ++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h | 198 ++++
drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c | 157 +++
drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h | 77 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c | 62 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h | 56 +
drivers/gpu/drm/msm/msm_drv.c | 8 +-
drivers/gpu/drm/msm/msm_drv.h | 10 +
drivers/gpu/drm/msm/msm_kms.c | 2 +-
include/uapi/linux/virtio_ids.h | 1 +
59 files changed, 11992 insertions(+), 4 deletions(-)
---
base-commit: 9a482184d910775cf648863148944945b74e2815
change-id: 20261022-jshekhar-dcp-hlos-c7c22b0ff7e4
prerequisite-patch-id: eecfd66a55b8f2b7b2fc7d76d815facca0489d00
Best regards,
--
Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 55+ messages in thread
* [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 9:38 ` sashiko-bot
` (3 more replies)
2026-10-26 6:21 ` [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers Jayant Shekhar
` (22 subsequent siblings)
23 siblings, 4 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
DCP platforms place the display pipeline under firmware control, so Linux
cannot discover display resources through direct register access. The DRM
driver therefore needs device-tree descriptions of the Qualcomm transport
wrapper, display topology, IOMMU contexts, and panel connections.
Add bindings for the Qualcomm DCP transport wrapper and VirtIO display
subsystem. Document the IPCC power-notify resources, transport and
framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and
DSI panel connections. The wrapper creates a standard VirtIO-MMIO child
after the IPCC power-notify sequence has completed.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
.../bindings/display/msm/qcom,dcp-dsi.yaml | 79 ++++++++++++
.../devicetree/bindings/display/msm/qcom,dcp.yaml | 123 +++++++++++++++++++
.../bindings/virtio/qcom,dcp-virtio.yaml | 132 +++++++++++++++++++++
3 files changed, 334 insertions(+)
diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
new file mode 100644
index 000000000000..9a0acaf00bb3
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp-dsi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP DSI host
+
+maintainers:
+ - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
+
+description: |
+ A MIPI DSI host exposed by the Qualcomm DCP VirtIO device (see
+ qcom,dcp.yaml). It follows the generic DSI controller binding, with panel
+ devices described as child nodes. The DCP INTF graph endpoint connects
+ directly to the panel; the DSI host node is not a graph endpoint. The host
+ node uses the dsi@N naming convention, and its reg property identifies the
+ DSI host index.
+
+allOf:
+ - $ref: /schemas/display/dsi-controller.yaml#
+
+properties:
+ reg:
+ maxItems: 1
+ description: DSI host index matching the DCP output port.
+
+required:
+ - reg
+ - "#address-cells"
+ - "#size-cells"
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ dcp-subsystem {
+ compatible = "virtio,device34";
+ iommus = <&apps_smmu 0x400 0x2>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_intf0_out: endpoint {
+ remote-endpoint = <&panel_dcp_in>;
+ };
+ };
+ };
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "novatek,nt37801";
+ reg = <0>;
+
+ vddio-supply = <&vreg_l12b_1p8>;
+ vci-supply = <&vreg_l13b_3p0>;
+ vdd-supply = <&display_panel_vdd>;
+ reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+ port {
+ panel_dcp_in: endpoint {
+ remote-endpoint = <&dcp_intf0_out>;
+ };
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml
new file mode 100644
index 000000000000..96f6c2a17e9e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml
@@ -0,0 +1,123 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm Display Co-Processor (DCP)
+
+maintainers:
+ - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
+
+description: |
+ The Display Co-Processor (DCP) is a firmware-owned display subsystem.
+ Linux communicates with DCP over HFI through VirtIO and does not access
+ display hardware registers directly.
+
+ This node describes the externally connected display graph between DCP
+ INTF outputs and attached DSI panels. It is assigned to the VirtIO device
+ by the Qualcomm transport driver and is not independently enumerated.
+ Firmware capabilities and internal resources, including pipes, mixers,
+ CRTCs, and clocks, are discovered at runtime.
+
+allOf:
+ - $ref: /schemas/virtio/virtio-device.yaml#
+
+properties:
+ compatible:
+ const: virtio,device34
+
+ iommus:
+ maxItems: 1
+ description:
+ SMMU context used by DCP firmware to access GEM framebuffers. This
+ is separate from the HFI transport context specified by the
+ iommus property on the parent DCP transport node.
+ The child context is used for framebuffer DMA performed by DCP
+ firmware, while the parent context is used for VirtIO transport and HFI
+ queue buffers. These contexts represent distinct firmware-visible DMA
+ address spaces and must not be used interchangeably.
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ description: |
+ DPU INTF output-only ports. The port number matches the firmware INTF
+ index. The corresponding DSI host is the child node whose reg value
+ matches the port number; the graph endpoint itself connects directly to
+ the attached panel. Unused INTF ports are omitted.
+ unevaluatedProperties: false
+ patternProperties:
+ "^port@[0-9a-f]+$":
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ properties:
+ endpoint:
+ $ref: /schemas/graph.yaml#/$defs/endpoint-base
+ unevaluatedProperties: false
+ required:
+ - remote-endpoint
+
+patternProperties:
+ "^dsi@[0-9a-f]+$":
+ $ref: qcom,dcp-dsi.yaml#
+
+required:
+ - compatible
+ - iommus
+ - "#address-cells"
+ - "#size-cells"
+ - ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ dcp-subsystem {
+ compatible = "virtio,device34";
+ iommus = <&apps_smmu 0x400 0x2>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_intf0_out: endpoint {
+ remote-endpoint = <&panel_dcp_in>;
+ };
+ };
+ };
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "novatek,nt37801";
+ reg = <0>;
+
+ vddio-supply = <&vreg_l12b_1p8>;
+ vci-supply = <&vreg_l13b_3p0>;
+ vdd-supply = <&display_panel_vdd>;
+ reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+ port {
+ panel_dcp_in: endpoint {
+ remote-endpoint = <&dcp_intf0_out>;
+ };
+ };
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
new file mode 100644
index 000000000000..6020e848ce25
--- /dev/null
+++ b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
@@ -0,0 +1,132 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/virtio/qcom,dcp-virtio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP VirtIO transport
+
+maintainers:
+ - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
+
+description: |
+ Qualcomm DCP VirtIO transport. The IPCC power-notify channel and
+ associated interrupts are required before the transport can access its
+ MMIO registers. This is a Qualcomm-specific transport and must not use the
+ generic virtio-mmio driver. The DCP VirtIO function is described as a child
+ node and is assigned to the registered VirtIO device by this transport.
+
+properties:
+ compatible:
+ const: qcom,dcp-virtio
+
+ reg:
+ maxItems: 1
+
+ interrupts-extended:
+ minItems: 2
+ maxItems: 2
+ description: The VirtIO-MMIO and DCP power-notify interrupts, in that order.
+
+ iommus:
+ maxItems: 1
+
+ iommu-ranges:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+ description: |
+ The firmware-visible IOVA range, specified as a four-cell tuple
+ containing a 64-bit IOVA base followed by a 64-bit range size.
+ items:
+ - description: IOVA base, high cell
+ $ref: /schemas/types.yaml#/definitions/uint32
+ - description: IOVA base, low cell
+ $ref: /schemas/types.yaml#/definitions/uint32
+ - description: IOVA range size, high cell
+ $ref: /schemas/types.yaml#/definitions/uint32
+ - description: IOVA range size, low cell
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ dma-coherent: true
+
+ mboxes:
+ maxItems: 1
+
+ mbox-names:
+ const: dcp-power-tx
+
+ dcp-subsystem:
+ $ref: ../display/msm/qcom,dcp.yaml#
+
+required:
+ - compatible
+ - reg
+ - interrupts-extended
+ - iommus
+ - iommu-ranges
+ - dma-coherent
+ - mboxes
+ - mbox-names
+ - dcp-subsystem
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ #include <dt-bindings/mailbox/qcom-ipcc.h>
+
+ virtio@9be8000 {
+ compatible = "qcom,dcp-virtio";
+ reg = <0x09be8000 0x1000>;
+ interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+ <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+ iommus = <&apps_smmu 0x15a3 0x0>;
+ iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
+ dma-coherent;
+ mboxes = <&ipcc 13 5>;
+ mbox-names = "dcp-power-tx";
+
+ dcp-subsystem {
+ compatible = "virtio,device34";
+ iommus = <&apps_smmu 0x400 0x2>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_intf0_out: endpoint {
+ remote-endpoint = <&panel_dcp_in>;
+ };
+ };
+ };
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ panel@0 {
+ compatible = "novatek,nt37801";
+ reg = <0>;
+
+ vddio-supply = <&vreg_l12b_1p8>;
+ vci-supply = <&vreg_l13b_3p0>;
+ vdd-supply = <&display_panel_vdd>;
+ reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+ port {
+ panel_dcp_in: endpoint {
+ remote-endpoint = <&dcp_intf0_out>;
+ };
+ };
+ };
+ };
+ };
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 9:48 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions Jayant Shekhar
` (21 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Define the HFI packet formats and serialization helpers required to
communicate with DCP firmware. Without common packing and unpacking
rules, the transport and display command layers cannot exchange
commands reliably.
Implement packet creation, property packing and unpacking, and
shared HFI parsing utilities for the DCP transport and command
implementations.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Kconfig | 25 +++
drivers/gpu/drm/msm/Makefile | 6 +
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h | 182 +++++++++++++++++++++
.../gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h | 79 +++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c | 106 ++++++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h | 65 ++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h | 76 +++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c | 157 ++++++++++++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h | 77 +++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c | 62 +++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h | 56 +++++++
11 files changed, 891 insertions(+)
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
index ed1c4775d1e9..a9f049271c25 100644
--- a/drivers/gpu/drm/msm/Kconfig
+++ b/drivers/gpu/drm/msm/Kconfig
@@ -114,6 +114,31 @@ config DRM_MSM_DPU
the MSM DRM driver. It is the display controller found in devices
using e.g. SDM845 and newer platforms.
+config DRM_MSM_DCP
+ bool "Enable DCP firmware-delegated display support in MSM DRM driver"
+ depends on DRM_MSM
+ depends on VIRTIO
+ select DRM_MSM_KMS
+ select DRM_BRIDGE_CONNECTOR
+ select DRM_MIPI_DSI
+ select DRM_DISPLAY_DSC_HELPER
+ default y
+ help
+ Compile in support for the DCP (Display Coprocessor) firmware-delegated
+ display architecture in the MSM DRM driver.
+
+ DCP is used on Hawi (qcom,hawi) SoC where all display hardware is
+ owned by a separate Display Coprocessor firmware. The host AP
+ communicates with DCP via two interfaces:
+ - IPCC SMP2P mailbox: one-shot wake signal before each HFI command
+ - HFI over VirtIO MMIO: all display command/response exchange
+
+ DPU and bridge nodes in the Device Tree are topology-only (no
+ compatible string, no reg). The driver detects them by node name
+ prefix using of_node_name_prefix().
+
+ If unsure, say N.
+
config DRM_MSM_DP
bool "Enable DisplayPort support in MSM DRM driver"
depends on DRM_MSM
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 1e246616f83f..ad7b9dbf736e 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -4,6 +4,7 @@ ccflags-y += -I $(obj)/generated
ccflags-y += -I $(src)/disp/dpu1
ccflags-$(CONFIG_DRM_MSM_DSI) += -I $(src)/dsi
ccflags-$(CONFIG_DRM_MSM_DP) += -I $(src)/dp
+ccflags-$(CONFIG_DRM_MSM_DCP) += -I $(src)/dcp
adreno-y := \
adreno/adreno_device.o \
@@ -70,6 +71,11 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
disp/mdp5/mdp5_plane.o \
disp/mdp5/mdp5_smp.o \
+msm-display-$(CONFIG_DRM_MSM_DCP) += \
+ dcp/hfi/hfi_packer.o \
+ dcp/hfi/hfi_unpacker.o \
+ dcp/hfi/hfi_utils.o
+
msm-display-$(CONFIG_DRM_MSM_DPU) += \
disp/dpu1/dpu_core_perf.o \
disp/dpu1/dpu_crtc.o \
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h
new file mode 100644
index 000000000000..8e58a50f600c
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_common.h
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_DEFS_COMMON_H__
+#define __H_HFI_DEFS_COMMON_H__
+
+#include <linux/bits.h>
+
+/* Boolean property values. */
+#define HFI_FALSE 0
+#define HFI_TRUE 1
+
+#define HFI_MAX_PLANES 4 /* Maximum number of planes supported */
+
+#define HFI_MAX_COLOR_COMPONENTS 3 /* Maximum number of color components supported */
+#define HFI_DHDR_PAYLOAD_MAX_SIZE 32 /* Maximum dynamic HDR payload size */
+
+/* Color-format class is encoded in bits 31:24. */
+enum hfi_color_formats {
+ /* Interleaved RGB */
+ HFI_COLOR_FORMAT_INTERLEAVED_RGB_MIN = 0x01000000,
+ HFI_COLOR_FORMAT_RGB565 = 0x01000001,
+ HFI_COLOR_FORMAT_RGB888 = 0x01000002,
+ HFI_COLOR_FORMAT_ARGB8888 = 0x01000003,
+ HFI_COLOR_FORMAT_RGBA8888 = 0x01000004,
+ HFI_COLOR_FORMAT_XRGB8888 = 0x01000005,
+ HFI_COLOR_FORMAT_RGBX8888 = 0x01000006,
+ HFI_COLOR_FORMAT_ARGB1555 = 0x01000007,
+ HFI_COLOR_FORMAT_RGBA5551 = 0x01000008,
+ HFI_COLOR_FORMAT_XRGB1555 = 0x01000009,
+ HFI_COLOR_FORMAT_RGBX5551 = 0x0100000a,
+ HFI_COLOR_FORMAT_ARGB4444 = 0x0100000b,
+ HFI_COLOR_FORMAT_RGBA4444 = 0x0100000c,
+ HFI_COLOR_FORMAT_RGBX4444 = 0x0100000d,
+ HFI_COLOR_FORMAT_XRGB4444 = 0x0100000e,
+ HFI_COLOR_FORMAT_ARGB2_10_10_10 = 0x0100000f,
+ HFI_COLOR_FORMAT_XRGB2_10_10_10 = 0x01000010,
+ HFI_COLOR_FORMAT_RGBA10_10_10_2 = 0x01000011,
+ HFI_COLOR_FORMAT_RGBX10_10_10_2 = 0x01000012,
+ HFI_COLOR_FORMAT_ARGB_FP_16 = 0x01000013,
+ HFI_COLOR_FORMAT_RGBA_FP_16 = 0x01000014,
+ HFI_COLOR_FORMAT_RGBA8888_TILED = 0x01000015,
+ HFI_COLOR_FORMAT_RGBX8888_TILED = 0x01000016,
+ HFI_COLOR_FORMAT_RGBA10_10_10_2_TILED = 0x01000017,
+ HFI_COLOR_FORMAT_RGBX10_10_10_2_TILED = 0x01000018,
+ HFI_COLOR_FORMAT_RGB888_BYPASS = 0x01000019,
+ HFI_COLOR_FORMAT_INTERLEAVED_RGB_MAX = 0x01ffffff,
+ /* Interleaved BGR */
+ HFI_COLOR_FORMAT_INTERLEAVED_BGR_MIN = 0x02000000,
+ HFI_COLOR_FORMAT_BGR565 = 0x02000001,
+ HFI_COLOR_FORMAT_BGR888 = 0x02000002,
+ HFI_COLOR_FORMAT_ABGR8888 = 0x02000003,
+ HFI_COLOR_FORMAT_BGRA8888 = 0x02000004,
+ HFI_COLOR_FORMAT_BGRX8888 = 0x02000005,
+ HFI_COLOR_FORMAT_XBGR8888 = 0x02000006,
+ HFI_COLOR_FORMAT_ABGR1555 = 0x02000007,
+ HFI_COLOR_FORMAT_BGRA5551 = 0x02000008,
+ HFI_COLOR_FORMAT_XBGR1555 = 0x02000009,
+ HFI_COLOR_FORMAT_BGRX5551 = 0x0200000a,
+ HFI_COLOR_FORMAT_ABGR4444 = 0x0200000b,
+ HFI_COLOR_FORMAT_BGRA4444 = 0x0200000c,
+ HFI_COLOR_FORMAT_BGRX4444 = 0x0200000d,
+ HFI_COLOR_FORMAT_XBGR4444 = 0x0200000e,
+ HFI_COLOR_FORMAT_ABGR2_10_10_10 = 0x0200000f,
+ HFI_COLOR_FORMAT_XBGR2_10_10_10 = 0x02000010,
+ HFI_COLOR_FORMAT_BGRA10_10_10_2 = 0x02000011,
+ HFI_COLOR_FORMAT_BGRX10_10_10_2 = 0x02000012,
+ HFI_COLOR_FORMAT_ABGR16_FP_16 = 0x02000013,
+ HFI_COLOR_FORMAT_BGRA16_FP_16 = 0x02000014,
+ HFI_COLOR_FORMAT_INTERLEAVED_BGR_MAX = 0x02ffffff,
+ /* Compressed RGBA UBWC 3.0/ 4.x / 5.0 Lossless */
+ HFI_COLOR_FORMAT_RGBA_UBWC_LOSSLESS_MIN = 0x03000000,
+ HFI_COLOR_FORMAT_UBWC_RGBA8888 = 0x03000001,
+ HFI_COLOR_FORMAT_UBWC_RGBA10_10_10_2 = 0x03000002,
+ HFI_COLOR_FORMAT_UBWC_RGBA16_16_16_16_F = 0x03000003,
+ HFI_COLOR_FORMAT_UBWC_RGB565 = 0x03000004,
+ HFI_COLOR_FORMAT_UBWC_RGBX8888 = 0x03000005,
+ HFI_COLOR_FORMAT_UBWC_RGBX10_10_10_2 = 0x03000006,
+ HFI_COLOR_FORMAT_RGBA_UBWC_LOSSLESS_MAX = 0x03ffffff,
+ /* Compressed RGBA UBWC 5.0 Lossy */
+ HFI_COLOR_FORMAT_RGBA_UBWC_LOSSY_MIN = 0x04000000,
+ HFI_COLOR_FORMAT_UBWC_RGBA8888_L_8_5 = 0x04000001,
+ HFI_COLOR_FORMAT_UBWC_RGBA8888_L_2_1 = 0x04000002,
+ HFI_COLOR_FORMAT_RGBA_UBWC_LOSSY_MAX = 0x04ffffff,
+ /* Linear / Uncompressed YUV420 / FSC */
+ HFI_COLOR_FORMAT_LINEAR_MIN = 0x05000000,
+ HFI_COLOR_FORMAT_YU12 = 0x05000001,
+ HFI_COLOR_FORMAT_YV12 = 0x05000002,
+ HFI_COLOR_FORMAT_NV12 = 0x05000003,
+ HFI_COLOR_FORMAT_NV21 = 0x05000004,
+ HFI_COLOR_FORMAT_P010 = 0x05000005,
+ HFI_COLOR_FORMAT_YUYV = 0x05000006,
+ HFI_COLOR_FORMAT_YVYU = 0x05000007,
+ HFI_COLOR_FORMAT_UYVY = 0x05000008,
+ HFI_COLOR_FORMAT_VYUY = 0x05000009,
+ HFI_COLOR_FORMAT_FSC_R = 0x0500000a,
+ HFI_COLOR_FORMAT_FSC_G = 0x0500000b,
+ HFI_COLOR_FORMAT_FSC_B = 0x0500000c,
+ HFI_COLOR_FORMAT_FSC_ALPHA = 0x0500000d,
+ HFI_COLOR_FORMAT_P210 = 0x0500000e,
+ HFI_COLOR_FORMAT_Y10 = 0x0500000f,
+ HFI_COLOR_FORMAT_A10 = 0x05000010,
+ HFI_COLOR_FORMAT_Y10_MSB_ALIGN = 0x05000011,
+ HFI_COLOR_FORMAT_A10_MSB_ALIGN = 0x05000012,
+ HFI_COLOR_FORMAT_Y8 = 0x05000013,
+ HFI_COLOR_FORMAT_LINEAR_MAX = 0x05ffffff,
+ /* Compressed UBWC 3.0 / 4.x / 5.0 Lossless / FSC */
+ HFI_COLOR_FORMAT_UBWC_LOSSLESS_MIN = 0x06000000,
+ HFI_COLOR_FORMAT_UBWC_NV12 = 0x06000001,
+ HFI_COLOR_FORMAT_UBWC_NV12_INTERLACE = 0x06000002,
+ HFI_COLOR_FORMAT_UBWC_TP10 = 0x06000003,
+ HFI_COLOR_FORMAT_UBWC_P010 = 0x06000004,
+ HFI_COLOR_FORMAT_UBWC_FSC_R = 0x06000005,
+ HFI_COLOR_FORMAT_UBWC_FSC_G = 0x06000006,
+ HFI_COLOR_FORMAT_UBWC_FSC_B = 0x06000007,
+ HFI_COLOR_FORMAT_UBWC_FSC_ALPHA = 0x06000008,
+ HFI_COLOR_FORMAT_UBWC_FSC_R_4R = 0x06000009,
+ HFI_COLOR_FORMAT_UBWC_FSC_G_4R = 0x0600000a,
+ HFI_COLOR_FORMAT_UBWC_FSC_B_4R = 0x0600000b,
+ HFI_COLOR_FORMAT_UBWC_FSC_ALPHA_4R = 0x0600000c,
+ HFI_COLOR_FORMAT_UBWC_ABGR_NV12_4R_4Y = 0x0600000d,
+ HFI_COLOR_FORMAT_UBWC_P210 = 0x0600000e,
+ HFI_COLOR_FORMAT_UBWC_Y10 = 0x0600000f,
+ HFI_COLOR_FORMAT_UBWC_A10 = 0x06000010,
+
+ HFI_COLOR_FORMAT_UBWC_LOSSLESS_MAX = 0x06ffffff,
+ /* Compressed UBWC 3.0 / 4.x / 5.0 Lossy */
+ HFI_COLOR_FORMAT_UBWC_LOSSY_MIN = 0x07000000,
+ HFI_COLOR_FORMAT_UBWC_NV12_LOSSY = 0x07000001,
+ HFI_COLOR_FORMAT_UBWC_TP10_LOSSY = 0x07000002,
+ HFI_COLOR_FORMAT_UBWC_LOSSY_MAX = 0x07ffffff,
+};
+
+/* Shared HFI buffer descriptor. */
+struct hfi_buff {
+ u32 addr_l;
+ u32 addr_h;
+ u32 size;
+ u32 version;
+ u32 flags;
+};
+
+/* Multi-plane HFI buffer descriptor. */
+struct hfi_plane_buff {
+ u32 addr_l[HFI_MAX_PLANES];
+ u32 addr_h[HFI_MAX_PLANES];
+ u32 size;
+ u32 version;
+ u32 flags;
+ u32 format;
+};
+
+/* 64-bit HFI property value. */
+struct hfi_prop_u64 {
+ u32 val_lo;
+ u32 val_hi;
+};
+
+#define HFI_BUFF_FEATURE_ENABLE BIT(0)
+#define HFI_BUFF_FEATURE_BROADCAST BIT(1)
+#define HFI_BUFF_FEATURE_HW_BLK_IDX_0 BIT(2)
+#define HFI_BUFF_FEATURE_HW_BLK_IDX_1 BIT(3)
+#define HFI_BUFF_FEATURE_HW_BLK_IDX_2 BIT(4)
+#define HFI_BUFF_FEATURE_HW_BLK_IDX_3 BIT(5)
+#define HFI_BUFF_3D_LUT_MODE_A BIT(16)
+#define HFI_BUFF_3D_LUT_MODE_B BIT(17)
+/* Buffer descriptor visible to the DPU. */
+struct hfi_buff_dpu {
+ u32 flags;
+ u32 iova;
+ u32 len;
+};
+
+/* HFI subsystem identifiers. */
+enum hfi_subsystem_type {
+ HFI_SUBSYSTEM_TYPE_LSR = 0,
+ HFI_SUBSYSTEM_TYPE_GMU = 1
+};
+
+#endif /* __H_HFI_DEFS_COMMON_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h
new file mode 100644
index 000000000000..b767e6ee8e40
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_pack_unpack_common.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PACK_UNPACK_COMMON_H__
+#define __H_HFI_PACK_UNPACK_COMMON_H__
+
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include "hfi_packet.h"
+
+/* Header size field width. */
+#define HFI_HEADER_SIZE_MAX 0x00ffffff
+/* Packet size field width. */
+#define HFI_PACKET_SIZE_MAX 0x000fffff
+
+#define HFI_HEADER_CMD_BUFF_TYPE_START_BIT 24
+#define HFI_PACKET_PAYLOAD_TYPE_START_BIT 21
+
+#define GET_HEADER_SIZE(data) \
+ ((data) & ((1 << HFI_HEADER_CMD_BUFF_TYPE_START_BIT) - 1))
+#define GET_HEADER_CMD_BUFF_TYPE(data) \
+ ((data) >> HFI_HEADER_CMD_BUFF_TYPE_START_BIT)
+#define GET_PACKET_SIZE(data) \
+ ((data) & ((1 << HFI_PACKET_PAYLOAD_TYPE_START_BIT) - 1))
+#define GET_PACKET_PAYLOAD_TYPE(data) \
+ ((data) >> HFI_PACKET_PAYLOAD_TYPE_START_BIT)
+
+static inline struct hfi_packet *hfi_packet_first(struct hfi_header *hdr)
+{
+ return (struct hfi_packet *)((u8 *)hdr + sizeof(*hdr));
+}
+
+static inline struct hfi_packet *hfi_packet_append(struct hfi_header *hdr)
+{
+ return (struct hfi_packet *)((u8 *)hdr +
+ GET_HEADER_SIZE(hdr->cmd_buff_info));
+}
+
+static inline struct hfi_packet *hfi_packet_next(struct hfi_packet *packet)
+{
+ return (struct hfi_packet *)((u8 *)packet +
+ GET_PACKET_SIZE(packet->payload_info));
+}
+
+/* Shared command-buffer handle. */
+struct hfi_cmd_buff_hdl {
+ void *cmd_buffer;
+ u32 size;
+};
+
+/* Packed HFI property key/value entry. */
+struct hfi_kv_info {
+ u32 key;
+ void *value_ptr;
+};
+
+/* HFI header fields. */
+struct hfi_header_info {
+ u32 num_packets;
+ enum hfi_cmd_buff_type cmd_buff_type;
+ u32 object_id;
+ u32 header_id;
+};
+
+/* HFI packet fields. */
+struct hfi_packet_info {
+ u32 cmd;
+ u32 id;
+ u32 flags;
+ u32 packet_id;
+ enum hfi_packet_payload_type payload_type;
+ u32 payload_size;
+ void *payload_ptr;
+};
+
+#endif /* __H_HFI_PACK_UNPACK_COMMON_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c b/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c
new file mode 100644
index 000000000000..291af4716544
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.c
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "hfi_packer.h"
+
+static int hfi_validate_cmd_buff_size(struct hfi_header *hdr,
+ u32 allocated_size, u32 bytes_to_add)
+{
+ u32 bytes_used;
+
+ if (allocated_size < sizeof(struct hfi_header))
+ return -EINVAL;
+
+ bytes_used = GET_HEADER_SIZE(hdr->cmd_buff_info);
+ if (bytes_used < sizeof(*hdr) || bytes_used > allocated_size ||
+ bytes_to_add > allocated_size - bytes_used ||
+ bytes_used + bytes_to_add > HFI_HEADER_SIZE_MAX)
+ return -EINVAL;
+
+ return 0;
+}
+
+u32 hfi_get_header_size(struct hfi_cmd_buff_hdl *cmd_buf_hdl)
+{
+ struct hfi_header *hdr = cmd_buf_hdl->cmd_buffer;
+
+ return GET_HEADER_SIZE(hdr->cmd_buff_info);
+}
+
+int hfi_create_header(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_header_info *header_info)
+{
+ struct hfi_header *hdr;
+
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !header_info)
+ return -EINVAL;
+
+ hdr = cmd_buf_hdl->cmd_buffer;
+ if (cmd_buf_hdl->size < sizeof(*hdr))
+ return -EINVAL;
+
+ memset(hdr, 0, sizeof(*hdr));
+ hdr->cmd_buff_info = (header_info->cmd_buff_type <<
+ HFI_HEADER_CMD_BUFF_TYPE_START_BIT) | sizeof(*hdr);
+ hdr->object_id = header_info->object_id;
+ hdr->header_id = header_info->header_id;
+
+ return 0;
+}
+
+static int hfi_create_packet(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info,
+ const void *payload, u32 payload_size)
+{
+ struct hfi_header *hdr;
+ struct hfi_packet *pkt_hdr;
+ u32 pkt_size;
+ int ret;
+
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !packet_info ||
+ (payload_size && !payload))
+ return -EINVAL;
+
+ if (payload_size > HFI_PACKET_SIZE_MAX - sizeof(struct hfi_packet))
+ return -EINVAL;
+
+ hdr = cmd_buf_hdl->cmd_buffer;
+ pkt_size = sizeof(*pkt_hdr) + payload_size;
+ ret = hfi_validate_cmd_buff_size(hdr, cmd_buf_hdl->size, pkt_size);
+ if (ret)
+ return ret;
+
+ pkt_hdr = hfi_packet_append(hdr);
+ memset(pkt_hdr, 0, sizeof(*pkt_hdr));
+ pkt_hdr->payload_info = (packet_info->payload_type <<
+ HFI_PACKET_PAYLOAD_TYPE_START_BIT) | pkt_size;
+ pkt_hdr->cmd = packet_info->cmd;
+ pkt_hdr->flags = packet_info->flags;
+ pkt_hdr->id = packet_info->id;
+ pkt_hdr->packet_id = packet_info->packet_id;
+ if (payload_size)
+ memcpy(pkt_hdr + 1, payload, payload_size);
+
+ hdr->cmd_buff_info += pkt_size;
+ hdr->num_packets++;
+
+ return 0;
+}
+
+int hfi_create_full_packet(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info)
+{
+ if (!packet_info)
+ return -EINVAL;
+
+ return hfi_create_packet(cmd_buf_hdl, packet_info,
+ packet_info->payload_ptr, packet_info->payload_size);
+}
+
+int hfi_create_packet_header(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info)
+{
+ return hfi_create_packet(cmd_buf_hdl, packet_info, NULL, 0);
+}
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h
new file mode 100644
index 000000000000..460c11c72416
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_packer.h
@@ -0,0 +1,65 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PACKER_H__
+#define __H_HFI_PACKER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+
+#include "hfi_pack_unpack_common.h"
+
+/* Pack a property ID, version, and size into one key. */
+#define HFI_PACK_KEY(property_id, version, dsize) \
+ ((property_id) | ((version) << 20) | ((dsize) << 24))
+
+/* Pack a version-zero property and return its serialized word count. */
+#define HFI_PACK(payload, property_id, ...) \
+({ \
+ const u32 __hfi_values[] = { __VA_ARGS__ }; \
+ u32 *__hfi_payload = (payload); \
+ u32 __hfi_count = ARRAY_SIZE(__hfi_values); \
+\
+ *__hfi_payload++ = HFI_PACK_KEY(property_id, 0, __hfi_count); \
+ memcpy(__hfi_payload, __hfi_values, sizeof(__hfi_values)); \
+ __hfi_count + 1; \
+})
+
+/* Pack a property with an explicitly selected protocol version. */
+#define HFI_PACK_VERSION(payload, property_id, version, ...) \
+({ \
+ const u32 __hfi_values[] = { __VA_ARGS__ }; \
+ u32 *__hfi_payload = (payload); \
+ u32 __hfi_count = ARRAY_SIZE(__hfi_values); \
+\
+ *__hfi_payload++ = HFI_PACK_KEY(property_id, version, __hfi_count); \
+ memcpy(__hfi_payload, __hfi_values, sizeof(__hfi_values)); \
+ __hfi_count + 1; \
+})
+
+/** hfi_get_header_size() - Return the bytes used by an HFI header. */
+u32 hfi_get_header_size(struct hfi_cmd_buff_hdl *cmd_buf_hdl);
+
+/** hfi_create_header() - Initialize an HFI command header. */
+int hfi_create_header(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_header_info *header_info);
+
+/** hfi_create_full_packet() - Append a packet and its payload. */
+int hfi_create_full_packet(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info);
+
+/** hfi_create_packet_header() - Append a packet without a payload. */
+int hfi_create_packet_header(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __H_HFI_PACKER_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h
new file mode 100644
index 000000000000..80c6b2b64cfd
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_packet.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PACKET_H__
+#define __H_HFI_PACKET_H__
+
+#include "hfi_defs_common.h"
+
+/* HFI header: size [23:0], command-buffer type [31:24]. */
+struct hfi_header {
+ u32 cmd_buff_info;
+ u16 device_id;
+ u16 object_id;
+ u32 timestamp_hi;
+ u32 timestamp_lo;
+ u32 header_id;
+ u32 reserved[2];
+ u32 num_packets;
+};
+
+/* HFI packet: size [20:0], payload type [31:21]. */
+struct hfi_packet {
+ u32 payload_info;
+ u32 cmd;
+ u32 flags;
+ u32 id;
+ u32 packet_id;
+ u32 reserved[3];
+};
+
+/* Command-buffer flags stored in object_id[15:8]. */
+enum hfi_cmd_buff_flag {
+ HFI_CMD_BUFF_FLAG_NONE = 0x00,
+ HFI_CMD_BUFF_FLAG_ASYNC = 0x01,
+ HFI_CMD_BUFF_FLAG_NO_WAIT = 0x02,
+};
+
+/* Packet payload encodings. */
+enum hfi_packet_payload_type {
+ HFI_PAYLOAD_NONE = 0x0,
+ HFI_PAYLOAD_U32 = 0x1,
+ HFI_PAYLOAD_U32_ARRAY = 0x2,
+ HFI_PAYLOAD_U64 = 0x3,
+ HFI_PAYLOAD_U64_ARRAY = 0x4,
+ HFI_PAYLOAD_BLOB = 0x5,
+};
+
+/* Command-buffer groups encoded in command headers. */
+enum hfi_cmd_buff_type {
+ HFI_CMD_BUFF_SYSTEM = 0x1,
+ HFI_CMD_BUFF_DEVICE = 0x2,
+ HFI_CMD_BUFF_DISPLAY = 0x3,
+ HFI_CMD_BUFF_DEBUG = 0x4,
+ HFI_CMD_BUFF_VIRTUALIZATION = 0x5,
+};
+
+/* Host-to-firmware packet flags. */
+enum hfi_packet_tx_flags {
+ HFI_TX_FLAGS_NONE = 0x0,
+ HFI_TX_FLAGS_INTR_REQUIRED = 0x1,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED = 0x2,
+ HFI_TX_FLAGS_NON_DISCARDABLE = 0x4,
+};
+
+/* Firmware-to-host packet flags. */
+enum hfi_packet_rx_flags {
+ HFI_RX_FLAGS_NONE = 0x0,
+ HFI_RX_FLAGS_SUCCESS = 0x1,
+ HFI_RX_FLAGS_INFORMATION = 0x2,
+ HFI_RX_FLAGS_DEVICE_ERROR = 0x4,
+ HFI_RX_FLAGS_SYSTEM_ERROR = 0x8,
+};
+
+#endif /* __H_HFI_PACKET_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c b/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c
new file mode 100644
index 000000000000..e5b1a1c48b01
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include "hfi_unpacker.h"
+
+static int hfi_sanitize_cmd_buff(struct hfi_header *hdr)
+{
+ struct hfi_packet *packet;
+ u32 packet_size, total_pkt_size = sizeof(*hdr);
+ u32 hdr_size = GET_HEADER_SIZE(hdr->cmd_buff_info);
+ int i;
+
+ if (hdr_size < sizeof(*hdr) + sizeof(*packet) || !hdr->num_packets)
+ return -EINVAL;
+
+ packet = hfi_packet_first(hdr);
+ for (i = 0; i < hdr->num_packets; i++) {
+ if (total_pkt_size > hdr_size - sizeof(*packet))
+ return -EINVAL;
+
+ packet_size = GET_PACKET_SIZE(packet->payload_info);
+ if (packet_size < sizeof(*packet) ||
+ packet_size > hdr_size - total_pkt_size)
+ return -EINVAL;
+
+ total_pkt_size += packet_size;
+ packet = hfi_packet_next(packet);
+ }
+
+ return hdr_size == total_pkt_size ? 0 : -EINVAL;
+}
+
+static struct hfi_packet *hfi_packet_at(struct hfi_header *hdr, u32 packet_num)
+{
+ struct hfi_packet *packet = hfi_packet_first(hdr);
+
+ while (--packet_num)
+ packet = hfi_packet_next(packet);
+
+ return packet;
+}
+
+int hfi_unpacker_get_header_info(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_header_info *header_info)
+{
+ struct hfi_header *hdr;
+ u32 hdr_size;
+
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !header_info)
+ return -EINVAL;
+
+ hdr = cmd_buf_hdl->cmd_buffer;
+ hdr_size = GET_HEADER_SIZE(hdr->cmd_buff_info);
+ if (hdr_size < sizeof(*hdr) || hdr_size > cmd_buf_hdl->size)
+ return -EINVAL;
+
+ header_info->num_packets = hdr->num_packets;
+ header_info->cmd_buff_type = GET_HEADER_CMD_BUFF_TYPE(hdr->cmd_buff_info);
+ header_info->object_id = hdr->object_id;
+ header_info->header_id = hdr->header_id;
+
+ return 0;
+}
+
+int hfi_unpacker_get_packet_info(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ u32 packet_num,
+ struct hfi_packet_info *packet_info)
+{
+ struct hfi_header *hdr;
+ struct hfi_packet *packet;
+ u32 packet_size;
+
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !packet_info ||
+ !packet_num)
+ return -EINVAL;
+
+ hdr = cmd_buf_hdl->cmd_buffer;
+ if (GET_HEADER_SIZE(hdr->cmd_buff_info) > cmd_buf_hdl->size ||
+ packet_num > hdr->num_packets || hfi_sanitize_cmd_buff(hdr))
+ return -EINVAL;
+
+ packet = hfi_packet_at(hdr, packet_num);
+ packet_size = GET_PACKET_SIZE(packet->payload_info);
+ packet_info->cmd = packet->cmd;
+ packet_info->flags = packet->flags;
+ packet_info->id = packet->id;
+ packet_info->packet_id = packet->packet_id;
+ packet_info->payload_type = GET_PACKET_PAYLOAD_TYPE(packet->payload_info);
+ packet_info->payload_size = packet_size - sizeof(*packet);
+ packet_info->payload_ptr = packet_info->payload_size ?
+ (void *)(packet + 1) : NULL;
+
+ return 0;
+}
+
+int hfi_unpacker_get_kv_pairs_count(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info,
+ u32 kv_payload_offset, u32 *num_props)
+{
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !packet_info ||
+ !packet_info->payload_ptr || !num_props ||
+ kv_payload_offset > packet_info->payload_size ||
+ !IS_ALIGNED(kv_payload_offset, sizeof(u32)) ||
+ packet_info->payload_size - kv_payload_offset < sizeof(u32))
+ return -EINVAL;
+
+ *num_props = *(u32 *)((u8 *)packet_info->payload_ptr + kv_payload_offset);
+
+ return 0;
+}
+
+int hfi_unpacker_get_kv_pairs(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info,
+ u32 kv_payload_offset, u32 num_props,
+ struct hfi_kv_info *kv_pairs)
+{
+ u32 *kv_pairs_counter_idx, *kv_pairs_key;
+ u32 value_size, consumed;
+ int i;
+
+ if (!cmd_buf_hdl || !cmd_buf_hdl->cmd_buffer || !packet_info ||
+ !packet_info->payload_ptr || !kv_pairs ||
+ kv_payload_offset > packet_info->payload_size ||
+ !IS_ALIGNED(kv_payload_offset, sizeof(u32)) ||
+ packet_info->payload_size - kv_payload_offset < sizeof(u32))
+ return -EINVAL;
+
+ kv_pairs_counter_idx = (u32 *)((u8 *)packet_info->payload_ptr +
+ kv_payload_offset);
+ if (*kv_pairs_counter_idx != num_props)
+ return -EINVAL;
+
+ consumed = sizeof(u32);
+ for (i = 0; i < num_props; i++) {
+ u32 remaining;
+
+ if (consumed > packet_info->payload_size - kv_payload_offset ||
+ packet_info->payload_size - kv_payload_offset - consumed <
+ sizeof(u32))
+ return -EINVAL;
+
+ kv_pairs_key = (u32 *)((u8 *)kv_pairs_counter_idx + consumed);
+ value_size = (*kv_pairs_key & 0xff000000) >> 24;
+ remaining = packet_info->payload_size - kv_payload_offset -
+ consumed - sizeof(u32);
+ if (value_size > remaining / sizeof(u32))
+ return -EINVAL;
+
+ kv_pairs[i].key = *kv_pairs_key;
+ kv_pairs[i].value_ptr = kv_pairs_key + 1;
+ consumed += sizeof(u32) * (1 + value_size);
+ }
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h
new file mode 100644
index 000000000000..9f893d4ad47c
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.h
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_UNPACKER_H__
+#define __H_HFI_UNPACKER_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "hfi_pack_unpack_common.h"
+
+/* Extract the property ID, version, and value size from a key. */
+#define HFI_UNPACK_KEY(key, property_id, version, size) \
+ do { \
+ typeof(key) __key = (key); \
+ (property_id) = __key & 0x000fffff; \
+ (version) = (__key & 0x00f00000) >> 20; \
+ (size) = (__key & 0xff000000) >> 24; \
+ } while (0)
+
+/**
+ * hfi_unpacker_get_header_info() - Parse the command-buffer header.
+ * @cmd_buf_hdl: Command buffer to inspect.
+ * @header_info: Receives the decoded header information.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int hfi_unpacker_get_header_info(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_header_info *header_info);
+
+/**
+ * hfi_unpacker_get_packet_info() - Parse one packet in a command buffer.
+ * @cmd_buf_hdl: Command buffer to inspect.
+ * @packet_num: One-based packet index.
+ * @packet_info: Receives the decoded packet information.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int hfi_unpacker_get_packet_info(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ u32 packet_num, struct hfi_packet_info *packet_info);
+
+/**
+ * hfi_unpacker_get_kv_pairs_count() - Count properties in a packet payload.
+ * @cmd_buf_hdl: Command buffer to inspect.
+ * @packet_info: Packet information returned by hfi_unpacker_get_packet_info().
+ * @kv_payload_offset: Byte offset where key/value pairs begin.
+ * @num_props: Receives the property count.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int hfi_unpacker_get_kv_pairs_count(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info,
+ u32 kv_payload_offset, u32 *num_props);
+
+/**
+ * hfi_unpacker_get_kv_pairs() - Decode properties from a packet payload.
+ * @cmd_buf_hdl: Command buffer to inspect.
+ * @packet_info: Packet information returned by hfi_unpacker_get_packet_info().
+ * @kv_payload_offset: Byte offset where key/value pairs begin.
+ * @num_props: Number of properties to decode.
+ * @kv_pairs: Receives the decoded property entries.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int hfi_unpacker_get_kv_pairs(struct hfi_cmd_buff_hdl *cmd_buf_hdl,
+ struct hfi_packet_info *packet_info,
+ u32 kv_payload_offset, u32 num_props,
+ struct hfi_kv_info *kv_pairs);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __H_HFI_UNPACKER_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c b/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c
new file mode 100644
index 000000000000..71e720ffe3f0
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#include <linux/errno.h>
+#include <linux/types.h>
+
+#include "hfi_utils.h"
+
+int hfi_util_kv_parser_init(struct hfi_util_kv_parser *kv_parser,
+ u32 bytes, u32 *payload)
+{
+ if (!kv_parser || !payload || bytes > HFI_UTIL_MAX_ALLOC)
+ return -EINVAL;
+
+ if (bytes % sizeof(u32))
+ return -EINVAL;
+
+ kv_parser->max_index = bytes / sizeof(u32);
+ kv_parser->cur_offset = 0;
+ kv_parser->payload = payload;
+
+ return 0;
+}
+
+int hfi_util_kv_parser_get_next(struct hfi_util_kv_parser *kv_parser,
+ u32 move, u32 *hfi_prop,
+ u32 **payload, u32 *max_words)
+{
+ if (!kv_parser || !payload || !hfi_prop || !max_words)
+ return -EINVAL;
+
+ if (kv_parser->cur_offset > kv_parser->max_index ||
+ move > kv_parser->max_index - kv_parser->cur_offset)
+ return -EINVAL;
+
+ kv_parser->cur_offset += move;
+ *hfi_prop = 0;
+ *payload = NULL;
+ *max_words = 0;
+
+ /* End of payload is a valid result. */
+ if (kv_parser->cur_offset == kv_parser->max_index)
+ return 0;
+
+ if (kv_parser->cur_offset > kv_parser->max_index)
+ return -EINVAL;
+
+ if ((kv_parser->max_index - kv_parser->cur_offset) < 2)
+ return -EINVAL;
+
+ *hfi_prop = kv_parser->payload[kv_parser->cur_offset++];
+ *payload = &kv_parser->payload[kv_parser->cur_offset];
+ *max_words = kv_parser->max_index - kv_parser->cur_offset;
+ if (HFI_PROP_SZ(*hfi_prop) && HFI_PROP_SZ(*hfi_prop) > *max_words)
+ return -EINVAL;
+ if (HFI_PROP_SZ(*hfi_prop))
+ *max_words = HFI_PROP_SZ(*hfi_prop);
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h
new file mode 100644
index 000000000000..61fdc483ad47
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h
@@ -0,0 +1,56 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef _HFI_UTILS_H_
+#define _HFI_UTILS_H_
+
+#include <linux/types.h>
+
+#define HFI_UTIL_MAX_ALLOC 4096
+#define HFI_PROP_ID(val) ((val) & 0xfffff)
+#define HFI_PROP_SZ(val) ((val) >> 24)
+#define HFI_VAL_L32(val) ((val) & 0xffffffff)
+#define HFI_VAL_H32(val) ((val) >> 32)
+
+/* Encode the major and minor feature versions in a property ID. */
+#define HFI_UTIL_PACK_VERSION(major, minor, prop_id) \
+ (((prop_id) & ~(0xf0f000)) | (((minor) & 0xf) << 20) | (((major) & 0xf) << 12))
+
+/**
+ * struct hfi_util_kv_parser - State for iterating key/value properties.
+ * @max_index: Number of u32 words available in @payload.
+ * @cur_offset: Current u32 offset.
+ * @payload: Property payload buffer.
+ */
+struct hfi_util_kv_parser {
+ u32 max_index;
+ u32 cur_offset;
+ u32 *payload;
+};
+
+/**
+ * hfi_util_kv_parser_init() - Initialize a key/value parser.
+ * @kv_parser: Parser state to initialize.
+ * @bytes: Payload size in bytes; must be u32-aligned.
+ * @payload: u32 payload buffer to parse.
+ *
+ * Return: 0 on success, or a negative errno.
+ */
+int hfi_util_kv_parser_init(struct hfi_util_kv_parser *kv_parser, u32 bytes, u32 *payload);
+
+/**
+ * hfi_util_kv_parser_get_next() - Read the next property from the payload.
+ * @kv_parser: Parser state, updated in place.
+ * @move: Number of u32 words occupied by the previous value.
+ * @hfi_prop: Receives the packed property key, or zero at end of input.
+ * @payload: Receives the property value pointer, or NULL at end of input.
+ * @max_words: Receives the available value length in u32 words.
+ *
+ * Return: 0 on success or end of input, or a negative errno.
+ */
+int hfi_util_kv_parser_get_next(struct hfi_util_kv_parser *kv_parser, u32 move,
+ u32 *hfi_prop, u32 **payload, u32 *max_words);
+
+#endif /* _HFI_UTILS_H_ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 04/23] drm/msm/dcp: Add HFI display definitions Jayant Shekhar
` (20 subsequent siblings)
23 siblings, 0 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Define the HFI device commands and properties needed to discover DCP
capabilities and register display resources. The KMS backend cannot
size or allocate the firmware-owned pipeline without these protocol
definitions.
Describe device commands, resource data, and capability property
identifiers used by the DCP device and KMS layers.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h | 78 ++++++++++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h | 61 ++++++++++++
.../gpu/drm/msm/dcp/hfi/hfi_properties_device.h | 102 +++++++++++++++++++++
3 files changed, 241 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h
new file mode 100644
index 000000000000..abf15e6ceaec
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_device.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_DEVICE_H__
+#define __H_HFI_COMMANDS_DEVICE_H__
+
+/* Device-level command range. */
+#define HFI_COMMAND_DEVICE_BEGIN 0x01000000
+
+/* Initialize the device and return firmware capabilities. */
+#define HFI_COMMAND_DEVICE_INIT 0x01000001
+
+/* Device, display, and pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS 0x01000002
+
+/* VIG pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_VIG_CAPS 0x01000003
+
+/* DMA pipeline capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DMA_CAPS 0x01000004
+
+/* Common VIG/DMA capability response. */
+#define HFI_COMMAND_DEVICE_INIT_COMMON_LAYER_CAPS 0x01000005
+
+/* Display capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS 0x01000006
+
+/* Writeback capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_WB_CAPS 0x01000007
+
+/* Register clocks and bandwidth resources with firmware. */
+#define HFI_COMMAND_DEVICE_RESOURCE_REGISTER 0x01000008
+
+/* Firmware requests a resource vote from the host. */
+#define HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE 0x01000009
+
+/* VIG rect-1 capability response. */
+#define HFI_COMMAND_DEVICE_INIT_VIG_R1_CAPS 0x0100000a
+
+/* DMA rect-1 capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DMA_R1_CAPS 0x0100000b
+
+/* Provide the last-command buffer to firmware. */
+#define HFI_COMMAND_DEVICE_LUT_DMA_LAST_CMD 0x0100000c
+
+/* Deinitialize the device. */
+#define HFI_COMMAND_DEVICE_DEINIT 0x0100000d
+
+/* LSR writeback CSC layer capability response. */
+#define HFI_COMMAND_DEVICE_INIT_LSR_WB_CSC_LAYER_CAPS 0x0100000e
+
+/* LSR writeback reprojection layer capability response. */
+#define HFI_COMMAND_DEVICE_INIT_LSR_WB_REPROJ_LAYER_CAPS 0x0100000f
+
+/* LSR writeback CSC output capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_LSR_WB_CSC_CAPS 0x01000010
+
+/* LSR writeback reprojection output capability response. */
+#define HFI_COMMAND_DEVICE_INIT_DISPLAY_LSR_WB_REPROJ_CAPS 0x01000011
+
+/* Configure the hardware-fence HFI queue. */
+#define HFI_COMMAND_DEVICE_HWFENCE_HFI_CONFIG 0x01000012
+
+/* Update hotplug state and provide EDID/mode buffers. */
+#define HFI_COMMAND_DEVICE_HOT_PLUG_DETECT 0x01000013
+
+/* Configure resources shared across virtual machines. */
+#define HFI_COMMAND_DEVICE_RESOURCE_CTRL 0x01000014
+
+/* Notify firmware of a subsystem restart. */
+#define HFI_COMMAND_DEVICE_SUBSYSTEM_RESTART_NOTIFY 0x01000015
+
+/* End of device-level command range. */
+#define HFI_COMMAND_DEVICE_END 0x01ffffff
+
+#endif /* __H_HFI_COMMANDS_DEVICE_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h
new file mode 100644
index 000000000000..c6c8fb556c3e
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_device.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_DEFS_DEVICE_H__
+#define __H_HFI_DEFS_DEVICE_H__
+
+#include "hfi_defs_common.h"
+
+/* Hotplug state for dynamic display interfaces. */
+struct hfi_device_hotplug_config {
+ u32 orientation;
+ u32 port_index;
+ u32 pin_config;
+ u32 hpd_state;
+ u32 hpd_irq;
+};
+
+/* Display resource ownership state. */
+enum hfi_device_res_state {
+ HFI_DEVICE_RESOURCE_ACQUIRE = 0x0,
+ HFI_DEVICE_RESOURCE_RELEASE = 0x1,
+ HFI_DEVICE_RESOURCE_SHARED = 0x2,
+ HFI_DEVICE_RESOURCE_PRE_RELEASE = 0x3,
+};
+
+/* Virtualized display-resource types. */
+enum hfi_device_resource_type {
+ HFI_DEVICE_RESOURCE_DISPLAY,
+};
+
+/* Virtualized display-resource configuration. */
+struct hfi_device_resource_config {
+ u32 disp_mask;
+ enum hfi_device_res_state vm_state;
+ u32 resource_type;
+ u32 reserved[2];
+};
+
+/* Subsystem-restart event. */
+enum hfi_device_ssr_event {
+ HFI_DEVICE_SSR_EVENT_START = 0,
+ HFI_DEVICE_SSR_EVENT_END = 1,
+};
+
+/* Subsystem-restart information. */
+struct hfi_device_ssr_info {
+ enum hfi_subsystem_type subsystem_type;
+ enum hfi_device_ssr_event ssr_event;
+ u32 reserved1;
+ u32 reserved2;
+};
+
+/* Active layer mask for a display. */
+struct hfi_device_active_layers_info {
+ u32 display_id;
+ u32 layers_mask;
+};
+
+#endif /* __H_HFI_DEFS_DEVICE_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h
new file mode 100644
index 000000000000..790fcca6ac0a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PROPERTIES_DEVICE_H__
+#define __H_HFI_PROPERTIES_DEVICE_H__
+
+/* Device-property range. */
+#define HFI_PROPERTY_DEVICE_BEGIN 0x00010000
+
+/* DCP hardware version. */
+#define HFI_PROPERTY_DEVICE_INIT_DCP_HW_VERSION 0x00010001
+
+/* MDSS hardware version. */
+#define HFI_PROPERTY_DEVICE_INIT_MDSS_HW_VERSION 0x00010002
+
+/* VIG layer indices; the high byte identifies the rectangle. */
+#define HFI_PROPERTY_DEVICE_INIT_VIG_INDICES 0x00010004
+
+/* DMA layer indices; the high byte identifies the rectangle. */
+#define HFI_PROPERTY_DEVICE_INIT_DMA_INDICES 0x00010005
+
+/* DCP firmware version. */
+#define HFI_PROPERTY_DEVICE_INIT_DCP_FW_VERSION 0x00010003
+
+/* Maximum display count. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_DISPLAY_COUNT 0x00010006
+
+/* Writeback block indices. */
+#define HFI_PROPERTY_DEVICE_INIT_WB_INDICES 0x00010007
+
+/* Maximum linear writeback size: width [31:16], height [15:0]. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_WB_LINEAR_RESOLUTION 0x00010008
+
+/* Maximum UBWC writeback size: width [31:16], height [15:0]. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_WB_UBWC_RESOLUTION 0x00010009
+
+/* DSI interface indices. */
+#define HFI_PROPERTY_DEVICE_INIT_DSI_INDICES 0x0001000a
+
+/* Maximum DSI size: width [31:16], height [15:0]. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_DSI_RESOLUTION 0x0001000b
+
+/* DisplayPort interface indices. */
+#define HFI_PROPERTY_DEVICE_INIT_DP_INDICES 0x0001000c
+
+/* Maximum DisplayPort size: width [31:16], height [15:0]. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_DP_RESOLUTION 0x0001000d
+
+/* Downscaler indices. */
+#define HFI_PROPERTY_DEVICE_INIT_DS_INDICES 0x0001000e
+
+/* Maximum downscaler size: width [31:16], height [15:0]. */
+#define HFI_PROPERTY_DEVICE_INIT_MAX_DS_RESOLUTION 0x0001000f
+
+/* Core average-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_CORE_AB 0x00010010
+
+/* Core instantaneous-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_CORE_IB 0x00010011
+
+/* Core power-rail vote. */
+#define HFI_PROPERTY_DEVICE_CORE_POWER_RAIL 0x00010012
+
+/* LLCC average-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_LLCC_AB 0x00010013
+
+/* LLCC instantaneous-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_LLCC_IB 0x00010014
+
+/* DRAM average-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_DRAM_AB 0x00010015
+
+/* DRAM instantaneous-bandwidth vote. */
+#define HFI_PROPERTY_DEVICE_DRAM_IB 0x00010016
+
+/* LSR writeback CSC indices. */
+#define HFI_PROPERTY_DEVICE_INIT_LSR_WB_CSC_INDICES 0x00010017
+
+/* LSR writeback reprojection indices. */
+#define HFI_PROPERTY_DEVICE_INIT_LSR_WB_REPROJ_INDICES 0x00010018
+
+/* LSR writeback CSC layer indices. */
+#define HFI_PROPERTY_DEVICE_INIT_LSR_WB_CSC_LAYER_INDICES 0x00010019
+
+/* LSR writeback reprojection layer indices. */
+#define HFI_PROPERTY_DEVICE_INIT_LSR_WB_REPROJ_LAYER_INDICES 0x0001001a
+
+/* System-cache staling configuration. */
+#define HFI_PROPERTY_DEVICE_INIT_SYS_CACHE_STALING 0x0001001b
+
+/* Active layers per display. */
+#define HFI_PROPERTY_DEVICE_INIT_ACTIVE_LAYERS 0x0001001c
+
+/* Writeback DNSC support indices. */
+#define HFI_PROPERTY_DEVICE_INIT_WB_DNSC_SUPPORT_INDICES 0x0001001d
+
+/* End of device-property range. */
+#define HFI_PROPERTY_DEVICE_END 0x0001ffff
+
+#endif /* __H_HFI_PROPERTIES_DEVICE_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 04/23] drm/msm/dcp: Add HFI display definitions
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (2 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 05/23] drm/msm/dcp: Add HFI panel definitions Jayant Shekhar
` (19 subsequent siblings)
23 siblings, 0 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Define the HFI display protocol used to configure the firmware-owned
display pipeline. DCP does not expose host-visible display registers,
so DRM state must be represented by display commands and properties.
Describe display commands, events, data structures, color-management
data, and property identifiers consumed by the DCP KMS and command
layers.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h | 105 +++++
.../drm/msm/dcp/hfi/hfi_commands_display_events.h | 90 ++++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h | 464 ++++++++++++++++++++
.../gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h | 247 +++++++++++
.../gpu/drm/msm/dcp/hfi/hfi_properties_display.h | 480 +++++++++++++++++++++
5 files changed, 1386 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h
new file mode 100644
index 000000000000..5f24b45e67b1
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display.h
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_DISPLAY_H__
+#define __H_HFI_COMMANDS_DISPLAY_H__
+
+/* Display-command range. */
+#define HFI_COMMAND_DISPLAY_BEGIN 0x02000000
+
+/* Set display properties. */
+#define HFI_COMMAND_DISPLAY_SET_PROPERTY 0x02000001
+
+/* Read display or layer properties. */
+#define HFI_COMMAND_DISPLAY_GET_PROPERTY 0x02000002
+
+/* Commit previously submitted display state. */
+#define HFI_COMMAND_DISPLAY_FRAME_TRIGGER 0x02000003
+
+/* Register a display event. */
+#define HFI_COMMAND_DISPLAY_EVENT_REGISTER 0x02000004
+
+/* Unregister a display event. */
+#define HFI_COMMAND_DISPLAY_EVENT_DEREGISTER 0x02000005
+
+/* Enable the display session. */
+#define HFI_COMMAND_DISPLAY_ENABLE 0x02000006
+
+/* Send post-enable panel commands. */
+#define HFI_COMMAND_DISPLAY_POST_ENABLE 0x02000007
+
+/* Disable the display stream. */
+#define HFI_COMMAND_DISPLAY_DISABLE 0x02000008
+
+/* Release post-disable display resources. */
+#define HFI_COMMAND_DISPLAY_POST_DISABLE 0x02000009
+
+/* Set a previously validated display mode. */
+#define HFI_COMMAND_DISPLAY_SET_MODE 0x0200000a
+
+/* Validate a display mode in firmware. */
+#define HFI_COMMAND_DISPLAY_MODE_VALIDATE 0x0200000b
+
+/* Request host-side display power control. */
+#define HFI_COMMAND_DISPLAY_POWER_CONTROL 0x0200000c
+
+/* Register display power-control handling. */
+#define HFI_COMMAND_DISPLAY_POWER_REGISTER 0x0200000d
+
+/* Transfer one DCS command. */
+#define HFI_COMMAND_DISPLAY_TRANSFER_DCS_CMD 0x0200000e
+
+/* Transfer a DCS command set. */
+#define HFI_COMMAND_DISPLAY_TRANSFER_DCS_CMD_SET 0x0200001d
+
+/* Configure the display idle timer. */
+#define HFI_COMMAND_DISPLAY_IDLE_TIMER_CONTROL 0x0200000f
+
+/* Configure DisplayPort transfer units. */
+#define HFI_COMMAND_DISPLAY_SET_DP_TU 0x02000010
+
+/* Configure HDR display properties. */
+#define HFI_COMMAND_DISPLAY_CONFIG_HDR 0x02000011
+
+/* Configure display audio. */
+#define HFI_COMMAND_DISPLAY_AUDIO_CONFIG 0x02000012
+
+/* Control display audio. */
+#define HFI_COMMAND_DISPLAY_AUDIO_CONTROL 0x02000013
+
+/* Set the display color space. */
+#define HFI_COMMAND_DISPLAY_SET_COLORSPACE 0x02000014
+
+/* Provide the HDCP 1.x AKSV. */
+#define HFI_COMMAND_DISPLAY_HDCP1X_AKSV 0x02000015
+
+/* Provide an HDCP 2.x response. */
+#define HFI_COMMAND_DISPLAY_HDCP2X_RESPONSE 0x02000016
+
+/* Report sink HDCP capabilities. */
+#define HFI_COMMAND_DISPLAY_HDCP_FEATURE_SUPPORTED 0x02000017
+
+/* Configure DSI DCS command remapping. */
+#define HFI_COMMAND_DISPLAY_DSI_CUSTOM_DCS_CMDS_SET_REMAP 0x02000018
+
+/* Configure DSI DCS command replacement. */
+#define HFI_COMMAND_DISPLAY_DSI_CUSTOM_DCS_CMDS_SET_REPLACE 0x02000019
+
+/* Configure display batch mode. */
+#define HFI_COMMAND_DISPLAY_BATCH_MODE 0x0200001a
+
+/* Configure the display HFI subsystem. */
+#define HFI_COMMAND_DISPLAY_HFI_SUBSYSTEM_CONFIG 0x0200001b
+
+/* Execute a DCS low-power command type. */
+#define HFI_COMMAND_DISPLAY_EXEC_DCS_CMD_TYPE 0x0200001c
+
+/* End of display-command range. */
+#define HFI_COMMAND_DISPLAY_END 0x02ffffff
+
+/* Alias for the display-command range end. */
+#define HFI_COMMAND_DISPLAY_CLOSE HFI_COMMAND_DISPLAY_END
+
+#endif /* __H_HFI_COMMANDS_DISPLAY_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display_events.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display_events.h
new file mode 100644
index 000000000000..48d9c936b672
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_display_events.h
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_DISPLAY_EVENTS_H
+#define __H_HFI_COMMANDS_DISPLAY_EVENTS_H
+
+/* Display-event command range. */
+#define HFI_COMMAND_DISPLAY_EVENT_BEGIN 0x04000000
+
+/* Report vertical sync and its index. */
+#define HFI_COMMAND_DISPLAY_EVENT_VSYNC 0x04000001
+
+/* Report frame-scan start and sequence ID. */
+#define HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START 0x04000002
+
+/* Report frame-scan completion. */
+#define HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE 0x04000003
+
+/* Report display idle state. */
+#define HFI_COMMAND_DISPLAY_EVENT_IDLE 0x04000004
+
+/* Report display power-state changes. */
+#define HFI_COMMAND_DISPLAY_EVENT_POWER 0x04000005
+
+/* Report display hardware recovery. */
+#define HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY 0x04000006
+
+/* Report frame-capture completion. */
+#define HFI_COMMAND_DISPLAY_EVENT_FRAME_CAPTURE_COMPLETE 0x04000007
+
+/* Report an unrecoverable panel failure. */
+#define HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD 0x04000008
+
+/* Report LTM statistics events. */
+#define HFI_COMMAND_DISPLAY_EVENT_LTM 0x04000009
+
+/* Report RGB histogram events. */
+#define HFI_COMMAND_DISPLAY_EVENT_RGB_HIST 0x0400000a
+
+/* Report PA histogram events. */
+#define HFI_COMMAND_DISPLAY_EVENT_PA_HIST 0x0400000b
+
+/* Report hotplug status. */
+#define HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS 0x0400000c
+
+/* Report EDID and supported modes. */
+#define HFI_COMMAND_DISPLAY_EVENT_EDID_INFO 0x0400000d
+
+/* Report SPR/OPR values at frame scan. */
+#define HFI_COMMAND_DISPLAY_EVENT_SPR_OPR 0x0400000e
+
+/* Report interface MISR values at frame scan. */
+#define HFI_COMMAND_DISPLAY_EVENT_INTERFACE_MISR 0x0400000f
+
+/* Request HDCP 1.x authentication start. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP1X_START 0x04000010
+
+/* Request HDCP 1.x authentication stop. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP1X_STOP 0x04000011
+
+/* Update HDCP 1.x encryption state. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP1X_ENC 0x04000012
+
+/* Update HDCP 1.x repeater topology. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP1X_TOPOLOGY_UPDATE 0x04000013
+
+/* Start the HDCP 2.x sequence. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP2X_START 0x04000014
+
+/* Process an HDCP 2.x message. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP2X_PROCESS_MSG 0x04000015
+
+/* Report an HDCP 2.x message timeout. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP2X_TIMEOUT 0x04000016
+
+/* Report sink HDCP capabilities. */
+#define HFI_COMMAND_DISPLAY_EVENT_HDCP_FEATURE_SUPPORTED 0x04000017
+
+/* Report COPR status data. */
+#define HFI_COMMAND_DISPLAY_EVENT_AIQE_COPR_READ 0x04000018
+
+/* Report programmable-line events. */
+#define HFI_COMMAND_DISPLAY_EVENT_PROG_LINE 0x04000019
+
+/* End of display-event command range. */
+#define HFI_COMMAND_DISPLAY_EVENT_END 0x04ffffff
+
+#endif /* __H_HFI_COMMANDS_DISPLAY_EVENTS_H */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h
new file mode 100644
index 000000000000..c01a001409ce
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display.h
@@ -0,0 +1,464 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_DEFS_DISPLAY_H__
+#define __H_HFI_DEFS_DISPLAY_H__
+
+#include "hfi_defs_common.h"
+
+/* Display blending and power controls. */
+enum hfi_display_blend_ops {
+ HFI_BLEND_OP_NOT_DEFINED = 0x0,
+ HFI_BLEND_OP_OPAQUE = 0x1,
+ HFI_BLEND_OP_PREMULTIPLIED = 0x2,
+ HFI_BLEND_OP_COVERAGE = 0x3,
+ HFI_BLEND_OP_SKIP = 0x4,
+ HFI_BLEND_OP_MAX = 0x5,
+};
+
+enum hfi_display_power_mode {
+ HFI_MODE_DPMS_INVALID = 0,
+ HFI_MODE_DPMS_OFF = 0x1,
+ HFI_MODE_DPMS_ON = 0x2,
+ HFI_MODE_DPMS_LP1 = 0x3,
+ HFI_MODE_DPMS_LP2 = 0x4,
+ HFI_MODE_DPMS_NOLP = 0x5,
+};
+
+enum hfi_display_power_control {
+ HFI_PANEL_POWER = 0x1,
+ HFI_CTRL_POWER = 0x2,
+ HFI_PHY_POWER = 0x4,
+};
+
+enum hfi_display_commit_flag {
+ HFI_VALIDATE = 0x1,
+ HFI_COMMIT = 0x2,
+};
+
+enum hfi_display_res_type {
+ HFI_RESOURCE_LM = 0x1,
+};
+
+/* Firmware resource configuration. */
+struct hfi_resource_cfg {
+ u32 res_type;
+ u32 resource_idx;
+ u32 width;
+ u32 height;
+ u32 reserved;
+};
+
+/* Display region of interest. */
+struct hfi_display_roi {
+ u32 x_pos;
+ u32 y_pos;
+ u32 width;
+ u32 height;
+};
+
+enum hfi_display_roi_type {
+ PANEL_ROI = 0x0,
+};
+
+/* VSync event payload. */
+struct hfi_display_vsync_data {
+ u32 timestamp_lo;
+ u32 timestamp_hi;
+ u32 vsync_index;
+};
+
+/* Frame-scan event payload. */
+struct hfi_display_frame_event_data {
+ u32 timestamp_lo;
+ u32 timestamp_hi;
+ u32 bufferflip_index;
+};
+
+struct hfi_display_color {
+ u16 color_0;
+ u16 color_1;
+ u16 color_2;
+ u16 color_3;
+};
+
+enum hfi_display_dim_layer_flag {
+ HFI_DIM_LAYER_INCLUSIVE = 0x1,
+ HFI_DIM_LAYER_EXCLUSIVE = 0x2,
+};
+
+/* Dim-layer configuration. */
+struct hfi_display_dim_layer {
+ enum hfi_display_dim_layer_flag flags;
+ u32 stage;
+ struct hfi_display_color color_fill;
+ struct hfi_display_roi rect;
+};
+
+enum hfi_layer_cache_state {
+ HFI_CACHE_STATE_DISABLE = 0x0,
+ HFI_CACHE_STATE_READ = 0x1,
+ HFI_CACHE_STATE_WRITE = 0x2,
+};
+
+enum hfi_layer_cache_op_type {
+ HFI_CACHE_OP_TYPE_NONE = 0x0,
+ HFI_CACHE_NORMAL_CACHEABLE_READ = 0x1,
+ HFI_CACHE_READ_INVALIDATE = 0x2,
+ HFI_CACHE_READ_EVICT = 0x3,
+ HFI_CACHE_PREFETCH_READ = 0x4,
+};
+
+/* Autorefresh configuration. */
+struct hfi_display_autorefresh_cfg {
+ u32 enable;
+ u32 frame_count;
+};
+
+struct hfi_display_idle_event_data {
+ u32 timestamp_lo;
+ u32 timestamp_hi;
+ u32 idle_index;
+};
+
+struct hfi_display_power_event_data {
+ u32 timestamp_lo;
+ u32 timestamp_hi;
+ enum hfi_display_power_mode power_state;
+};
+
+struct hfi_display_prog_line_event_data {
+ u32 timestamp_lo;
+ u32 timestamp_hi;
+ u32 prog_line_index;
+};
+
+enum hfi_display_idle_timer_control {
+ HFI_DEFAULT = 0x0,
+ HFI_WAKEUP = 0x1,
+ HFI_BLOCK_TIMER = 0x2,
+ HFI_UNBLOCK_TIMER = 0x3,
+};
+
+/* Display event identifiers. */
+enum hfi_display_event_id {
+ HFI_EVENT_VSYNC = 0x1,
+ HFI_EVENT_FRAME_SCAN_START = 0x2,
+ HFI_EVENT_FRAME_SCAN_COMPLETE = 0x3,
+ HFI_EVENT_FRAME_IDLE = 0x4,
+ HFI_EVENT_DISPLAY_POWER = 0x5,
+ HFI_EVENT_HW_RECOVERY = 0x6,
+ HFI_EVENT_FRAME_CAPTURE_COMPLETE = 0x7,
+ HFI_EVENT_PANEL_DEAD = 0x8,
+ HFI_EVENT_LTM = 0x9,
+ HFI_EVENT_RGB_HIST = 0xa,
+ HFI_EVENT_PA_HIST = 0xb,
+ HFI_EVENT_HPD_STATUS = 0xc,
+ HFI_EVENT_DISPLAY_EDID_INFO = 0xd,
+ HFI_EVENT_SPR_OPR = 0xe,
+ HFI_EVENT_INTF_MISR = 0xf,
+ HFI_EVENT_HDCP1X_START = 0x10,
+ HFI_EVENT_HDCP1X_STOP = 0x11,
+ HFI_EVENT_HDCP1X_ENC = 0x12,
+ HFI_EVENT_HDCP1X_TOPOLOGY_UPDATE = 0x13,
+ HFI_EVENT_HDCP2X_START = 0x14,
+ HFI_EVENT_HDCP2X_PROCESS_MSG = 0x15,
+ HFI_EVENT_HDCP2X_TIMEOUT = 0x16,
+ HFI_EVENT_HDCP_FEATURE_SUPPORTED = 0x17,
+ HFI_EVENT_AIQE_COPR = 0x18,
+ HFI_EVENT_PROG_LINE_INTR = 0x19,
+};
+
+enum hfi_display_dp_event {
+ HFI_DP_EVENT_NONE = 0x0,
+ HFI_DP_EVENT_HPD_PLUGGED = 0x1,
+ HFI_DP_EVENT_IRQ_HPD = 0x2,
+ HFI_DP_EVENT_SET_MODE = 0x3,
+ HFI_DP_EVENT_HPD_UNPLUGGED = 0x4,
+};
+
+enum hfi_display_dp_state {
+ HFI_DP_STATE_DISCONNECTED = 0x0,
+ HFI_DP_STATE_HPD_IN = 0x1,
+ HFI_DP_STATE_CONNECTED = 0x2,
+ HFI_DP_STATE_HPD_OUT = 0x3,
+};
+
+struct hfi_display_hpd_status {
+ enum hfi_display_dp_event dp_evt;
+ enum hfi_display_dp_state dp_state;
+};
+
+/* Display mode sent to firmware. */
+struct hfi_display_mode_info {
+ u32 size;
+ u32 h_active;
+ u32 h_back_porch;
+ u32 h_sync_width;
+ u32 h_front_porch;
+ u32 h_skew;
+ u32 h_sync_polarity;
+ u32 v_active;
+ u32 v_back_porch;
+ u32 v_sync_width;
+ u32 v_front_porch;
+ u32 v_sync_polarity;
+ u32 refresh_rate;
+ u32 clk_rate_hz_lo;
+ u32 clk_rate_hz_hi;
+ u32 flags_lo;
+ u32 flags_hi;
+ u32 reserved1;
+ u32 reserved2;
+};
+
+/* DSI command descriptor. */
+struct hfi_dsi_cmd_desc {
+ u32 size;
+
+ u8 channel;
+ u8 type;
+ u16 flags;
+
+ u32 tx_len;
+ u32 tx_buff_addr_lsb;
+ u32 tx_buff_addr_msb;
+
+ u32 rx_len;
+ u32 rx_buff_addr_lsb;
+ u32 rx_buff_addr_msb;
+
+ u32 ctrl_idx;
+ u32 ctrl_flags;
+ u32 last_command;
+ u32 post_wait_ms;
+
+ u32 seq_no;
+
+ u32 reserved2;
+};
+
+/* DSI command descriptor. */
+struct hfi_dsi_cmd_desc_set {
+ u32 size;
+ u32 type;
+ u32 count;
+ u32 state;
+ u32 seq_no;
+ u32 reserved2;
+ struct hfi_dsi_cmd_desc cmds[];
+};
+
+enum hfi_display_blend_stage {
+ HFI_BLEND_STAGE_BASE,
+ HFI_BLEND_STAGE_0,
+ HFI_BLEND_STAGE_1,
+ HFI_BLEND_STAGE_2,
+ HFI_BLEND_STAGE_3,
+ HFI_BLEND_STAGE_4,
+ HFI_BLEND_STAGE_5,
+ HFI_BLEND_STAGE_6,
+ HFI_BLEND_STAGE_7,
+ HFI_BLEND_STAGE_8,
+ HFI_BLEND_STAGE_9,
+ HFI_BLEND_STAGE_10,
+};
+
+enum hfi_layer_fetch_mode {
+ HFI_PARALLEL_FETCH = 0x0,
+ HFI_TIME_MULTIPLEX_FETCH = 0x1,
+};
+
+enum hfi_layer_security_policy {
+ HFI_LAYER_SECURITY_POLICY_NON_SECURE = 0x0,
+ HFI_LAYER_SECURITY_POLICY_SECURE = 0x1,
+ HFI_LAYER_SECURITY_POLICY_SECURE_DIR_TRANSLATION = 0x2,
+ HFI_LAYER_SECURITY_POLICY_MAX
+};
+
+#define HFI_DISPLAY_ROTATION_0 BIT(0)
+
+#define HFI_DISPLAY_ROTATION_90 BIT(1)
+
+#define HFI_DISPLAY_ROTATION_180 BIT(2)
+
+#define HFI_DISPLAY_ROTATION_270 BIT(3)
+
+#define HFI_DISPLAY_REFLECT_X BIT(4)
+
+#define HFI_DISPLAY_REFLECT_Y BIT(5)
+
+enum hfi_cwb_tap_points {
+ HFI_TAP_POINT_NONE,
+ HFI_TAP_POINT_LM,
+ HFI_TAP_POINT_DSPP,
+ HFI_TAP_POINT_DEMURA,
+ HFI_TAP_POINT_MAX,
+};
+
+enum hfi_fence_type {
+ HFI_FENCE_SCAN_START,
+ HFI_FENCE_SCAN_COMPLETE,
+};
+
+enum hfi_dp_sink_capability_flags {
+ HFI_DP_SINK_CAP_MST,
+ HFI_DP_SINK_CAP_DSC,
+ HFI_DP_SINK_CAP_FEC,
+};
+
+/* EDID and mode information. */
+struct hfi_display_event_edid_info {
+ u32 controller_id;
+ u32 stream_id;
+ u32 link_rate;
+ u32 lane_count;
+ u32 bits_per_pixel;
+ u32 flags;
+ struct hfi_buff edid_modes_buf;
+};
+
+struct hfi_display_dp_tu {
+ u32 dp_tu;
+ u32 valid_boundary;
+ u32 valid_boundary2;
+};
+
+/* Display audio configuration. */
+struct hfi_audio_config {
+ u32 sample_rate;
+ u32 num_of_channels;
+ u32 channel_allocation;
+ u32 level_shift;
+ u32 down_mix;
+ u32 sample_present;
+ u32 stream_id;
+};
+
+struct hfi_hdr_metadata {
+ u32 hdr_state;
+ u32 eotf;
+ u32 hdr_supported;
+ u32 display_primaries_x[HFI_MAX_COLOR_COMPONENTS];
+ u32 display_primaries_y[HFI_MAX_COLOR_COMPONENTS];
+ u32 white_point_x;
+ u32 white_point_y;
+ u32 max_luminance;
+ u32 min_luminance;
+ u32 max_content_light_level;
+ u32 max_average_light_level;
+};
+
+/* HDR display configuration. */
+struct hfi_display_hdr_cfg {
+ struct hfi_hdr_metadata hdr_meta;
+ u32 dynamic_hdr_payload_size;
+ u8 dynamic_hdr_payload[HFI_DHDR_PAYLOAD_MAX_SIZE];
+};
+
+enum hfi_colorimetry {
+ HFI_COLORIMETRY_DEFAULT = 0,
+ HFI_COLORIMETRY_SMPTE_170M_YCC = 1,
+ HFI_COLORIMETRY_BT709_YCC = 2,
+ HFI_COLORIMETRY_XVYCC_601 = 3,
+ HFI_COLORIMETRY_XVYCC_709 = 4,
+ HFI_COLORIMETRY_SYCC_601 = 5,
+ HFI_COLORIMETRY_OPYCC_601 = 6,
+ HFI_COLORIMETRY_OPRGB = 7,
+ HFI_COLORIMETRY_BT2020_CYCC = 8,
+ HFI_COLORIMETRY_BT2020_RGB = 9,
+ HFI_COLORIMETRY_BT2020_YCC = 10,
+ HFI_COLORIMETRY_DCI_P3_RGB_D65 = 11,
+ HFI_COLORIMETRY_DCI_P3_RGB_THEATER = 12,
+};
+
+enum hfi_misr_block {
+ HFI_MISR_DSI = 0x0,
+ HFI_MISR_MIXER = 0x1,
+ HFI_MISR_INTF = 0x2,
+};
+
+enum hfi_batch_mode {
+ HFI_BATCH_MODE_NONE = 0x0,
+ HFI_BATCH_MODE_START = 0x1,
+ HFI_BATCH_MODE_END = 0x2,
+ HFI_BATCH_MODE_CANCEL = 0x3,
+};
+
+enum hfi_batch_usecase_id {
+ HFI_BATCH_USECASE_NONE = 0,
+ HFI_BATCH_USECASE_GMU_REPROJ,
+};
+
+struct hfi_misr_config {
+ u32 enable;
+ u32 frame_count;
+ enum hfi_misr_block block;
+};
+
+/* HDCP 1.x topology. */
+struct hfi_hdcp1x_topology_data {
+ u32 depth;
+ u32 device_count;
+ u32 max_devices_exceeded;
+ u32 max_cascade_exceeded;
+};
+
+/* HDCP 2.x message payload. */
+struct hfi_hdcp2_message {
+ u32 timeout_ms;
+ u32 repeater_flag;
+ struct hfi_buff request;
+ struct hfi_buff response;
+};
+
+struct hfi_display_mode_extended_info {
+ u32 size;
+ struct hfi_display_mode_info base;
+ u32 mixer_count;
+ u32 cmpr_count;
+ u32 cmpr_enabled;
+ u32 cmpr_bpp;
+ u32 cmpr_slice_count;
+ u8 test_pattern;
+ u8 reserved[7];
+};
+
+struct hfi_cmd_set_remap {
+ u32 cmd_type;
+ u32 custom_cmd_type;
+ u32 flag;
+ u32 reserved;
+};
+
+struct hfi_dsi_dcs_cmd_set_replace_entry {
+ u32 dpu_buff_type_offset;
+ u32 cmd_type;
+ u32 count_cmds;
+ u32 hfi_buff_struct_offset;
+ u32 reserved;
+};
+
+struct hfi_custom_wd_te_params {
+ u32 wd_te_enabled;
+ u32 custom_fps;
+};
+
+/* Display batch-mode configuration. */
+struct hfi_batch_mode_info {
+ enum hfi_batch_mode mode;
+ enum hfi_batch_usecase_id usecase_id;
+ u32 reserved[2];
+};
+
+#define HFI_WB_DNSC_CFG_DISABLE BIT(0)
+
+struct hfi_dnsc_cfg {
+ u32 flags;
+ u32 dst_width;
+ u32 dst_height;
+};
+
+#endif /* __H_HFI_DEFS_DISPLAY_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h
new file mode 100644
index 000000000000..7a474e0c55b7
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_display_color.h
@@ -0,0 +1,247 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_DEFS_DISPLAY_COLOR_H__
+#define __H_HFI_DEFS_DISPLAY_COLOR_H__
+
+#include <linux/types.h>
+
+#include "hfi_defs_common.h"
+
+#define HFI_DISPLAY_COLOR_FLAGS_FEATURE_ENABLE BIT(0)
+
+#define HFI_DISPLAY_COLOR_FLAGS_FEATURE_BROADCAST BIT(1)
+
+#define HFI_DISPLAY_COLOR_HW_BLK_INDEX_0_FLAG BIT(2)
+#define HFI_DISPLAY_COLOR_HW_BLK_INDEX_1_FLAG BIT(3)
+#define HFI_DISPLAY_COLOR_HW_BLK_INDEX_2_FLAG BIT(4)
+#define HFI_DISPLAY_COLOR_HW_BLK_INDEX_3_FLAG BIT(5)
+
+#define HFI_DITHER_MATRIX_SZ 16
+#define HFI_DITHER_LUMA_MODE BIT(0)
+#define HFI_DITHER_OFFSET_ENABLE BIT(1)
+
+#define HFI_DITHER_MATRIX_SZ_EXTENDED 256
+#define HFI_DITHER_MATRIX_SELECT_4_4 0
+#define HFI_DITHER_MATRIX_SELECT_6_6 1
+#define HFI_DITHER_MATRIX_SELECT_8_8 2
+#define HFI_DITHER_MATRIX_SELECT_16_16 3
+
+/* Dither configuration. */
+struct hfi_display_dither {
+ u32 flags;
+ u32 feature_flags;
+ u32 temporal_en;
+ u32 c0_bitdepth;
+ u32 c1_bitdepth;
+ u32 c2_bitdepth;
+ u32 c3_bitdepth;
+ u32 dither_matrix_select;
+ s32 matrix[HFI_DITHER_MATRIX_SZ_EXTENDED];
+};
+
+/* PA dither configuration. */
+struct hfi_display_pa_dither {
+ u32 flags;
+ u32 strength;
+ u32 offset_en;
+ u32 matrix[HFI_DITHER_MATRIX_SZ];
+};
+
+#define HFI_QRTC_LUT_SIZE 81 /* Each LUT is 9x9 */
+#define HFI_QRTC_NUM_LUTS 3 /* Number of LPF/HPF tables */
+
+/* QRTC subsampling modes. */
+#define HFI_QRTC_SUBSAMPLE_1x1 0x0 /* Horizontal: 1, Vertical: 1 */
+#define HFI_QRTC_SUBSAMPLE_2x1 0x1 /* Horizontal: 2, Vertical: 1 */
+#define HFI_QRTC_SUBSAMPLE_2x2 0x2 /* Horizontal: 2, Vertical: 2 */
+#define HFI_QRTC_SUBSAMPLE_3x3 0x3 /* Horizontal: 3, Vertical: 3 */
+
+/* QRTC DMA selections. */
+#define HFI_QRTC_DMA_1 0x1
+#define HFI_QRTC_DMA_3 0x3
+
+/* QRTC rectangle selections. */
+#define HFI_QRTC_RECT0 0x0 /* Select Rectangle 0 */
+#define HFI_QRTC_RECT1 0x1 /* Select Rectangle 1 */
+#define HFI_WRITE_BACK_0 0x0
+#define HFI_WRITE_BACK_1 0x1
+#define HFI_WRITE_BACK_2 0x2
+#define HFI_CWB_BLOCK_0 0x0
+#define HFI_CWB_BLOCK_1 0x1
+#define HFI_CWB_BLOCK_2 0x2
+#define HFI_CWB_BLOCK_3 0x3
+#define HFI_CWB_BLOCK_4 0x4
+#define HFI_CWB_BLOCK_5 0x5
+#define HFI_CWB_BLOCK_6 0x6
+#define HFI_CWB_BLOCK_7 0x7
+
+/* Bit 16 marks a secure QRTC buffer. */
+#define HFI_QRTC_SECURE_BUFF BIT(16)
+
+/* QRTC configuration and capture-buffer selection. */
+struct hfi_qrtc_config {
+ u32 flags;
+ u32 lut_dma_flags;
+ u32 iova_l;
+ u32 iova_h;
+ u32 len;
+ u32 coring_en;
+ u32 coring_pos;
+ u32 coring_neg;
+ u32 lpf_en;
+ u32 subsample_mode;
+ u32 dma_sel;
+ u32 rect_sel;
+ u32 wb_sel;
+ u32 cwb_sel;
+ u32 cwb_tap_point;
+ u32 cwb_iova_l;
+ u32 cwb_iova_h;
+ u32 cwb_len;
+ u32 cwb_height;
+ u32 cwb_width;
+ u32 format;
+ u32 aligned_cwb_height;
+ u32 aligned_cwb_width;
+};
+
+#define HFI_LTM_DATA_SIZE_0 32
+#define HFI_LTM_DATA_SIZE_1 128
+#define HFI_LTM_DATA_SIZE_2 256
+#define HFI_LTM_BLOCK_MAX 4
+
+#define HFI_LTM_STATS_SAT BIT(1)
+#define HFI_LTM_STATS_MERGE_SAT BIT(2)
+
+#define HFI_LTM_HIST_DONE BIT(0)
+#define HFI_LTM_WB_PB BIT(1)
+#define HFI_LTM_HIST_OFF BIT(2)
+
+struct hfi_display_ltm_init_param {
+ u32 flags;
+ u32 init_param_01;
+ u32 init_param_02;
+ u32 init_param_03;
+ u32 init_param_04;
+};
+
+struct hfi_display_ltm_cfg_param {
+ u32 flags;
+ u32 cfg_param_01;
+ u32 cfg_param_02;
+ u32 cfg_param_03;
+ u32 cfg_param_04;
+ u32 cfg_param_05;
+ u32 cfg_param_06;
+};
+
+/* Shared LTM statistics buffer descriptor. */
+struct hfi_display_ltm_buffer {
+ u32 flags;
+ u32 dpu_iova_l;
+ u32 dpu_iova_h;
+ u32 dcp_addr_l;
+ u32 dcp_addr_h;
+ u32 size;
+};
+
+struct hfi_display_ltm_stats_data {
+ u32 stats_01[HFI_LTM_DATA_SIZE_0][HFI_LTM_DATA_SIZE_1];
+ u32 stats_02[HFI_LTM_DATA_SIZE_2];
+ u32 stats_03[HFI_LTM_DATA_SIZE_0];
+ u32 stats_04[HFI_LTM_DATA_SIZE_0];
+ u32 stats_05[HFI_LTM_DATA_SIZE_0];
+ u32 status_flag;
+ u32 display_h;
+ u32 display_v;
+ u32 init_h[HFI_LTM_BLOCK_MAX];
+ u32 init_v;
+ u32 inc_h;
+ u32 inc_v;
+ u32 portrait_en;
+ u32 merge_en;
+ u32 cfg_param_01;
+ u32 cfg_param_02;
+ u32 cfg_param_03;
+ u32 cfg_param_04;
+ u32 feature_flag;
+ u32 checksum;
+};
+
+struct hfi_display_ltm_event_resp {
+ u32 event_type;
+ u32 dcp_addr_h;
+ u32 dcp_addr_l;
+};
+
+#define HFI_RGB_HISTOGRAM_BIN_COUNT 1024
+#define HFI_RGB_COMPONENT_SIZE 3
+
+#define HFI_RGB_HIST_ROI_ENABLE BIT(1)
+
+#define HFI_RGB_HIST_DONE BIT(0)
+#define HFI_RGB_HIST_WB_ERR BIT(1)
+#define HFI_RGB_HIST_OFF BIT(2)
+
+struct hfi_display_rgb_hist_ctrl {
+ u32 flags;
+ u32 roi_mode;
+ u32 roi_x;
+ u32 roi_y;
+ u32 roi_width;
+ u32 roi_height;
+ u32 tap_point;
+ u32 colorspace_mode;
+};
+
+struct hfi_display_rgb_hist_stats_data {
+ u32 hist[HFI_RGB_HISTOGRAM_BIN_COUNT];
+ u32 hist_min;
+ u32 hist_max;
+ u32 hist_sum_lsb;
+ u32 hist_sum_msb;
+ u32 hcount;
+ u32 crc;
+ u32 status_flags;
+ u32 roi_mode;
+ u32 roi_x;
+ u32 roi_y;
+ u32 roi_width;
+ u32 roi_height;
+ u32 tap_point;
+ u32 colorspace_mode;
+};
+
+/* Shared RGB histogram buffer descriptor. */
+struct hfi_display_rgb_hist_buffer {
+ u32 flags;
+ u32 dpu_iova_lo[HFI_RGB_COMPONENT_SIZE];
+ u32 dpu_iova_hi[HFI_RGB_COMPONENT_SIZE];
+ u32 dcp_addr_lo[HFI_RGB_COMPONENT_SIZE];
+ u32 dcp_addr_hi[HFI_RGB_COMPONENT_SIZE];
+ u32 size;
+};
+
+struct hfi_display_rgb_hist_event_resp {
+ u32 event_type;
+ u32 dcp_addr_lo[HFI_RGB_COMPONENT_SIZE];
+ u32 dcp_addr_hi[HFI_RGB_COMPONENT_SIZE];
+};
+
+/* Shared PA histogram buffer descriptor. */
+struct hfi_display_pa_hist_buffer {
+ u32 flags;
+ u32 dcp_addr_lo;
+ u32 dcp_addr_hi;
+ u32 size;
+};
+
+struct hfi_display_pa_hist_event_resp {
+ u32 dcp_addr_lo;
+ u32 dcp_addr_hi;
+};
+
+#endif /* __H_HFI_DEFS_DISPLAY_COLOR_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_display.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_display.h
new file mode 100644
index 000000000000..34a4220393f4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_display.h
@@ -0,0 +1,480 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PROPERTIES_DISPLAY_H__
+#define __H_HFI_PROPERTIES_DISPLAY_H__
+
+/* Display-property range. */
+#define HFI_PROPERTY_DISPLAY_BEGIN 0x00020000
+
+/* Whether DIM layer is supported. */
+#define HFI_PROPERTY_DISPLAY_HAS_DIM_LAYER 0x00020001
+
+/* Whether Idle PC is supported. */
+#define HFI_PROPERTY_DISPLAY_HAS_IDLE_PC 0x00020002
+
+/* Whether LM Noise is supported. */
+#define HFI_PROPERTY_DISPLAY_LM_NOISE 0x00020003
+
+/* Noise layer version. */
+#define HFI_PROPERTY_DISPLAY_NOISE_VERSION 0x00020004
+
+/* Whether dedicated CWB is supported. */
+#define HFI_PROPERTY_DISPLAY_FEATURE_DEDICATED_CWB 0x00020005
+
+/* Whether CWB feature is supported. */
+#define HFI_PROPERTY_DISPLAY_FEATURE_CWB 0x00020006
+
+/* Whether system cache is used for static display traffic. */
+#define HFI_PROPERTY_DISPLAY_SYS_CACHE_DISP 0x00020007
+
+/* Whether trusted-display mode is supported. */
+#define HFI_PROPERTY_DISPLAY_FEATURE_TRUSTED_DISPLAY_MODE 0x00020008
+
+/* Whether UBWC Statistics is supported. */
+#define HFI_PROPERTY_DISPLAY_UBWC_STATS 0x00020009
+
+/* Whether dynamic clock control is supported. */
+#define HFI_PROPERTY_DISPLAY_DYN_CLK_SUPPORT 0x0002000a
+
+/* Whether qsync mode is supported. */
+#define HFI_PROPERTY_DISPLAY_QSYNC 0x0002000b
+
+/* Whether AVR Step is supported. */
+#define HFI_PROPERTY_DISPLAY_AVR_STEP 0x0002000c
+
+/* Whether CWB Dither is supported. */
+#define HFI_PROPERTY_DISPLAY_WB_CWB_DITHER 0x0002000d
+
+/* Whether system cache is used for writeback. */
+#define HFI_PROPERTY_DISPLAY_WB_SYS_CACHE_DISP 0x0002000e
+
+/* Whether writeback supports linear rotation. */
+#define HFI_PROPERTY_DISPLAY_WB_LINEAR_ROTATION 0x0002000f
+
+/* The extended power modes supported by the Display. */
+#define HFI_PROPERTY_DISPLAY_POWER_MODE 0x00020010
+
+/* The DPU DRAM IB vote for DRAM(LLCC(SDE)->DDR) in Kbps. */
+#define HFI_PROPERTY_DISPLAY_DRAM_IB 0x00020011
+
+/* The DPU DRAM AB vote for DRAM(LLCC(SDE)->DDR) in Kbps. */
+#define HFI_PROPERTY_DISPLAY_DRAM_AB 0x00020012
+
+/* The DPU LLCC IB vote for LLCC(SDE_master_on_MNOC->LLCC). */
+#define HFI_PROPERTY_DISPLAY_LLCC_IB 0x00020013
+
+/* The DPU LLCC IB vote for LLCC(SDE_master_on_MNOC->LLCC). */
+#define HFI_PROPERTY_DISPLAY_LLCC_AB 0x00020014
+
+/* The DPU IB vote for MNOC(virt_master-> virt_slave(NOC)). */
+#define HFI_PROPERTY_DISPLAY_CORE_IB 0x00020015
+
+/* The DPU AB vote for MNOC(virt_master-> virt_slave(NOC)). */
+#define HFI_PROPERTY_DISPLAY_CORE_AB 0x00020016
+
+/* The Mdp core clock in hertz. */
+#define HFI_PROPERTY_DISPLAY_CORE_CLK 0x00020017
+
+/* Associates a layer before its properties are programmed. */
+#define HFI_PROPERTY_DISPLAY_ATTACH_LAYER 0x00020018
+
+/* Removes a layer from the display. */
+#define HFI_PROPERTY_DISPLAY_DETACH_LAYER 0x00020019
+
+/* Commit sequence returned with frame-scan events. */
+#define HFI_PROPERTY_DISPLAY_SCAN_SEQUENCE_ID 0x0002001a
+
+/* Enables or disables interface auto-refresh. */
+#define HFI_PROPERTY_DISPLAY_AUTOREFRESH_CFG 0x0002001b
+
+/* Configure the HFI queue. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_HFI_CONFIG 0x0002001c
+
+/* Reports scratch-memory size required by LSR writeback. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_NEEDS_SCRATCH_MEM 0x0002001d
+
+/* Configure the scratch memory. */
+#define HFI_PROPERTY_DISPLAY_SET_SCRATCH_MEM 0x0002001e
+
+/* Configure the sync display. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_REPROJ_SYNC_TO 0x0002001f
+
+/* Configure the LSR. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_REPROJ_CONFIG_MATRIX 0x00020020
+
+/* Configure. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_REPROJ_CONFIG_EXT 0x00020021
+
+/* Configure. */
+#define HFI_PROPERTY_DISPLAY_INPUT_FENCE 0x00020022
+
+/* Provide a reusable fence. */
+#define HFI_PROPERTY_DISPLAY_REUSABLE_FENCE 0x00020023
+
+/* Configure the HFI queue. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_CVP_BUFF 0x00020025
+
+/* Configure the output buffers. */
+#define HFI_PROPERTY_DISPLAY_LSR_WB_OUT_BUFFERS 0x00020026
+
+/* Configure the LSR. */
+#define HFI_PROPERTY_DISPLAY_LSR_MAPPED_HFI_BASE_ADDR 0x00020027
+
+/* Configure the shared memory. */
+#define HFI_PROPERTY_DISPLAY_HRP_HFI_CONFIG 0x00020028
+
+/* Attach output layer before. */
+#define HFI_PROPERTY_DISPLAY_ATTACH_OUTPUT_LAYER 0x00020029
+
+/* Removes an output layer from the display. */
+#define HFI_PROPERTY_DISPLAY_DETACH_OUTPUT_LAYER 0x0002002a
+
+/* Set destination ROI for display. */
+#define HFI_PROPERTY_DISPLAY_DEST_ROI 0x0002002b
+
+/* Configures output fence. */
+#define HFI_PROPERTY_DISPLAY_OUTPUT_FENCE 0x0002002c
+
+/* The dim layer parameters for display output. */
+#define HFI_PROPERTY_DISPLAY_DIM_LAYER 0x0002002d
+
+/* The dim layer parameters for display output. */
+#define HFI_PROPERTY_DISPLAY_MISR_CONFIG 0x0002002e
+
+/* QSYNC mode for video/command mode panels. */
+#define HFI_PROPERTY_DISPLAY_QSYNC_MODE 0x0002002f
+
+/* Configure resource output. */
+#define HFI_PROPERTY_DISPLAY_SET_RESOURCE_DATA 0x00020030
+
+/* Set VRR (Variable Refresh Rate). */
+#define HFI_PROPERTY_DISPLAY_VRR_FRAME_PARAMS 0x00020031
+
+/* The Expected Preset Time which is used. */
+#define HFI_PROPERTY_DISPLAY_EPT 0x00020032
+
+/* This property sets line pointer offset time in nanoseconds. */
+#define HFI_PROPERTY_DISPLAY_VSYNC_OFFSET 0x00020033
+
+/* Update the panel brightness level. */
+#define HFI_PROPERTY_DISPLAY_BRIGHTNESS 0x00020034
+
+/* Set vsync source to watchdog. */
+#define HFI_PROPERTY_DISPLAY_CUSTOM_WD_TE 0x00020035
+
+/* Display color-property range. */
+#define HFI_PROPERTY_DISPLAY_COLOR_BEGIN 0x00020100
+
+/* Setup 3D LUT. */
+#define HFI_PROPERTY_DISPLAY_COLOR_3D_LUT 0x00020101
+
+/* Setup IGC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_IGC 0x00020102
+
+/* Setup GC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_GC 0x00020103
+
+/* Setup PCC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_PCC 0x00020104
+
+/* Setup PA HSIC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_HSIC 0x00020105
+
+/* Setup PA MEMCOLOR SKIN. */
+#define HFI_PROPERTY_DISPLAY_COLOR_MEMCOLOR_SKIN 0x00020106
+
+/* Setup PA MEMCOLOR SKY. */
+#define HFI_PROPERTY_DISPLAY_COLOR_MEMCOLOR_SKY 0x00020107
+
+/* Setup PA MEMCOLOR FOLIAGE. */
+#define HFI_PROPERTY_DISPLAY_COLOR_MEMCOLOR_FOLIAGE 0x00020108
+
+/* Setup PA MEMCOLOR PROTECTION. */
+#define HFI_PROPERTY_DISPLAY_COLOR_MEMCOLOR_PROT 0x00020109
+
+/* Setup PA SIXZONE. */
+#define HFI_PROPERTY_DISPLAY_COLOR_SIXZONE 0x0002010a
+
+/* Setup PA VLUT. */
+#define HFI_PROPERTY_DISPLAY_COLOR_VLUT 0x0002010b
+
+/* Setup PPB DITHER. */
+#define HFI_PROPERTY_DISPLAY_COLOR_DITHER 0x0002010c
+
+/* Setup RC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_RC 0x0002010d
+
+/* Setup SPR_INIT config. */
+#define HFI_PROPERTY_DISPLAY_COLOR_SPR_INIT 0x0002010e
+
+/* Setup SPR_UDC config. */
+#define HFI_PROPERTY_DISPLAY_COLOR_SPR_UDC 0x0002010f
+
+/* Setup DSPP SPR DITHER. */
+#define HFI_PROPERTY_DISPLAY_COLOR_SPR_DITHER 0x00020110
+
+/* Setup DSPP PA DITHER. */
+#define HFI_PROPERTY_DISPLAY_COLOR_PA_DITHER 0x00020111
+
+/* Setup Demura. */
+#define HFI_PROPERTY_DISPLAY_COLOR_DEMURA_CFG 0x00020112
+
+/* Setup Demura tables. */
+#define HFI_PROPERTY_DISPLAY_COLOR_DEMURA_TABLE 0x00020113
+
+/* Setup Demura backlight. */
+#define HFI_PROPERTY_DISPLAY_COLOR_DEMURA_BACKLIGHT 0x00020114
+
+/* Setup destination scaler. */
+#define HFI_PROPERTY_DISPLAY_DEST_SCALER 0x00020115
+
+/* Setup QRTC config. */
+#define HFI_PROPERTY_DISPLAY_COLOR_QRTC_CONFIG 0x00020116
+
+/* Setup LTM initialization params. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_INIT 0x00020117
+
+/* Setup LTM configuration params. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_CFG 0x00020118
+
+/* Setup LTM noise threshold. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_NOISE_THRESH 0x00020119
+
+/* Setup LTM histogram control. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_HIST_CTRL 0x0002011a
+
+/* Setup LTM VLUT table. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_VLUT 0x0002011b
+
+/* Setup LTM initial params. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_QUEUE_BUF 0x0002011c
+
+/* Setup LTM initial params. */
+#define HFI_PROPERTY_DISPLAY_COLOR_LTM_CLEAR_BUFS 0x0002011d
+
+/* Property to configure RGB hist control. */
+#define HFI_PROPERTY_DISPLAY_COLOR_RGB_HIST_CTRL 0x0002011e
+
+/* Property to queue RGB histogram. */
+#define HFI_PROPERTY_DISPLAY_COLOR_RGB_HIST_QUEUE_BUFFER 0x0002011f
+
+/* Property to clear all queued RGB. */
+#define HFI_PROPERTY_DISPLAY_COLOR_RGB_HIST_CLEAR_BUFFERS 0x00020120
+
+/* Setup AI Scaler. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_AI_SCALER 0x00020121
+
+/* Setup SSRC configuration. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_SSRC_CONFIG 0x00020122
+
+/* Setup SSRC data. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_SSRC_DATA 0x00020123
+
+/* Setup MDNIE. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_MDNIE 0x00020124
+
+/* Setup IPC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_MDNIE_IPC 0x00020125
+
+/* Setup ART. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_MDNIE_ART 0x00020126
+
+/* Setup ABC. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_ABC 0x00020127
+
+/* Setup RC for PU. */
+#define HFI_PROPERTY_DISPLAY_COLOR_RC_PU 0x00020128
+
+/* Property to configure PA hist control. */
+#define HFI_PROPERTY_DISPLAY_COLOR_PA_HIST_CTRL 0x00020129
+
+/* Property to queue PA histogram. */
+#define HFI_PROPERTY_DISPLAY_COLOR_PA_HIST_QUEUE_BUFFER 0x0002012a
+
+/* Property to clear all queued PA. */
+#define HFI_PROPERTY_DISPLAY_COLOR_PA_HIST_CLEAR_BUFFERS 0x0002012b
+
+/* Property to setup SPR partial update. */
+#define HFI_PROPERTY_DISPLAY_COLOR_SPR_PU 0x0002012c
+
+/* Property to setup SPR partial update. */
+#define HFI_PROPERTY_DISPLAY_COLOR_AIQE_COPR 0x0002012d
+
+/* End of display color properties. */
+#define HFI_PROPERTY_DISPLAY_COLOR_END 0x000201ff
+
+/* End of display-property range. */
+#define HFI_PROPERTY_DISPLAY_END 0x0002ffff
+
+/* Layer-property range. */
+#define HFI_PROPERTY_LAYER_BEGIN 0x00030000
+
+/* Whether layer decimation is supported. */
+#define HFI_PROPERTY_LAYER_FEATURE_DECIMATION 0x00030001
+
+/* Blend operations set by Host. */
+#define HFI_PROPERTY_LAYER_BLEND_TYPE 0x00030002
+
+/* Blending alpha of the plane. */
+#define HFI_PROPERTY_LAYER_ALPHA 0x00030003
+
+/* Z-order of the plane. */
+#define HFI_PROPERTY_LAYER_ZPOS 0x00030004
+
+/* Source ROI per layer. */
+#define HFI_PROPERTY_LAYER_SRC_ROI 0x00030005
+
+/* Destination ROI per layer. */
+#define HFI_PROPERTY_LAYER_DEST_ROI 0x00030006
+
+/* Configures blend roi. */
+#define HFI_PROPERTY_LAYER_BLEND_ROI 0x00030007
+
+/* Total Image Height. */
+#define HFI_PROPERTY_LAYER_SRC_IMG_SIZE_H 0x00030008
+
+/* Total Image Width. */
+#define HFI_PROPERTY_LAYER_SRC_IMG_SIZE_W 0x00030009
+
+/* SMMU mapped source Image start address. */
+#define HFI_PROPERTY_LAYER_SRC_ADDR 0x0003000a
+
+/* Source Format Configuration for each layer. */
+#define HFI_PROPERTY_LAYER_SRC_FORMAT 0x0003000b
+
+/* Stride for each layer. */
+#define HFI_PROPERTY_LAYER_STRIDE 0x0003000c
+
+/* Multi rectangle fetch modes. */
+#define HFI_PROPERTY_LAYER_MULTIRECT_MODE 0x0003000d
+
+/* Blending background alpha of the plane. */
+#define HFI_PROPERTY_LAYER_BG_ALPHA 0x0003000e
+
+/* Source Region of Interest (ROI). */
+#define HFI_PROPERTY_OUTPUT_LAYER_SRC_ROI 0x0003000f
+
+/* Destination Region of Interest(ROI). */
+#define HFI_PROPERTY_OUTPUT_LAYER_DST_ROI 0x00030010
+
+/* Total image height of output buffer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_SRC_IMG_SIZE_H 0x00030011
+
+/* Total image width of output buffer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_SRC_IMG_SIZE_W 0x00030012
+
+/* SMMU mapped start address. */
+#define HFI_PROPERTY_OUTPUT_LAYER_DST_ADDR 0x00030013
+
+/* Destination Format Configuration of output buffer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_DST_FORMAT 0x00030014
+
+/* Stride for each layer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_STRIDE 0x00030015
+
+/* Rotation for each layer. */
+#define HFI_PROPERTY_LAYER_ROTATION 0x00030016
+
+/* Security policy settings applied. */
+#define HFI_PROPERTY_LAYER_SECURITY_POLICY 0x00030017
+
+/* The LSR alpha buffer properties of the input layer. */
+#define HFI_PROPERTY_LAYER_LSR_IN_A_BUFFER 0x00030018
+
+/* The LSR Reprojection layer's plane equation. */
+#define HFI_PROPERTY_LAYER_LSR_REPROJ_PLANE_EQ 0x0003001a
+
+/* The layer's gamma type. */
+#define HFI_PROPERTY_LAYER_GAMMA 0x0003001b
+
+/* The LSR layer's local/remote/other type. */
+#define HFI_PROPERTY_LAYER_LSR_LAYER_TYPE 0x0003001c
+
+/* The LSR layer's lock type. */
+#define HFI_PROPERTY_LAYER_LSR_LOCK_TYPE 0x0003001d
+
+/* Sets the LSR reprojection render pose. */
+#define HFI_PROPERTY_LAYER_LSR_RENDER_POSE 0x0003001e
+
+/* The LSR render frustum properties of the layer. */
+#define HFI_PROPERTY_LAYER_LSR_RENDER_FRUSTUM 0x0003001f
+
+/* Custom buffers for this layer. */
+#define HFI_PROPERTY_LAYER_LSR_X_BUFFER 0x00030020
+
+/* The LSR Reprojection pose properties of the layer. */
+#define HFI_PROPERTY_LAYER_LSR_REPROJ_POSE 0x00030021
+
+/* Source image size of each layer in bytes. */
+#define HFI_PROPERTY_LAYER_SRC_IMAGE_SIZE 0x00030022
+
+/* Rotation for output layer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_ROTATION 0x00030023
+
+/* Sys cache related attributes for each layer. */
+#define HFI_PROPERTY_LAYER_CACHE_ATTR 0x00030024
+
+/* LLCC scid of the system cache to be used. */
+#define HFI_PROPERTY_LAYER_LLCC_SCID 0x00030025
+
+/* Sets system-cache attributes for the output layer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_CACHE_ATTR 0x00030026
+
+/* LLCC scid of the system cache to be used. */
+#define HFI_PROPERTY_OUTPUT_LAYER_LLCC_SCID 0x00030027
+
+/* The CWB tap point for output layer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_CWB_TAP_POINT 0x00030028
+
+/* Configure downscale blur. */
+#define HFI_PROPERTY_OUTPUT_LAYER_DNSC_BLUR_CFG 0x00030029
+
+/* Source exclusion rectangle region. */
+#define HFI_PROPERTY_LAYER_EXCLUSION_RECTANGLE_ROI 0x0003002a
+
+/* Configure downscale. */
+#define HFI_PROPERTY_OUTPUT_LAYER_DNSC_CFG 0x0003002b
+
+/* The security policy for output layer. */
+#define HFI_PROPERTY_OUTPUT_LAYER_SECURITY_POLICY 0x00030031
+
+/* Sets the disparity-layer injection interval. */
+#define HFI_PROPERTY_LAYER_LSR_DISPARITY_PHASE 0x00030032
+
+/* Layer color-property range. */
+#define HFI_PROPERTY_LAYER_COLOR_BEGIN 0x00030100
+
+/* Setup VIG GAMUT. */
+#define HFI_PROPERTY_LAYER_COLOR_3D_LUT 0x00030101
+
+/* Setup UCSC IGC. */
+#define HFI_PROPERTY_LAYER_COLOR_UCSC_IGC 0x00030102
+
+/* Setup UCSC UNMULT. */
+#define HFI_PROPERTY_LAYER_COLOR_UCSC_UNMULT 0x00030103
+
+/* Setup UCSC GC. */
+#define HFI_PROPERTY_LAYER_COLOR_UCSC_GC 0x00030104
+
+/* Setup UCSC ALPHA DITHER. */
+#define HFI_PROPERTY_LAYER_COLOR_UCSC_ALPHA_DITHER 0x00030105
+
+/* Setup UCSC CSC. */
+#define HFI_PROPERTY_LAYER_COLOR_UCSC_CSC 0x00030106
+
+/* Setup CSC. */
+#define HFI_PROPERTY_LAYER_COLOR_CSC 0x00030107
+
+/* Setup VIG scaler. */
+#define HFI_PROPERTY_LAYER_COLOR_SCALER 0x00030108
+
+/* End of layer color properties. */
+#define HFI_PROPERTY_LAYER_COLOR_END 0x000301ff
+
+/* End of layer-property range. */
+#define HFI_PROPERTY_LAYER_END 0x0003ffff
+
+#endif /* __H_HFI_PROPERTIES_DISPLAY_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 05/23] drm/msm/dcp: Add HFI panel definitions
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (3 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 04/23] drm/msm/dcp: Add HFI display definitions Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport Jayant Shekhar
` (18 subsequent siblings)
23 siblings, 0 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Define the HFI panel protocol required to initialize DSI panels and
issue DCS transfers through DCP firmware. Without these definitions,
the DSI path cannot describe panel modes or panel commands to firmware.
Describe panel commands, timing and panel data structures, and property
identifiers used by panel initialization and DCS transfer helpers.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h | 27 ++
drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h | 287 +++++++++++++++++++++
drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h | 198 ++++++++++++++
3 files changed, 512 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h
new file mode 100644
index 000000000000..ceccc8da8a29
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_commands_panel.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_COMMANDS_PANEL_H__
+#define __H_HFI_COMMANDS_PANEL_H__
+
+/* Panel-command range. */
+#define HFI_COMMAND_PANEL_BEGIN 0x03000000
+
+/* Start panel initialization; send with timing and generic packets in one HFI header. */
+#define HFI_COMMAND_PANEL_INIT_PANEL_CAPS 0x03000001
+
+/* Provide one panel timing-mode description. */
+#define HFI_COMMAND_PANEL_INIT_TIMING_MODE_CAPS 0x03000002
+
+/* Provide non-timing panel properties. */
+#define HFI_COMMAND_PANEL_INIT_GENERIC_CAPS 0x03000003
+
+/* Deinitialize panel attributes. */
+#define HFI_COMMAND_PANEL_DEINIT 0x03000004
+
+/* End of panel-command range. */
+#define HFI_COMMAND_PANEL_END 0x03ffffff
+
+#endif /* __H_HFI_COMMANDS_PANEL_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h
new file mode 100644
index 000000000000..a38d778898b2
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h
@@ -0,0 +1,287 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_DEFS_PANEL_H__
+#define __H_HFI_DEFS_PANEL_H__
+
+#include "hfi_defs_common.h"
+
+/* Panel format, link, and power properties. */
+enum hfi_panel_phy_type {
+ HFI_PANEL_PHY_OLED = 0x1,
+ HFI_PANEL_PHY_LCD = 0x2,
+};
+
+/* Lower values have higher display priority. */
+enum hfi_panel_display_type {
+ HFI_PANEL_DISPLAY_TYPE_NONE = 0x0,
+ HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0 = 0x1,
+ HFI_PANEL_DISPLAY_TYPE_BUILT_IN_1 = 0x2,
+ HFI_PANEL_DISPLAY_TYPE_BUILT_IN_2 = 0x3,
+ HFI_PANEL_DISPLAY_TYPE_BUILT_IN_3 = 0x4,
+ HFI_PANEL_DISPLAY_TYPE_BUILT_IN_4 = 0x5,
+};
+
+enum hfi_panel_bpp {
+ HFI_PANEL_BPP_3 = 1,
+ HFI_PANEL_BPP_8 = 2,
+ HFI_PANEL_BPP_12 = 3,
+ HFI_PANEL_BPP_16 = 4,
+ HFI_PANEL_BPP_18 = 5,
+ HFI_PANEL_BPP_24 = 6,
+ HFI_PANEL_BPP_30 = 7,
+};
+
+enum hfi_panel_deinit_prop {
+ HFI_PANEL_DEINIT_CONT_SPLASH = 0x00000001,
+};
+
+enum hfi_panel_lane_enable {
+ HFI_PANEL_LANE_0 = 0x00000001,
+ HFI_PANEL_LANE_1 = 0x00000002,
+ HFI_PANEL_LANE_2 = 0x00000004,
+ HFI_PANEL_LANE_3 = 0x00000008,
+};
+
+enum hfi_panel_lane_map {
+ HFI_PANEL_LANE_MAP_0123 = 1,
+};
+
+enum hfi_panel_color_order_type {
+ HFI_PANEL_COLOR_ORDER_RGB_SWAP_RGB = 0x1,
+ HFI_PANEL_COLOR_ORDER_RGB_SWAP_RBG = 0x2,
+ HFI_PANEL_COLOR_ORDER_RGB_SWAP_BRG = 0x3,
+ HFI_PANEL_COLOR_ORDER_RGB_SWAP_GRB = 0x4,
+ HFI_PANEL_COLOR_ORDER_RGB_SWAP_GBR = 0x5,
+};
+
+enum hfi_panel_vsync_source {
+ HFI_PANEL_VSYNC_SOURCE_GPIO_0 = 0x0,
+ HFI_PANEL_VSYNC_SOURCE_GPIO_1 = 0x1,
+ HFI_PANEL_VSYNC_SOURCE_GPIO_2 = 0x2,
+ HFI_PANEL_VSYNC_SOURCE_GPIO_3 = 0x3,
+ HFI_PANEL_VSYNC_SOURCE_GPIO_4 = 0x4,
+ HFI_PANEL_VSYNC_SOURCE_GPIO_5 = 0x5,
+ HFI_PANEL_VSYNC_SOURCE_WD = 0xf,
+};
+
+enum hfi_panel_trigger_type {
+ HFI_PANEL_TRIGGER_NONE = 1,
+ HFI_PANEL_TRIGGER_TE = 2,
+ HFI_PANEL_TRIGGER_SW = 3,
+ HFI_PANEL_TRIGGER_SW_TE = 4,
+ HFI_PANEL_TRIGGER_SW_SEOF = 5,
+ HFI_PANEL_TRIGGER_SEOF = 6,
+};
+
+enum hfi_panel_fps_traffic_mode {
+ HFI_PANEL_TRAFFIC_NON_BURST_SYNC_PULSE_MODE = 1,
+ HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE = 2,
+ HFI_PANEL_TRAFFIC_BURST_MODE = 3,
+};
+
+enum hfi_panel_modes {
+ HFI_PANEL_VIDEO_MODE_8_BIT = 0x1,
+ HFI_PANEL_VIDEO_MODE_10_BIT = 0x2,
+ HFI_PANEL_CMD_MODE_8_BIT = 0x4,
+ HFI_PANEL_CMD_MODE_10_BIT = 0x8,
+ HFI_PANEL_VIDEO_MODE_PSR_8_BIT = 0x10,
+ HFI_PANEL_VIDEO_MODE_PSR_10_BIT = 0x20,
+ HFI_PANEL_VIDEO_LTPO_8_BIT = 0x40,
+ HFI_PANEL_VIDEO_LTPO_10_BIT = 0x80
+};
+
+enum hfi_panel_backlight_ctrl {
+ HFI_PANEL_BACKLIGHT_CTRL_UNKNOWN = 0x0,
+ HFI_PANEL_BACKLIGHT_CTRL_PWM = 0x1,
+ HFI_PANEL_BACKLIGHT_CTRL_WLED = 0x2,
+ HFI_PANEL_BACKLIGHT_CTRL_DCS = 0x3,
+ HFI_PANEL_BACKLIGHT_CTRL_EXTERNAL = 0x4,
+};
+
+enum hfi_panel_compression_mode {
+ HFI_PANEL_COMPRESSION_NONE = 0,
+ HFI_PANEL_COMPRESSION_DSC = 1,
+};
+
+enum hfi_panel_chroma_format_type {
+ HFI_PANEL_CHROMA_FORMAT_444 = 1,
+ HFI_PANEL_CHROMA_FORMAT_422 = 2,
+ HFI_PANEL_CHROMA_FORMAT_420 = 3,
+};
+
+enum hfi_panel_color_space_type {
+ HFI_PANEL_COLOR_SPACE_RGB = 1,
+ HFI_PANEL_COLOR_SPACE_YUV = 2,
+};
+
+enum hfi_panel_dfps_type {
+ HFI_PANEL_DFPS_NONE = 0,
+ HFI_PANEL_DFPS_SUSPEND_RESUME,
+ HFI_PANEL_DFPS_IMMEDIATE_CLK,
+ HFI_PANEL_DFPS_IMMEDIATE_HFP,
+ HFI_PANEL_DFPS_IMMEDIATE_VFP,
+ HFI_PANEL_DFPS_MAX
+};
+
+/* Panel topology. */
+struct hfi_panel_topology {
+ u32 mixer_count;
+ u32 enc_count;
+ u32 display_count;
+};
+
+/* DSI command sequencing. */
+enum hfi_panel_dcs_cmd_state {
+ HFI_PANEL_DSI_HS_MODE = 0x1,
+ HFI_PANEL_DSI_LP_MODE = 0x2,
+};
+
+enum hfi_panel_dcs_command_type {
+ HFI_DCS_CMD_PRE_ON = 0x00000000,
+ HFI_DCS_CMD_ON = 0x00000001,
+ HFI_DCS_CMD_VIDEO_ON = 0x00000002,
+ HFI_DCS_CMD_COMMAND_ON = 0x00000003,
+ HFI_DCS_CMD_POST_PANEL_ON = 0x00000004,
+ HFI_DCS_CMD_PRE_OFF = 0x00000005,
+ HFI_DCS_CMD_OFF = 0x00000006,
+ HFI_DCS_CMD_POST_OFF = 0x00000007,
+ HFI_DCS_CMD_PRE_RES_SWITCH = 0x00000008,
+ HFI_DCS_CMD_RES_SWITCH = 0x0000009,
+ HFI_DCS_CMD_POST_RES_SWITCH = 0x00000010,
+ HFI_DCS_CMD_VIDEO_MODE_SWITCH_INS = 0x00000011,
+ HFI_DCS_CMD_VIDEO_MODE_SWITCH_OUTS = 0x00000012,
+ HFI_DCS_CMD_COMMAND_MODE_SWITCH_INS = 0x00000013,
+ HFI_DCS_CMD_COMMAND_MODE_SWITCH_OUTS = 0x00000014,
+ HFI_DCS_CMD_PANEL_STATUS = 0x00000015,
+ HFI_DCS_CMD_LP1 = 0x00000016,
+ HFI_DCS_CMD_LP2 = 0x00000017,
+ HFI_DCS_CMD_NOLP = 0x00000018,
+ HFI_DCS_CMD_PPS = 0x00000019,
+ HFI_DCS_CMD_ROI = 0x00000020,
+ HFI_DCS_CMD_TIMING_SWITCH = 0x00000021,
+ HFI_DCS_CMD_POST_MODE_SWITCH_ON = 0x00000022,
+ HFI_DCS_CMD_QSYNC_ONS = 0x00000023,
+ HFI_DCS_CMD_QSYNC_OFFS = 0x00000024,
+ HFI_DCS_CMD_MAX = 0x00000025
+};
+
+/* Panel ESD status modes. */
+enum hfi_panel_esd_status_mode {
+ HFI_PANEL_ESD_STATUS_MODE_REG_READ = 0x1,
+ HFI_PANEL_ESD_STATUS_MODE_SW_BTA = 0x2,
+ HFI_PANEL_ESD_STATUS_MODE_PANEL_TE = 0x3,
+ HFI_PANEL_ESD_STATUS_MODE_PANEL_RW = 0x4,
+ HFI_PANEL_ESD_STATUS_MODE_SW_SIM_SUCCESS = 0x5,
+ HFI_PANEL_ESD_STATUS_MODE_SW_SIM_FAILURE = 0x6
+};
+
+/* Panel timing parameters. */
+struct hfi_panel_res_data {
+ u32 active_width;
+ u32 active_height;
+ u32 h_front_porch;
+ u32 h_back_porch;
+ u32 h_left_border;
+ u32 h_right_border;
+ u32 h_pulse_width;
+ u32 h_sync_skew;
+ u32 h_sync_pulse;
+ u32 v_front_porch;
+ u32 v_back_porch;
+ u32 v_top_border;
+ u32 v_bottom_border;
+ u32 v_pulse_width;
+};
+
+/* Panel compression parameters. */
+struct hfi_panel_compression_params {
+ enum hfi_panel_compression_mode mode;
+ u32 version;
+ u32 scr_version;
+ u32 slice_height;
+ u32 slice_width;
+ u32 slices_per_pkt;
+ u32 bits_per_component;
+ u32 pps_delay_ms;
+ u32 bits_per_pixel;
+ enum hfi_panel_chroma_format_type chroma_format;
+ enum hfi_panel_color_space_type color_space;
+ u32 block_prediction_enable;
+};
+
+/* DCS command buffers. */
+struct hfi_panel_dcs_cmd_buffer_info {
+ u32 offset_l;
+ u32 offset_h;
+ u32 size;
+ u32 delay;
+ u32 ctrl_flags;
+ enum hfi_panel_dcs_cmd_state mode;
+ u32 reserve1;
+ u32 reserve2;
+};
+
+struct hfi_panel_compression_rc_override {
+ u8 min_qp[15];
+ u8 padding1;
+ u8 max_qp[15];
+ u8 padding2;
+ u8 offsets[15];
+ u8 padding3;
+};
+
+/* Panel ESD configuration. */
+/* Register-read fields are valid only in REG_READ mode. */
+struct hfi_panel_esd_config {
+ u32 size;
+ enum hfi_panel_esd_status_mode status_mode;
+
+ u32 count;
+ u32 groups;
+
+ u32 hfi_dsi_cmd_desc_lsb;
+ u32 hfi_dsi_cmd_desc_msb;
+
+ u32 valid_params_lsb;
+ u32 valid_params_msb;
+
+ u32 status_values_lsb;
+ u32 status_values_msb;
+};
+
+struct hfi_qsync_params {
+ u32 avr_step_fps;
+ u32 qsync_min_fps;
+};
+
+struct hfi_panel_esync_caps {
+ u32 esync_support;
+ u32 esync_milli_skew;
+ u32 hsync_milli_pulse_width;
+ u32 emsync_milli_pulse_width;
+ u32 emsync_fps;
+};
+
+struct hfi_panel_dfps_caps {
+ u32 dfps_support;
+ u32 min_refresh_rate;
+ u32 max_refresh_rate;
+ enum hfi_panel_dfps_type type;
+};
+
+struct hfi_display_vrr_frame_params {
+ u32 frame_interval_ns_lo;
+ u32 frame_interval_ns_hi;
+ u32 usecase_idx;
+};
+
+/* Panel operating-mode capabilities. */
+struct hfi_panel_operating_mode_caps {
+ u32 panel_mode_switch_enabled;
+ u32 vsync_aligned_switch;
+};
+
+#endif /* __H_HFI_DEFS_PANEL_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h
new file mode 100644
index 000000000000..53c7789239a6
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_properties_panel.h
@@ -0,0 +1,198 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __H_HFI_PROPERTIES_PANEL_H__
+#define __H_HFI_PROPERTIES_PANEL_H__
+
+/* Panel-property range. */
+#define HFI_PROPERTY_PANEL_BEGIN 0x00040000
+
+/* Sets the total number of timing modes available for the panel. */
+#define HFI_PROPERTY_PANEL_TIMING_MODE_COUNT 0x00040001
+
+/* Sets the index of the timing mode. */
+
+#define HFI_PROPERTY_PANEL_INDEX 0x00040002
+/* Sets the panel clock speed in Hz. */
+#define HFI_PROPERTY_PANEL_CLOCKRATE 0x00040003
+
+/* Sets the frame rate for the panel. */
+#define HFI_PROPERTY_PANEL_FRAMERATE 0x00040004
+
+/* Sets active dimensions, porches, pulse width, and sync skew. */
+#define HFI_PROPERTY_PANEL_RESOLUTION_DATA 0x00040005
+
+/* Sets jitter as a numerator/denominator pair for early wakeup. */
+#define HFI_PROPERTY_PANEL_JITTER 0x00040006
+
+/* Sets all the compression data for VDC / DSC. */
+#define HFI_PROPERTY_PANEL_COMPRESSION_DATA 0x00040007
+
+/* Lists available display topologies. */
+#define HFI_PROPERTY_PANEL_DISPLAY_TOPOLOGY 0x00040008
+
+/* Sets the default topology index. */
+#define HFI_PROPERTY_PANEL_DEFAULT_TOPOLOGY_INDEX 0x00040009
+
+/* Panel name available for current timing mode. */
+#define HFI_PROPERTY_PANEL_NAME 0x0004000a
+
+/* Specifies the type of the panel, OLED / LCD. */
+#define HFI_PROPERTY_PANEL_PHYSICAL_TYPE 0x0004000b
+
+/* Sets the panel bits per pixel. */
+#define HFI_PROPERTY_PANEL_BPP 0x0004000c
+
+/* Enables or disables each DSI data lane. */
+#define HFI_PROPERTY_PANEL_LANES_STATE 0x0004000d
+
+/* Specifies the panel lane map. */
+#define HFI_PROPERTY_PANEL_LANE_MAP 0x0004000e
+
+/* Specifies the R, G and B channel ordering. */
+#define HFI_PROPERTY_PANEL_COLOR_ORDER 0x0004000f
+
+/* Specifies the trigger mechanism to be used for DMA path. */
+#define HFI_PROPERTY_PANEL_DMA_TRIGGER 0x00040010
+
+/* Property used to enable appending end of transmission packets. */
+#define HFI_PROPERTY_PANEL_TX_EOT_APPEND 0x00040011
+
+/* Selects the DSI lane state at BLLP end-of-frame. */
+#define HFI_PROPERTY_PANEL_BLLP_EOF_POWER_MODE 0x00040012
+
+/* Selects the DSI lane state during BLLP. */
+#define HFI_PROPERTY_PANEL_BLLP_POWER_MODE 0x00040013
+
+/* Specifies the panel traffic mode. */
+#define HFI_PROPERTY_PANEL_TRAFFIC_MODE 0x00040014
+
+/* Specifies the virtual channel identifier. */
+#define HFI_PROPERTY_PANEL_VIRTUAL_CHANNEL_ID 0x00040015
+
+/* DCS command for write_memory_start. */
+#define HFI_PROPERTY_PANEL_WR_MEM_START 0x00040016
+
+/* DCS command for write_memory_continue. */
+#define HFI_PROPERTY_PANEL_WR_MEM_CONTINUE 0x00040017
+
+/* Inserts the DCS command. */
+#define HFI_PROPERTY_PANEL_TE_DCS_COMMAND 0x00040018
+
+/* Specifies the panel operating mode. */
+#define HFI_PROPERTY_PANEL_OPERATING_MODE 0x00040019
+
+/* Specifies the min backlight level supported by the panel. */
+#define HFI_PROPERTY_PANEL_BL_MIN_LEVEL 0x0004001a
+
+/* Specifies the max backlight level supported by the panel. */
+#define HFI_PROPERTY_PANEL_BL_MAX_LEVEL 0x0004001b
+
+/* Specifies the max brightness level supported. */
+#define HFI_PROPERTY_PANEL_BRIGHTNESS_MAX_LEVEL 0x0004001c
+
+/* Specifies the DSI controllers to use for primary panel. */
+#define HFI_PROPERTY_PANEL_CTRL_NUM 0x0004001d
+
+/* Specifies the DSI PHYs to use for primary panel. */
+#define HFI_PROPERTY_PANEL_PHY_NUM 0x0004001e
+
+/* An array that lists the sequence of reset GPIO values and sleeps. */
+#define HFI_PROPERTY_PANEL_RESET_SEQUENCE 0x0004001f
+
+/* Specifies the implementation of backlight control for this panel. */
+#define HFI_PROPERTY_PANEL_BL_PMIC_CONTROL_TYPE 0x00040020
+
+/* Specifies the implementation of backlight control for secondary panel. */
+#define HFI_PROPERTY_PANEL_SEC_BL_PMIC_CONTROL_TYPE 0x00040021
+
+/* Specifies whether to invert the display brightness value. */
+#define HFI_PROPERTY_PANEL_BL_INVERTED_DBV 0x00040022
+
+/* Sets DCS command buffer data for different types of DCS commands. */
+#define HFI_PROPERTY_PANEL_DCS_CMD_INFO 0x00040024
+
+/* Provides iova of DPU Mapped buffer of HW understandable panel commands. */
+#define HFI_PROPERTY_PANEL_DPU_ADDRESS 0x00040025
+
+/* Provides dva of DCP Mapped buffer of structures of cmd descriptions. */
+#define HFI_PROPERTY_PANEL_DCP_ADDRESS 0x00040026
+
+/* Provides D-PHY timing parameters. */
+#define HFI_PROPERTY_PANEL_DPHY_TIMINGS 0x00040027
+
+/* Entry to select vsync source from GPIO's/Watchdog timer. */
+#define HFI_PROPERTY_PANEL_VSYNC_SOURCE 0x00040028
+
+/* Selects the pixel-stream trigger for command mode. */
+#define HFI_PROPERTY_PANEL_STREAM_TRIGGER 0x00040029
+
+/* Specifies the TE Path for command mode panels. */
+#define HFI_PROPERTY_PANEL_TE_MODE 0x0004002a
+
+/* Reports whether the panel uses C-PHY. */
+#define HFI_PROPERTY_PANEL_CPHY_MODE 0x0004002b
+
+/* Provides the shared DCP address for DCS command DMA. */
+#define HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_DVA 0x0004002c
+
+/* Provides the shared DPU address for DCS command DMA. */
+#define HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_IOVA 0x0004002d
+
+/* Sets the compression rate control override value for the panel. */
+#define HFI_PROPERTY_PANEL_COMPRESSION_RC_OVERRIDE 0x0004002e
+
+/* Selects the line that triggers DSI command DMA. */
+#define HFI_PROPERTY_PANEL_DMA_SCHEDULE_LINE 0x0004002f
+
+/* Provides the panel ESD config data required for ESD status check to the DCP. */
+#define HFI_PROPERTY_PANEL_ESD_CONFIG 0x00040030
+
+/* Specifies the QSYNC parameters of the panel. */
+#define HFI_PROPERTY_PANEL_QSYNC_PARAMS 0x00040031
+
+/* Provides esync properties for VHM panel. */
+#define HFI_PROPERTY_PANEL_ESYNC_CAPS 0x00040032
+
+/* Specifies the DFPS capabilities supported by the panel. */
+#define HFI_PROPERTY_PANEL_DFPS_CAPS 0x00040033
+
+/* Specifies the frequency stepping pattern of the panel. */
+#define HFI_PROPERTY_PANEL_FREQ_PATTERN 0x00040034
+
+/* Specifies whether panel requires LP11_INIT mode. */
+#define HFI_PROPERTY_PANEL_LP11_INIT 0x00040035
+
+/* Specifies the width of the DMA scheduling window, expressed in number of display lines. */
+#define HFI_PROPERTY_PANEL_DMA_SCHEDULE_WINDOW 0x00040036
+
+/* Provides DSI bit clock frequencies for dynamic refresh. */
+#define HFI_PROPERTY_PANEL_DYN_REF_CLK_FREQS 0x00040038
+
+/* Reports supported operating-mode switches. */
+#define HFI_PROPERTY_PANEL_OPERATING_SWITCH_CAPABILITY 0x00040039
+
+/* Selects the display type and priority. */
+#define HFI_PROPERTY_PANEL_DISPLAY_TYPE 0x0004003a
+
+/* Specifies whether ULPS (Ultra Low Power State) is supported by the panel. */
+#define HFI_PROPERTY_PANEL_ULPS_SUPPORTED 0x0004003b
+
+/* Specifies the DSI custom command set start index and count. */
+#define HFI_PROPERTY_PANEL_DSI_CUSTOM_DCS_CMDS_SET_INFO 0x00040040
+
+/* Provides per-mode esync timing capabilities for VHM panel. */
+#define HFI_PROPERTY_PANEL_ESYNC_TIMING_CAPS 0x00040041
+
+/* Provides per-mode qsync timing parameters for panels supporting QSYNC/AVR. */
+#define HFI_PROPERTY_PANEL_QSYNC_TIMING_PARAMS 0x00040042
+
+/* Specifies the current display format being used by the panel. */
+#define HFI_PROPERTY_PANEL_COLOR_FORMAT 0x00040043
+
+/* End of panel-property range. */
+#define HFI_PROPERTY_PANEL_END 0x0004ffff
+
+#endif /* __H_HFI_PROPERTIES_PANEL_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (4 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 05/23] drm/msm/dcp: Add HFI panel definitions Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:07 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
` (17 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Implement the HFI transport between Linux and DCP firmware over
VirtIO. The display backend needs a transport that owns DMA buffers,
submits HFI packets, receives responses, and completes commands
without exposing firmware queues to higher layers.
Allocate and manage command and response buffers, handle VirtIO queue
callbacks, provide synchronous and asynchronous submission paths,
and synchronize transport teardown with in-flight activity.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_drv.h | 28 ++
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 169 +++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 836 +++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h | 48 ++
5 files changed, 1082 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index ad7b9dbf736e..56c48fe2e2c5 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -72,6 +72,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
disp/mdp5/mdp5_smp.o \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
+ dcp/dcp_hfi_core.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h
new file mode 100644
index 000000000000..44d9b2cd5171
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DCP_DRV_H__
+#define __DCP_DRV_H__
+
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+
+#include "dcp_hfi.h"
+
+struct dcp_dpu_kms;
+
+struct dcp_drv {
+ struct virtio_device *vdev;
+ struct platform_device *dcp_subsystem_dev;
+ struct device_dma_parameters dcp_subsystem_dma_parms;
+ struct device_node *dcp_subsystem_node;
+ struct dcp_hfi *hfi;
+ struct dcp_dpu_kms *kms;
+ struct dcp_caps caps;
+ struct notifier_block panic_notifier;
+};
+
+#endif /* __DCP_DRV_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
new file mode 100644
index 000000000000..23e1cd39b89d
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -0,0 +1,169 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP HFI (Hardware Firmware Interface) protocol layer.
+ */
+
+#ifndef __DCP_HFI_H__
+#define __DCP_HFI_H__
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/virtio.h>
+#include <linux/workqueue.h>
+#include <linux/xarray.h>
+
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_pack_unpack_common.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_defs_common.h"
+
+struct dcp_drv;
+
+/* Queue 0 carries commands; queue 1 carries responses and events. */
+#define DCP_VQ_CMD 0 /* TX: all commands */
+#define DCP_VQ_RESP 1 /* RX: all responses and async events */
+#define DCP_NUM_VQS 2
+
+#define DCP_MAX_DISPLAYS 4
+#define DCP_HFI_CAPS_BUF_SIZE 4096
+
+struct dcp_hfi_caps_cache {
+ struct completion device_done;
+ struct completion display_done;
+
+ u8 device_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 device_len;
+ u8 display_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 display_len;
+ u8 vig_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 vig_len;
+ u8 dma_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 dma_len;
+ u8 vig_r1_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 vig_r1_len;
+ u8 dma_r1_buf[DCP_HFI_CAPS_BUF_SIZE];
+ u32 dma_r1_len;
+ u32 pipe_caps_received;
+};
+
+#define DCP_MAX_SSPP_COUNT 16
+#define DCP_MAX_VIG_PIPES DCP_MAX_SSPP_COUNT
+#define DCP_MAX_DMA_PIPES DCP_MAX_SSPP_COUNT
+
+#define DCP_MAX_BLOCKS 8
+
+/* Pipe indices encode the rectangle ID in bits 31:24. */
+#define DCP_REC_ID(x) (((x) & 0xff000000) >> 24)
+
+/* Capabilities parsed from DEVICE_INIT responses. */
+struct dcp_caps {
+ u32 dcp_hw_rev;
+ u32 hw_rev;
+ u32 fw_rev;
+
+ u32 num_vig_pipes;
+ u32 vig_pipe_ids[DCP_MAX_SSPP_COUNT];
+ u32 virt_vig_count;
+ u32 vig_r1_indices[DCP_MAX_SSPP_COUNT];
+
+ u32 num_dma_pipes;
+ u32 dma_pipe_ids[DCP_MAX_SSPP_COUNT];
+ u32 virt_dma_count;
+ u32 dma_r1_indices[DCP_MAX_SSPP_COUNT];
+
+ /* Number of displays reported by firmware. */
+ u32 num_displays;
+
+ u32 dsi_count;
+ u32 dsi_indices[DCP_MAX_BLOCKS];
+
+ /* Maximum dimensions reported for DSI display modes. */
+ u32 max_width;
+ u32 max_height;
+
+ u32 max_planes;
+};
+
+struct dcp_hfi_buf {
+ void *data;
+ dma_addr_t dma_addr;
+ size_t size;
+ struct scatterlist sg;
+ struct dcp_hfi_completion *comp;
+ struct list_head node;
+};
+
+struct dcp_hfi_completion {
+ struct completion done;
+ int status;
+ void *resp_data;
+ size_t resp_len;
+ u32 packet_id;
+};
+
+struct dcp_hfi {
+ struct device *dev;
+ struct dcp_drv *drv;
+
+ struct device *dma_dev;
+
+ struct virtqueue *cmd_vq;
+ struct virtqueue *resp_vq;
+
+ struct dcp_hfi_buf *cmd_pool;
+ struct list_head cmd_free_list;
+ /* Protects cmd_free_list and command buffer ownership. */
+ spinlock_t cmd_lock;
+
+ struct dcp_hfi_buf *resp_pool;
+ /* Protects response queue draining and power callback publication. */
+ spinlock_t resp_drain_lock;
+ struct list_head resp_repost_list;
+ struct delayed_work resp_repost_work;
+ struct xarray pending_xa;
+ /* Protects pending_xa and completion publication. */
+ spinlock_t pending_lock;
+ /* Allocates non-zero packet IDs for HFI transactions. */
+ atomic_t packet_id;
+ /* Blocks new work once transport teardown begins. */
+ atomic_t shutdown_in_progress;
+};
+
+int dcp_hfi_init(struct dcp_drv *drv, struct virtio_device *vdev,
+ struct virtqueue *cmd_vq, struct virtqueue *resp_vq,
+ struct device *dma_dev);
+void dcp_hfi_stop(struct dcp_hfi *hfi);
+void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi);
+void dcp_hfi_deinit(struct dcp_hfi *hfi);
+
+void dcp_hfi_resp_callback(struct virtqueue *vq);
+void dcp_hfi_cmd_callback(struct virtqueue *vq);
+
+int dcp_hfi_send_sync(struct dcp_hfi *hfi, u32 cmd,
+ enum hfi_cmd_buff_type cmd_type, u32 object_id,
+ enum hfi_packet_payload_type payload_type, void *payload,
+ u32 payload_size, u32 tx_flags, void *resp_data,
+ size_t resp_len, unsigned int timeout_ms);
+
+int dcp_hfi_send_no_wait(struct dcp_hfi *hfi, u32 cmd,
+ enum hfi_cmd_buff_type cmd_type, u32 object_id,
+ enum hfi_packet_payload_type payload_type, void *payload,
+ u32 payload_size);
+
+int dcp_hfi_send_no_wait_flags(struct dcp_hfi *hfi, u32 cmd,
+ enum hfi_cmd_buff_type cmd_type, u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload, u32 payload_size, u32 tx_flags);
+int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
+ enum hfi_cmd_buff_type cmd_type, u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload, u32 payload_size, u32 tx_flags);
+#endif /* __DCP_HFI_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
new file mode 100644
index 000000000000..9f7ac798e13c
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -0,0 +1,836 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP HFI core transport.
+ */
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/virtio.h>
+#include <linux/workqueue.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "dcp_drv.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_defs_common.h"
+
+static int dcp_hfi_prepost_resp_bufs(struct dcp_hfi *hfi);
+
+u32 dcp_hfi_next_packet_id(struct dcp_hfi *hfi)
+{
+ u32 old, next;
+
+ do {
+ old = (u32)atomic_read(&hfi->packet_id);
+ next = old + 1;
+ if (!next)
+ next = 1;
+ } while ((u32)atomic_cmpxchg(&hfi->packet_id, old, next) != old);
+
+ return next;
+}
+
+#define DCP_HFI_BUF_ALLOC_RETRIES 3 /* Bound command-path stalls. */
+#define DCP_HFI_BUF_ALLOC_SLEEP_US_MIN 30000
+#define DCP_HFI_BUF_ALLOC_SLEEP_US_MAX 40000
+
+static struct dcp_hfi_buf *_dcp_hfi_buf_alloc(struct dcp_hfi *hfi,
+ struct list_head *free_list,
+ spinlock_t *lock,
+ const char *pool_name)
+{
+ struct dcp_hfi_buf *buf = NULL;
+ unsigned long flags;
+ int retry;
+
+ if (atomic_read(&hfi->shutdown_in_progress)) {
+ dev_dbg(hfi->dev,
+ "HFI shutdown in progress — refusing %s buffer allocation\n",
+ pool_name);
+ return NULL;
+ }
+
+ for (retry = 0; retry <= DCP_HFI_BUF_ALLOC_RETRIES; retry++) {
+ if (atomic_read(&hfi->shutdown_in_progress))
+ return NULL;
+
+ spin_lock_irqsave(lock, flags);
+ if (!list_empty(free_list)) {
+ buf = list_first_entry(free_list,
+ struct dcp_hfi_buf, node);
+ list_del(&buf->node);
+ }
+ spin_unlock_irqrestore(lock, flags);
+
+ if (buf)
+ return buf;
+
+ if (retry < DCP_HFI_BUF_ALLOC_RETRIES)
+ usleep_range(DCP_HFI_BUF_ALLOC_SLEEP_US_MIN,
+ DCP_HFI_BUF_ALLOC_SLEEP_US_MAX);
+ }
+
+ dev_warn_ratelimited(hfi->dev,
+ "HFI %s TX pool exhausted after %d retries — command dropped\n",
+ pool_name, DCP_HFI_BUF_ALLOC_RETRIES);
+ return NULL;
+}
+
+static void _dcp_hfi_buf_free_locked(struct dcp_hfi_buf *buf,
+ struct list_head *free_list)
+{
+ buf->comp = NULL;
+ list_add_tail(&buf->node, free_list);
+}
+
+struct dcp_hfi_buf *dcp_hfi_buf_alloc(struct dcp_hfi *hfi)
+{
+ return _dcp_hfi_buf_alloc(hfi, &hfi->cmd_free_list,
+ &hfi->cmd_lock, "cmd");
+}
+
+void dcp_hfi_buf_free(struct dcp_hfi *hfi, struct dcp_hfi_buf *buf)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ _dcp_hfi_buf_free_locked(buf, &hfi->cmd_free_list);
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+}
+
+struct dcp_hfi_buf *dcp_hfi_buf_alloc_atomic(struct dcp_hfi *hfi)
+{
+ struct dcp_hfi_buf *buf = NULL;
+ unsigned long flags;
+
+ if (atomic_read(&hfi->shutdown_in_progress))
+ return NULL;
+
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ if (!list_empty(&hfi->cmd_free_list)) {
+ buf = list_first_entry(&hfi->cmd_free_list,
+ struct dcp_hfi_buf, node);
+ list_del(&buf->node);
+ }
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+
+ return buf;
+}
+
+/* Multi-packet headers register each response completion before submission. */
+int dcp_hfi_pending_add(struct dcp_hfi *hfi,
+ struct dcp_hfi_completion *comp)
+{
+ unsigned long flags;
+ int ret;
+
+ spin_lock_irqsave(&hfi->pending_lock, flags);
+ ret = xa_insert(&hfi->pending_xa, comp->packet_id, comp, GFP_ATOMIC);
+ spin_unlock_irqrestore(&hfi->pending_lock, flags);
+
+ if (ret < 0) {
+ dev_err_ratelimited(hfi->dev,
+ "HFI pending XArray full or collision at pkt_id=%u: %d\n",
+ comp->packet_id, ret);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+struct dcp_hfi_completion *dcp_hfi_pending_find(struct dcp_hfi *hfi,
+ u32 packet_id)
+{
+ unsigned long flags;
+ struct dcp_hfi_completion *comp;
+
+ spin_lock_irqsave(&hfi->pending_lock, flags);
+ comp = xa_erase(&hfi->pending_xa, packet_id);
+ spin_unlock_irqrestore(&hfi->pending_lock, flags);
+ return comp;
+}
+
+static void dcp_hfi_complete_pending(struct dcp_hfi *hfi,
+ const struct hfi_packet_info *pkt_info,
+ int status)
+{
+ struct dcp_hfi_completion *comp;
+ size_t n;
+
+ comp = dcp_hfi_pending_find(hfi, pkt_info->packet_id);
+ if (!comp) {
+ if (!status)
+ dev_warn_ratelimited(hfi->dev,
+ "HFI resp: SUCCESS cmd=0x%x pkt_id=%u no completion; payload dropped (size=%u)\n",
+ pkt_info->cmd, pkt_info->packet_id,
+ pkt_info->payload_size);
+ return;
+ }
+
+ if (!status && comp->resp_data && comp->resp_len &&
+ pkt_info->payload_size) {
+ n = min_t(size_t, comp->resp_len, pkt_info->payload_size);
+ memcpy(comp->resp_data, pkt_info->payload_ptr, n);
+ }
+
+ comp->status = status;
+ complete(&comp->done);
+}
+
+static void dcp_hfi_cancel_pending(struct dcp_hfi *hfi, int status)
+{
+ unsigned long flags;
+ struct dcp_hfi_completion *comp;
+ unsigned long id;
+ void *entry;
+
+ spin_lock_irqsave(&hfi->pending_lock, flags);
+ xa_for_each(&hfi->pending_xa, id, entry) {
+ comp = entry;
+ comp->status = status;
+ complete(&comp->done);
+ }
+ xa_destroy(&hfi->pending_xa);
+ xa_init(&hfi->pending_xa);
+ spin_unlock_irqrestore(&hfi->pending_lock, flags);
+}
+
+void dcp_hfi_cmd_callback(struct virtqueue *vq)
+{
+ struct dcp_drv *drv = vq->vdev->priv;
+ struct dcp_hfi *hfi = drv ? drv->hfi : NULL;
+
+ struct dcp_hfi_buf *buf;
+ unsigned int len;
+ unsigned long flags;
+
+ if (!hfi)
+ return;
+
+ if (atomic_read(&hfi->shutdown_in_progress))
+ return;
+
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ virtqueue_disable_cb(vq);
+ do {
+ while ((buf = virtqueue_get_buf(vq, &len)) != NULL)
+ _dcp_hfi_buf_free_locked(buf, &hfi->cmd_free_list);
+ } while (!virtqueue_enable_cb(vq));
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+}
+
+void dcp_hfi_resp_callback(struct virtqueue *vq)
+{
+ struct dcp_drv *drv = vq->vdev->priv;
+ struct dcp_hfi *hfi = drv ? drv->hfi : NULL;
+ struct dcp_hfi_buf *buf;
+ unsigned int len;
+ unsigned long drain_flags;
+
+ if (!hfi || atomic_read(&hfi->shutdown_in_progress))
+ return;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
+ virtqueue_disable_cb(vq);
+ for (;;) {
+ while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ u32 pkt_num;
+ int rc;
+
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
+
+ if (unlikely(len < sizeof(struct hfi_header) ||
+ len > DCP_HFI_BUF_SIZE)) {
+ dev_warn_ratelimited(hfi->dev,
+ "HFI resp: short buffer len=%u (min=%zu) — skipping\n",
+ len, sizeof(struct hfi_header));
+ goto repost;
+ }
+
+ hdl.cmd_buffer = buf->data;
+ hdl.size = (u32)len;
+ rc = hfi_unpacker_get_header_info(&hdl, &hdr_info);
+ if (rc) {
+ dev_warn_ratelimited(hfi->dev,
+ "HFI resp: invalid header (rc=%d) len=%u\n",
+ rc, len);
+ print_hex_dump(KERN_WARNING, "HFI resp raw: ",
+ DUMP_PREFIX_OFFSET, 16, 4,
+ buf->data, min_t(u32, len, 64), false);
+ goto repost;
+ }
+
+ for (pkt_num = 1; pkt_num <= hdr_info.num_packets; pkt_num++) {
+ memset(&pkt_info, 0, sizeof(pkt_info));
+ rc = hfi_unpacker_get_packet_info(&hdl, pkt_num,
+ &pkt_info);
+ if (rc) {
+ dev_warn_ratelimited(hfi->dev,
+ "HFI resp: bad pkt %u (rc=%d)\n",
+ pkt_num, rc);
+ break;
+ }
+
+ dev_dbg(hfi->dev,
+ "HFI resp: cmd=0x%x flags=0x%x pkt_id=%u id=%u payload_size=%u\n",
+ pkt_info.cmd, pkt_info.flags, pkt_info.packet_id,
+ pkt_info.id, pkt_info.payload_size);
+
+ if (pkt_info.flags & HFI_RX_FLAGS_SYSTEM_ERROR) {
+ dev_err(hfi->dev,
+ "HFI system error: cmd=0x%x pkt_id=%u\n",
+ pkt_info.cmd, pkt_info.packet_id);
+ dcp_hfi_complete_pending(hfi, &pkt_info, -EIO);
+ } else if (pkt_info.flags & HFI_RX_FLAGS_DEVICE_ERROR) {
+ dev_err(hfi->dev,
+ "HFI error: cmd=0x%x pkt_id=%u\n",
+ pkt_info.cmd, pkt_info.packet_id);
+ dcp_hfi_complete_pending(hfi, &pkt_info, -EIO);
+ } else if (pkt_info.flags & HFI_RX_FLAGS_SUCCESS) {
+ if (pkt_info.flags & HFI_RX_FLAGS_INFORMATION)
+ dev_dbg(hfi->dev,
+ "HFI info with success: cmd=0x%x pkt_id=%u\n",
+ pkt_info.cmd, pkt_info.packet_id);
+ dcp_hfi_complete_pending(hfi, &pkt_info, 0);
+ } else if (pkt_info.flags & HFI_RX_FLAGS_INFORMATION) {
+ dev_dbg(hfi->dev,
+ "HFI info with cmd=0x%x pkt_id=%u\n",
+ pkt_info.cmd, pkt_info.packet_id);
+ dcp_hfi_complete_pending(hfi, &pkt_info, 0);
+ } else {
+ dev_dbg_ratelimited(hfi->dev,
+ "HFI resp: unhandled cmd=0x%x flags=0x%x pkt_id=%u\n",
+ pkt_info.cmd, pkt_info.flags,
+ pkt_info.packet_id);
+ }
+ }
+
+repost:
+ spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
+ dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
+ if (virtqueue_add_inbuf_premapped(vq, &buf->sg, 1, buf,
+ NULL, GFP_ATOMIC)) {
+ list_add_tail(&buf->node, &hfi->resp_repost_list);
+ schedule_delayed_work(&hfi->resp_repost_work,
+ msecs_to_jiffies(20));
+ }
+ }
+
+ if (virtqueue_kick_prepare(vq))
+ virtqueue_notify(vq);
+ if (virtqueue_enable_cb(vq))
+ break;
+ }
+
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
+}
+
+int dcp_hfi_send_cmd_async(struct dcp_hfi *hfi,
+ struct dcp_hfi_buf *buf,
+ size_t cmd_len,
+ struct dcp_hfi_completion *comp)
+{
+ unsigned long flags;
+ int ret;
+
+ buf->comp = comp;
+ dcp_hfi_buf_init_sg(buf, cmd_len);
+
+ /* Serialize shutdown with both queue submission and notification. */
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ if (atomic_read(&hfi->shutdown_in_progress)) {
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ dcp_hfi_buf_free(hfi, buf);
+ return -ENODEV;
+ }
+
+ if (comp) {
+ ret = dcp_hfi_pending_add(hfi, comp);
+ if (ret) {
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+ }
+ }
+
+ ret = virtqueue_add_outbuf_premapped(hfi->cmd_vq, &buf->sg, 1, buf,
+ GFP_ATOMIC);
+ if (ret) {
+ if (comp)
+ dcp_hfi_pending_find(hfi, comp->packet_id);
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ dcp_hfi_buf_free(hfi, buf);
+ dev_err(hfi->dev, "virtqueue_add_outbuf failed: %d\n", ret);
+ return ret;
+ }
+
+ virtqueue_kick(hfi->cmd_vq);
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ return 0;
+}
+
+static const char *dcp_hfi_cmd_buff_type_name(enum hfi_cmd_buff_type cmd_type)
+{
+ switch (cmd_type) {
+ case HFI_CMD_BUFF_SYSTEM: return "SYSTEM";
+ case HFI_CMD_BUFF_DEVICE: return "DEVICE";
+ case HFI_CMD_BUFF_DISPLAY: return "DISPLAY";
+ case HFI_CMD_BUFF_DEBUG: return "DEBUG";
+ case HFI_CMD_BUFF_VIRTUALIZATION: return "VIRT";
+ default: return "UNKNOWN";
+ }
+}
+
+static int dcp_hfi_send_sync_on_vq(struct dcp_hfi *hfi,
+ struct virtqueue *vq __maybe_unused,
+ u32 cmd,
+ enum hfi_cmd_buff_type cmd_type,
+ u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload,
+ u32 payload_size,
+ u32 tx_flags,
+ void *resp_data,
+ size_t resp_len,
+ unsigned int timeout_ms)
+{
+ struct dcp_hfi_buf *buf;
+ struct dcp_hfi_completion comp;
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ u32 packet_id;
+ unsigned long flags;
+ int ret;
+
+ buf = dcp_hfi_buf_alloc(hfi);
+ if (!buf) {
+ dev_err(hfi->dev, "HFI TX buffer pool exhausted (cmd=0x%x)\n",
+ cmd);
+ return -ENOMEM;
+ }
+
+ hdl.cmd_buffer = buf->data;
+ hdl.size = DCP_HFI_BUF_SIZE;
+
+ hdr_info.num_packets = 0;
+ hdr_info.cmd_buff_type = cmd_type;
+ hdr_info.object_id = object_id;
+ hdr_info.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl, &hdr_info);
+ if (ret) {
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+ }
+
+ packet_id = dcp_hfi_next_packet_id(hfi);
+
+ pkt_info.cmd = cmd;
+ pkt_info.id = object_id & 0xFFFF;
+ pkt_info.flags = tx_flags;
+ pkt_info.packet_id = packet_id;
+ pkt_info.payload_type = payload_type;
+ pkt_info.payload_size = payload_size;
+ pkt_info.payload_ptr = payload;
+
+ if (payload_type == HFI_PAYLOAD_NONE || !payload || !payload_size)
+ ret = hfi_create_packet_header(&hdl, &pkt_info);
+ else
+ ret = hfi_create_full_packet(&hdl, &pkt_info);
+
+ if (ret) {
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+ }
+
+ init_completion(&comp.done);
+ comp.resp_data = resp_data;
+ comp.resp_len = resp_len;
+ comp.status = 0;
+ comp.packet_id = packet_id;
+
+ dev_dbg(hfi->dev,
+ "HFI send cmd=0x%x type=%s obj=%u pkt=%u payload=%u timeout=%ums\n",
+ cmd, dcp_hfi_cmd_buff_type_name(cmd_type), object_id,
+ packet_id, payload_size, timeout_ms);
+
+ ret = dcp_hfi_send_cmd_async(hfi, buf,
+ hfi_get_header_size(&hdl), &comp);
+ if (ret)
+ return ret;
+
+ if (!wait_for_completion_timeout(&comp.done, msecs_to_jiffies(timeout_ms))) {
+ dev_err(hfi->dev,
+ "HFI cmd=0x%x buf_type=%s object_id=%u timed out after %ums (pkt_id=%u)\n",
+ cmd, dcp_hfi_cmd_buff_type_name(cmd_type), object_id,
+ timeout_ms, packet_id);
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ dcp_hfi_pending_find(hfi, packet_id);
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+ return -ETIMEDOUT;
+ }
+
+ return comp.status;
+}
+
+int dcp_hfi_send_sync(struct dcp_hfi *hfi,
+ u32 cmd,
+ enum hfi_cmd_buff_type cmd_type,
+ u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload,
+ u32 payload_size,
+ u32 tx_flags,
+ void *resp_data,
+ size_t resp_len,
+ unsigned int timeout_ms)
+{
+ int ret;
+
+ ret = dcp_hfi_send_sync_on_vq(hfi, NULL,
+ cmd, cmd_type, object_id,
+ payload_type, payload, payload_size,
+ tx_flags, resp_data, resp_len,
+ timeout_ms);
+ if (ret)
+ dev_err(hfi->dev, "HFI command 0x%x failed: %d\n", cmd, ret);
+
+ return ret;
+}
+
+int dcp_hfi_send_no_wait_flags(struct dcp_hfi *hfi,
+ u32 cmd,
+ enum hfi_cmd_buff_type cmd_type,
+ u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload,
+ u32 payload_size,
+ u32 tx_flags)
+{
+ struct dcp_hfi_buf *buf;
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ int ret;
+
+ buf = dcp_hfi_buf_alloc(hfi);
+ if (!buf) {
+ dev_warn_ratelimited(hfi->dev,
+ "HFI TX pool exhausted (no-wait cmd=0x%x)\n", cmd);
+ return -ENOMEM;
+ }
+
+ hdl.cmd_buffer = buf->data;
+ hdl.size = DCP_HFI_BUF_SIZE;
+
+ hdr_info.num_packets = 0;
+ hdr_info.cmd_buff_type = cmd_type;
+ hdr_info.object_id = object_id;
+ hdr_info.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl, &hdr_info);
+ if (ret) {
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+ }
+
+ pkt_info.cmd = cmd;
+ pkt_info.id = object_id & 0xFFFF;
+ pkt_info.flags = tx_flags;
+ pkt_info.packet_id = dcp_hfi_next_packet_id(hfi);
+ pkt_info.payload_type = payload_type;
+ pkt_info.payload_size = payload_size;
+ pkt_info.payload_ptr = payload;
+
+ if (payload_type == HFI_PAYLOAD_NONE || !payload || !payload_size)
+ ret = hfi_create_packet_header(&hdl, &pkt_info);
+ else
+ ret = hfi_create_full_packet(&hdl, &pkt_info);
+
+ if (ret) {
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+ }
+
+ ret = dcp_hfi_send_cmd_async(hfi, buf,
+ hfi_get_header_size(&hdl), NULL);
+ if (ret)
+ dev_warn(hfi->dev,
+ "HFI send (no-wait) cmd=0x%x pkt_id=%u failed: %d\n",
+ cmd, pkt_info.packet_id, ret);
+
+ return ret;
+}
+
+int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi,
+ u32 cmd,
+ enum hfi_cmd_buff_type cmd_type,
+ u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload,
+ u32 payload_size,
+ u32 tx_flags)
+{
+ struct dcp_hfi_buf *buf;
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ int ret;
+
+ buf = dcp_hfi_buf_alloc_atomic(hfi);
+ if (!buf)
+ return -ENOMEM;
+
+ hdl.cmd_buffer = buf->data;
+ hdl.size = DCP_HFI_BUF_SIZE;
+ hdr_info.num_packets = 0;
+ hdr_info.cmd_buff_type = cmd_type;
+ hdr_info.object_id = object_id;
+ hdr_info.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl, &hdr_info);
+ if (ret)
+ goto free_buf;
+
+ pkt_info.cmd = cmd;
+ pkt_info.id = object_id & 0xFFFF;
+ pkt_info.flags = tx_flags;
+ pkt_info.packet_id = dcp_hfi_next_packet_id(hfi);
+ pkt_info.payload_type = payload_type;
+ pkt_info.payload_size = payload_size;
+ pkt_info.payload_ptr = payload;
+
+ if (payload_type == HFI_PAYLOAD_NONE || !payload || !payload_size)
+ ret = hfi_create_packet_header(&hdl, &pkt_info);
+ else
+ ret = hfi_create_full_packet(&hdl, &pkt_info);
+ if (ret)
+ goto free_buf;
+
+ return dcp_hfi_send_cmd_async(hfi, buf,
+ hfi_get_header_size(&hdl), NULL);
+
+free_buf:
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+}
+
+int dcp_hfi_send_no_wait(struct dcp_hfi *hfi,
+ u32 cmd,
+ enum hfi_cmd_buff_type cmd_type,
+ u32 object_id,
+ enum hfi_packet_payload_type payload_type,
+ void *payload,
+ u32 payload_size)
+{
+ return dcp_hfi_send_no_wait_flags(hfi, cmd, cmd_type, object_id,
+ payload_type, payload, payload_size, HFI_TX_FLAGS_NONE);
+}
+
+static void dcp_hfi_free_pool(struct dcp_hfi *hfi, struct dcp_hfi_buf *pool,
+ u32 count)
+{
+ u32 i;
+
+ if (!pool)
+ return;
+
+ for (i = 0; i < count; i++) {
+ if (!pool[i].data)
+ continue;
+
+ dma_free_coherent(hfi->dma_dev, DCP_HFI_BUF_SIZE,
+ pool[i].data, pool[i].dma_addr);
+ pool[i].data = NULL;
+ }
+}
+
+static int dcp_hfi_prepost_resp_bufs(struct dcp_hfi *hfi)
+{
+ int i, ret;
+
+ for (i = 0; i < DCP_HFI_RESP_POOL_SIZE; i++) {
+ struct dcp_hfi_buf *buf = &hfi->resp_pool[i];
+
+ dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
+
+ ret = virtqueue_add_inbuf_premapped(hfi->resp_vq, &buf->sg, 1,
+ buf, NULL, GFP_KERNEL);
+ if (ret) {
+ dev_warn(hfi->dev,
+ "resp_vq full at buf %d/%d: %d\n",
+ i, DCP_HFI_RESP_POOL_SIZE, ret);
+ break;
+ }
+ }
+ return ret;
+}
+
+static void dcp_hfi_resp_repost_work(struct work_struct *work)
+{
+ struct dcp_hfi *hfi = container_of(to_delayed_work(work), struct dcp_hfi,
+ resp_repost_work);
+ struct dcp_hfi_buf *buf, *tmp;
+ unsigned long flags;
+ bool retry = false;
+
+ if (atomic_read(&hfi->shutdown_in_progress))
+ return;
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ list_for_each_entry_safe(buf, tmp, &hfi->resp_repost_list, node) {
+ dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
+ if (virtqueue_add_inbuf_premapped(hfi->resp_vq, &buf->sg, 1,
+ buf, NULL, GFP_ATOMIC)) {
+ retry = true;
+ break;
+ }
+ list_del_init(&buf->node);
+ }
+ if (virtqueue_kick_prepare(hfi->resp_vq))
+ virtqueue_notify(hfi->resp_vq);
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+ if (retry && !atomic_read(&hfi->shutdown_in_progress))
+ schedule_delayed_work(&hfi->resp_repost_work, msecs_to_jiffies(20));
+}
+
+int dcp_hfi_init(struct dcp_drv *drv,
+ struct virtio_device *vdev,
+ struct virtqueue *cmd_vq,
+ struct virtqueue *resp_vq,
+ struct device *dma_dev)
+{
+ struct dcp_hfi *hfi;
+ int i, ret;
+
+ hfi = devm_kzalloc(&vdev->dev, sizeof(*hfi), GFP_KERNEL);
+ if (!hfi)
+ return -ENOMEM;
+
+ hfi->dev = &vdev->dev;
+ hfi->drv = drv;
+ hfi->cmd_vq = cmd_vq;
+ hfi->resp_vq = resp_vq;
+ hfi->dma_dev = dma_dev;
+
+ spin_lock_init(&hfi->cmd_lock);
+ spin_lock_init(&hfi->pending_lock);
+ spin_lock_init(&hfi->resp_drain_lock);
+
+ atomic_set(&hfi->packet_id, 0);
+ atomic_set(&hfi->shutdown_in_progress, 0);
+
+ INIT_LIST_HEAD(&hfi->cmd_free_list);
+
+ xa_init(&hfi->pending_xa);
+
+ INIT_DELAYED_WORK(&hfi->resp_repost_work, dcp_hfi_resp_repost_work);
+ INIT_LIST_HEAD(&hfi->resp_repost_list);
+
+ hfi->cmd_pool = devm_kcalloc(hfi->dev, DCP_HFI_CMD_POOL_SIZE,
+ sizeof(*hfi->cmd_pool), GFP_KERNEL);
+ if (!hfi->cmd_pool)
+ return -ENOMEM;
+
+ hfi->resp_pool = devm_kcalloc(hfi->dev, DCP_HFI_RESP_POOL_SIZE,
+ sizeof(*hfi->resp_pool), GFP_KERNEL);
+ if (!hfi->resp_pool)
+ return -ENOMEM;
+ for (i = 0; i < DCP_HFI_CMD_POOL_SIZE; i++) {
+ hfi->cmd_pool[i].data = dma_alloc_coherent(hfi->dma_dev,
+ DCP_HFI_BUF_SIZE,
+ &hfi->cmd_pool[i].dma_addr,
+ GFP_KERNEL);
+ if (!hfi->cmd_pool[i].data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ hfi->cmd_pool[i].size = DCP_HFI_BUF_SIZE;
+ list_add_tail(&hfi->cmd_pool[i].node, &hfi->cmd_free_list);
+ }
+
+ for (i = 0; i < DCP_HFI_RESP_POOL_SIZE; i++) {
+ hfi->resp_pool[i].data =
+ dma_alloc_coherent(hfi->dma_dev, DCP_HFI_BUF_SIZE,
+ &hfi->resp_pool[i].dma_addr, GFP_KERNEL);
+ if (!hfi->resp_pool[i].data) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ hfi->resp_pool[i].size = DCP_HFI_BUF_SIZE;
+ INIT_LIST_HEAD(&hfi->resp_pool[i].node);
+ }
+
+ ret = dcp_hfi_prepost_resp_bufs(hfi);
+ if (ret)
+ goto err;
+
+ drv->hfi = hfi;
+ dev_dbg(hfi->dev,
+ "HFI core initialized: cmd_vq=%p resp_vq=%p\n",
+ cmd_vq, resp_vq);
+ return 0;
+
+err:
+ atomic_set(&hfi->shutdown_in_progress, 1);
+ cancel_delayed_work_sync(&hfi->resp_repost_work);
+ while (virtqueue_detach_unused_buf(hfi->cmd_vq))
+ ;
+ while (virtqueue_detach_unused_buf(hfi->resp_vq))
+ ;
+ xa_destroy(&hfi->pending_xa);
+
+ dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
+ dcp_hfi_free_pool(hfi, hfi->resp_pool, DCP_HFI_RESP_POOL_SIZE);
+ return ret;
+}
+
+void dcp_hfi_stop(struct dcp_hfi *hfi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ atomic_set(&hfi->shutdown_in_progress, 1);
+ spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+
+ cancel_delayed_work_sync(&hfi->resp_repost_work);
+ dcp_hfi_cancel_pending(hfi, -ENODEV);
+}
+
+void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ if (hfi->cmd_vq)
+ virtqueue_disable_cb(hfi->cmd_vq);
+ if (hfi->resp_vq)
+ virtqueue_disable_cb(hfi->resp_vq);
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+
+ if (hfi->cmd_vq)
+ while (virtqueue_detach_unused_buf(hfi->cmd_vq))
+ ;
+ if (hfi->resp_vq)
+ while (virtqueue_detach_unused_buf(hfi->resp_vq))
+ ;
+}
+
+void dcp_hfi_deinit(struct dcp_hfi *hfi)
+{
+ xa_destroy(&hfi->pending_xa);
+
+ dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
+ dcp_hfi_free_pool(hfi, hfi->resp_pool, DCP_HFI_RESP_POOL_SIZE);
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h b/drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h
new file mode 100644
index 000000000000..cc8a618182fe
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Private HFI helpers; external users should include dcp_hfi.h.
+ */
+
+#ifndef __DCP_HFI_PRIV_H__
+#define __DCP_HFI_PRIV_H__
+
+#include "dcp_hfi.h"
+
+#define DCP_HFI_CMD_POOL_SIZE 64
+#define DCP_HFI_RESP_POOL_SIZE 64
+#define DCP_HFI_BUF_SIZE 4096
+
+#define DCP_HFI_TIMEOUT_INIT_MS 2000
+#define DCP_HFI_TIMEOUT_RESOURCE_REGISTER_MS 5000
+#define DCP_HFI_TIMEOUT_CAPS_MS 1000
+#define DCP_HFI_TIMEOUT_DISPLAY_MS 1000
+#define DCP_HFI_TIMEOUT_MODE_MS 3000
+#define DCP_HFI_TIMEOUT_COMMIT_MS 2000
+#define DCP_HFI_TIMEOUT_DISPLAY_DCS_MS 10000
+
+struct dcp_hfi_buf *dcp_hfi_buf_alloc(struct dcp_hfi *hfi);
+struct dcp_hfi_buf *dcp_hfi_buf_alloc_atomic(struct dcp_hfi *hfi);
+void dcp_hfi_buf_free(struct dcp_hfi *hfi, struct dcp_hfi_buf *buf);
+
+static inline void dcp_hfi_buf_init_sg(struct dcp_hfi_buf *buf, size_t len)
+{
+ sg_init_table(&buf->sg, 1);
+ sg_dma_address(&buf->sg) = buf->dma_addr;
+ sg_dma_len(&buf->sg) = len;
+}
+
+u32 dcp_hfi_next_packet_id(struct dcp_hfi *hfi);
+
+int dcp_hfi_send_cmd_async(struct dcp_hfi *hfi,
+ struct dcp_hfi_buf *buf,
+ size_t cmd_len,
+ struct dcp_hfi_completion *comp);
+
+int dcp_hfi_pending_add(struct dcp_hfi *hfi, struct dcp_hfi_completion *comp);
+
+struct dcp_hfi_completion *dcp_hfi_pending_find(struct dcp_hfi *hfi,
+ u32 packet_id);
+
+#endif /* __DCP_HFI_PRIV_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (5 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:22 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
` (16 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Handle HFI messages that are not synchronous command
completions. Capability discovery, resource voting, and display power
control produce asynchronous responses that must not be mistaken for
ordinary command replies.
Dispatch display events, cache capability payloads, deliver
power-control notifications, and defer resource-vote processing while
preserving the existing completion path for synchronous commands.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 49 ++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 298 +++++++++++++++++++++++++++++++++
2 files changed, 347 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 23e1cd39b89d..055e9c584892 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -19,11 +19,24 @@
#include <linux/workqueue.h>
#include <linux/xarray.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
#include "hfi/hfi_packet.h"
#include "hfi/hfi_pack_unpack_common.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_commands_display_events.h"
#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_defs_device.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_properties_device.h"
+#include "hfi/hfi_properties_display.h"
+#include "hfi/hfi_properties_panel.h"
struct dcp_drv;
@@ -35,6 +48,19 @@ struct dcp_drv;
#define DCP_MAX_DISPLAYS 4
#define DCP_HFI_CAPS_BUF_SIZE 4096
+#define DCP_HFI_RESOURCE_VOTE_BUF_SIZE 256
+
+/* Wire-format resource vote entry. */
+struct dcp_resource_vote_pair {
+ u32 key;
+ u64 value;
+};
+
+struct dcp_resource_vote_wire {
+ __le32 key;
+ __le64 value;
+} __packed;
+
struct dcp_hfi_caps_cache {
struct completion device_done;
struct completion display_done;
@@ -135,6 +161,24 @@ struct dcp_hfi {
atomic_t packet_id;
/* Blocks new work once transport teardown begins. */
atomic_t shutdown_in_progress;
+ struct work_struct resource_vote_work;
+ /* Protects resource_vote_buf, resource_vote_len, and callback state. */
+ spinlock_t resource_vote_lock;
+ u8 resource_vote_buf[DCP_HFI_RESOURCE_VOTE_BUF_SIZE];
+ u32 resource_vote_len;
+ u32 resource_vote_generation;
+ void (*resource_vote_cb)(struct dcp_drv *drv,
+ const struct dcp_resource_vote_pair *pairs,
+ u32 count);
+ /* Protects capability payload buffers during event delivery. */
+ spinlock_t caps_lock;
+ /* Serializes capability transactions. */
+ struct mutex caps_cache_lock;
+ struct dcp_hfi_caps_cache caps_cache;
+
+ void *power_control_priv;
+ void (*power_control_cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable, u32 packet_id);
};
int dcp_hfi_init(struct dcp_drv *drv, struct virtio_device *vdev,
@@ -167,3 +211,8 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
enum hfi_packet_payload_type payload_type,
void *payload, u32 payload_size, u32 tx_flags);
#endif /* __DCP_HFI_H__ */
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+ void (*cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable,
+ u32 packet_id));
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv);
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
index 9f7ac798e13c..774a5afac0f0 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -9,6 +9,7 @@
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/kernel.h>
+#include <linux/unaligned.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <linux/spinlock.h>
@@ -21,8 +22,13 @@
#include "hfi/hfi_packet.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_display_events.h"
#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+static void dcp_hfi_resource_vote_work(struct work_struct *work);
static int dcp_hfi_prepost_resp_bufs(struct dcp_hfi *hfi);
u32 dcp_hfi_next_packet_id(struct dcp_hfi *hfi)
@@ -227,6 +233,218 @@ void dcp_hfi_cmd_callback(struct virtqueue *vq)
spin_unlock_irqrestore(&hfi->cmd_lock, flags);
}
+static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
+ struct completion *done,
+ u8 *dst, u32 *dst_len,
+ const struct hfi_packet_info *pkt_info,
+ const char *name)
+{
+ u32 n;
+
+ if (!pkt_info->payload_ptr || !pkt_info->payload_size) {
+ dev_dbg(hfi->dev, "%s: empty payload\n", name);
+ return;
+ }
+
+ if (pkt_info->payload_size > DCP_HFI_CAPS_BUF_SIZE) {
+ dev_warn(hfi->dev, "%s: payload too large: %u\n",
+ name, pkt_info->payload_size);
+ return;
+ }
+
+ n = pkt_info->payload_size;
+ memcpy(dst, pkt_info->payload_ptr, n);
+ *dst_len = n;
+ if (done)
+ complete(done);
+ dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
+}
+
+/* Route asynchronous events and capability packets before flag handling. */
+static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
+ const struct hfi_packet_info *pkt_info)
+{
+ switch (pkt_info->cmd) {
+ case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS:
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD:
+ dev_err(hfi->dev,
+ "DCP: panel dead display %u\n",
+ pkt_info->id & 0xFFFF);
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY:
+ dev_err(hfi->dev,
+ "DCP: HW recovery display %u\n",
+ pkt_info->id & 0xFFFF);
+ return true;
+
+ case HFI_COMMAND_DISPLAY_POWER_CONTROL: {
+ u32 display_id = pkt_info->id & 0xFFFF;
+ u32 power_mask = 0;
+ bool enable = true;
+ unsigned long flags;
+ void *power_priv;
+ void (*power_cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable, u32 packet_id);
+
+ if (pkt_info->payload_ptr &&
+ pkt_info->payload_size >= 2 * sizeof(u32)) {
+ const u32 *p = pkt_info->payload_ptr;
+
+ power_mask = p[0];
+ enable = (p[1] == HFI_TRUE);
+ }
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ power_cb = hfi->power_control_cb;
+ power_priv = hfi->power_control_priv;
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+
+ if (power_cb)
+ power_cb(power_priv, display_id, power_mask, enable,
+ pkt_info->packet_id);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.device_done,
+ hfi->caps_cache.device_buf,
+ &hfi->caps_cache.device_len,
+ pkt_info, "DEVICE_INIT_DEVICE_CAPS");
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.display_done,
+ hfi->caps_cache.display_buf,
+ &hfi->caps_cache.display_len,
+ pkt_info, "DEVICE_INIT_DISPLAY_CAPS");
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_VIG_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.vig_buf,
+ &hfi->caps_cache.vig_len,
+ pkt_info, "VIG_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(0);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.dma_buf,
+ &hfi->caps_cache.dma_len,
+ pkt_info, "DMA_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(1);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_VIG_R1_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.vig_r1_buf,
+ &hfi->caps_cache.vig_r1_len,
+ pkt_info, "VIG_R1_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(2);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_R1_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ dcp_hfi_store_caps_payload(hfi, NULL, hfi->caps_cache.dma_r1_buf,
+ &hfi->caps_cache.dma_r1_len,
+ pkt_info, "DMA_R1_CAPS");
+ hfi->caps_cache.pipe_caps_received |= BIT(3);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_COMMON_LAYER_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ hfi->caps_cache.pipe_caps_received |= BIT(4);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ dev_dbg(hfi->dev,
+ "COMMON_LAYER_CAPS: received (size=%u)\n",
+ pkt_info->payload_size);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DISPLAY_WB_CAPS: {
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ hfi->caps_cache.pipe_caps_received |= BIT(5);
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ dev_dbg(hfi->dev,
+ "DISPLAY_WB_CAPS: received (size=%u) — all pipe caps received\n",
+ pkt_info->payload_size);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE: {
+ unsigned long vote_flags;
+ u32 payload_size = pkt_info->payload_size;
+ bool valid = pkt_info->payload_ptr && payload_size &&
+ payload_size <= DCP_HFI_RESOURCE_VOTE_BUF_SIZE &&
+ payload_size % sizeof(struct dcp_resource_vote_wire) == 0;
+
+ if (!valid) {
+ dev_warn_ratelimited(hfi->dev,
+ "dropping malformed RESOURCE_VOTE payload size=%u max=%u\n",
+ payload_size, DCP_HFI_RESOURCE_VOTE_BUF_SIZE);
+ payload_size = 0;
+ }
+
+ spin_lock_irqsave(&hfi->resource_vote_lock, vote_flags);
+ if (payload_size)
+ memcpy(hfi->resource_vote_buf, pkt_info->payload_ptr,
+ payload_size);
+ hfi->resource_vote_len = payload_size;
+ hfi->resource_vote_generation++;
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, vote_flags);
+
+ if (payload_size)
+ schedule_work(&hfi->resource_vote_work);
+ return true;
+ }
+
+ default:
+ return false;
+ }
+}
+
void dcp_hfi_resp_callback(struct virtqueue *vq)
{
struct dcp_drv *drv = vq->vdev->priv;
@@ -297,6 +515,8 @@ void dcp_hfi_resp_callback(struct virtqueue *vq)
"HFI error: cmd=0x%x pkt_id=%u\n",
pkt_info.cmd, pkt_info.packet_id);
dcp_hfi_complete_pending(hfi, &pkt_info, -EIO);
+ } else if (dcp_hfi_dispatch_event(hfi, &pkt_info)) {
+ /* Event dispatch runs before flag handling. */
} else if (pkt_info.flags & HFI_RX_FLAGS_SUCCESS) {
if (pkt_info.flags & HFI_RX_FLAGS_INFORMATION)
dev_dbg(hfi->dev,
@@ -639,6 +859,47 @@ int dcp_hfi_send_no_wait(struct dcp_hfi *hfi,
payload_type, payload, payload_size, HFI_TX_FLAGS_NONE);
}
+static void dcp_hfi_resource_vote_work(struct work_struct *work)
+{
+ struct dcp_hfi *hfi = container_of(work, struct dcp_hfi,
+ resource_vote_work);
+ u8 buf[DCP_HFI_RESOURCE_VOTE_BUF_SIZE];
+ struct dcp_resource_vote_pair pairs[DCP_HFI_RESOURCE_VOTE_BUF_SIZE /
+ sizeof(struct dcp_resource_vote_wire)];
+ const struct dcp_resource_vote_wire *wire;
+ u32 len, generation, count, i;
+ bool dirty;
+ unsigned long flags;
+
+ do {
+ spin_lock_irqsave(&hfi->resource_vote_lock, flags);
+ len = hfi->resource_vote_len;
+ generation = hfi->resource_vote_generation;
+ if (len)
+ memcpy(buf, hfi->resource_vote_buf, len);
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, flags);
+
+ if (!len || !hfi->resource_vote_cb)
+ return;
+ if (len % sizeof(struct dcp_resource_vote_wire)) {
+ dev_warn_ratelimited(hfi->dev,
+ "dropping malformed RESOURCE_VOTE size=%u\n", len);
+ return;
+ }
+ count = len / sizeof(struct dcp_resource_vote_wire);
+ wire = (const struct dcp_resource_vote_wire *)buf;
+ for (i = 0; i < count; i++) {
+ pairs[i].key = le32_to_cpu(wire[i].key);
+ pairs[i].value = get_unaligned_le64(&wire[i].value);
+ }
+
+ hfi->resource_vote_cb(hfi->drv, pairs, count);
+ spin_lock_irqsave(&hfi->resource_vote_lock, flags);
+ dirty = generation != hfi->resource_vote_generation;
+ spin_unlock_irqrestore(&hfi->resource_vote_lock, flags);
+ } while (dirty);
+}
+
static void dcp_hfi_free_pool(struct dcp_hfi *hfi, struct dcp_hfi_buf *pool,
u32 count)
{
@@ -726,7 +987,10 @@ int dcp_hfi_init(struct dcp_drv *drv,
spin_lock_init(&hfi->cmd_lock);
spin_lock_init(&hfi->pending_lock);
+ spin_lock_init(&hfi->resource_vote_lock);
spin_lock_init(&hfi->resp_drain_lock);
+ spin_lock_init(&hfi->caps_lock);
+ mutex_init(&hfi->caps_cache_lock);
atomic_set(&hfi->packet_id, 0);
atomic_set(&hfi->shutdown_in_progress, 0);
@@ -735,8 +999,11 @@ int dcp_hfi_init(struct dcp_drv *drv,
xa_init(&hfi->pending_xa);
+ INIT_WORK(&hfi->resource_vote_work, dcp_hfi_resource_vote_work);
INIT_DELAYED_WORK(&hfi->resp_repost_work, dcp_hfi_resp_repost_work);
INIT_LIST_HEAD(&hfi->resp_repost_list);
+ hfi->resource_vote_len = 0;
+ hfi->resource_vote_generation = 0;
hfi->cmd_pool = devm_kcalloc(hfi->dev, DCP_HFI_CMD_POOL_SIZE,
sizeof(*hfi->cmd_pool), GFP_KERNEL);
@@ -784,6 +1051,7 @@ int dcp_hfi_init(struct dcp_drv *drv,
err:
atomic_set(&hfi->shutdown_in_progress, 1);
+ cancel_work_sync(&hfi->resource_vote_work);
cancel_delayed_work_sync(&hfi->resp_repost_work);
while (virtqueue_detach_unused_buf(hfi->cmd_vq))
;
@@ -796,6 +1064,31 @@ int dcp_hfi_init(struct dcp_drv *drv,
return ret;
}
+void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
+ void (*cb)(void *priv, u32 display_id,
+ u32 power_mask, bool enable,
+ u32 packet_id))
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ hfi->power_control_priv = priv;
+ hfi->power_control_cb = cb;
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+}
+
+void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ if (hfi->power_control_priv == priv) {
+ hfi->power_control_priv = NULL;
+ hfi->power_control_cb = NULL;
+ }
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+}
+
void dcp_hfi_stop(struct dcp_hfi *hfi)
{
unsigned long flags;
@@ -804,6 +1097,7 @@ void dcp_hfi_stop(struct dcp_hfi *hfi)
atomic_set(&hfi->shutdown_in_progress, 1);
spin_unlock_irqrestore(&hfi->cmd_lock, flags);
+ cancel_work_sync(&hfi->resource_vote_work);
cancel_delayed_work_sync(&hfi->resp_repost_work);
dcp_hfi_cancel_pending(hfi, -ENODEV);
}
@@ -829,6 +1123,10 @@ void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
void dcp_hfi_deinit(struct dcp_hfi *hfi)
{
+ mutex_lock(&hfi->caps_cache_lock);
+ memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+ mutex_unlock(&hfi->caps_cache_lock);
+
xa_destroy(&hfi->pending_xa);
dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (6 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:36 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
` (15 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Wake and acknowledge DCP firmware through the platform IPCC
channel. DCP may power-collapse while VirtIO-MMIO remains accessible,
so a VirtIO queue kick alone cannot reliably wake firmware during
system power transitions.
Implement serialized IPCC notifications, bounded acknowledgment waits,
IRQ wake handling, and teardown-safe callback lifetime management
while leaving HFI data transport on VirtIO.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_drv.h | 1 +
drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 173 +++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_ipcc.h | 42 +++++++++
4 files changed, 217 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 56c48fe2e2c5..9c698c4f4949 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -73,6 +73,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_core.o \
+ dcp/dcp_ipcc.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h
index 44d9b2cd5171..16f87035cb9a 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -11,6 +11,7 @@
#include <linux/platform_device.h>
#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
struct dcp_dpu_kms;
diff --git a/drivers/gpu/drm/msm/dcp/dcp_ipcc.c b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
new file mode 100644
index 000000000000..e161939dddc1
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP IPCC power-notify mailbox client.
+ *
+ * The APSS-to-DCP channel must be signaled before a VirtIO queue kick.
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/of_irq.h>
+#include <linux/string.h>
+#include <linux/virtio.h>
+
+#include "dcp_ipcc.h"
+
+#define DCP_IPCC_POWER_NOTIFY_TIMEOUT_MS 10000
+
+static void dcp_ipcc_handle_rx(struct dcp_ipcc *ipcc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ if (ipcc->shutting_down) {
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+ return;
+ }
+
+ complete(&ipcc->power_notify_done);
+ if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
+ virtqueue_notify(ipcc->resp_vq);
+
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+}
+
+static irqreturn_t dcp_ipcc_power_notify_irq(int irq, void *data)
+{
+ struct dcp_ipcc *ipcc = data;
+
+ dcp_ipcc_handle_rx(ipcc);
+
+ return IRQ_HANDLED;
+}
+
+static void dcp_ipcc_rx_callback(struct mbox_client *cl, void *data)
+{
+ struct dcp_ipcc *ipcc = container_of(cl, struct dcp_ipcc, client);
+
+ dcp_ipcc_handle_rx(ipcc);
+}
+
+static void dcp_ipcc_free_channel(void *data)
+{
+ mbox_free_channel(data);
+}
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev, unsigned int irq_index)
+{
+ int irq, ret;
+
+ memset(ipcc, 0, sizeof(*ipcc));
+ init_completion(&ipcc->power_notify_done);
+ mutex_init(&ipcc->power_notify_lock);
+ spin_lock_init(&ipcc->state_lock);
+
+ ipcc->client.dev = dev;
+ ipcc->client.tx_block = false;
+ ipcc->client.tx_tout = 0;
+ ipcc->client.knows_txdone = true;
+ ipcc->client.rx_callback = dcp_ipcc_rx_callback;
+
+ ipcc->power_chan = mbox_request_channel(&ipcc->client, 0);
+ if (IS_ERR(ipcc->power_chan)) {
+ ret = PTR_ERR(ipcc->power_chan);
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "IPCC: failed to get DCP power channel: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, dcp_ipcc_free_channel,
+ ipcc->power_chan);
+ if (ret)
+ return ret;
+
+ irq = of_irq_get(dev->of_node, irq_index);
+ if (irq <= 0)
+ return dev_err_probe(dev, irq ? irq : -EINVAL,
+ "failed to get required power notify IRQ\n");
+
+ ret = devm_request_irq(dev, irq, dcp_ipcc_power_notify_irq, 0,
+ "dcp-power-notify", ipcc);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to request power notify IRQ %d\n", irq);
+
+ ipcc->power_notify_irq = irq;
+
+ return 0;
+}
+
+void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ if (!ipcc->shutting_down)
+ ipcc->resp_vq = resp_vq;
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+}
+
+void dcp_ipcc_deinit(struct dcp_ipcc *ipcc)
+{
+ struct device *dev = ipcc->client.dev;
+ unsigned long flags;
+
+ mutex_lock(&ipcc->power_notify_lock);
+
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ ipcc->shutting_down = true;
+ ipcc->resp_vq = NULL;
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+
+ devm_free_irq(dev, ipcc->power_notify_irq, ipcc);
+ devm_release_action(dev, dcp_ipcc_free_channel, ipcc->power_chan);
+ mutex_unlock(&ipcc->power_notify_lock);
+}
+
+int dcp_ipcc_enable_irq_wake(struct dcp_ipcc *ipcc)
+{
+ return enable_irq_wake(ipcc->power_notify_irq);
+}
+
+void dcp_ipcc_disable_irq_wake(struct dcp_ipcc *ipcc)
+{
+ disable_irq_wake(ipcc->power_notify_irq);
+}
+
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
+{
+ unsigned long timeout;
+ int ret;
+
+ mutex_lock(&ipcc->power_notify_lock);
+
+ reinit_completion(&ipcc->power_notify_done);
+
+ ret = mbox_send_message(ipcc->power_chan, NULL);
+ mbox_client_txdone(ipcc->power_chan, ret);
+ if (ret < 0) {
+ dev_err(ipcc->client.dev,
+ "IPCC TX: power notify failed: %d\n", ret);
+ goto out_unlock;
+ }
+
+ timeout = wait_for_completion_timeout(&ipcc->power_notify_done,
+ msecs_to_jiffies(DCP_IPCC_POWER_NOTIFY_TIMEOUT_MS));
+ if (!timeout) {
+ dev_err(ipcc->client.dev,
+ "IPCC power notify acknowledgment timed out\n");
+ ret = -ETIMEDOUT;
+ } else {
+ ret = 0;
+ }
+
+out_unlock:
+ mutex_unlock(&ipcc->power_notify_lock);
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_ipcc.h b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
new file mode 100644
index 000000000000..2d1ddd5adebb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * IPCC power-notify mailbox interface for DCP firmware.
+ *
+ * Channel 5 carries APSS-to-DCP wake notifications; responses arrive through
+ * the mailbox client's RX callback.
+ */
+
+#ifndef __DCP_IPCC_H__
+#define __DCP_IPCC_H__
+
+#include <linux/completion.h>
+#include <linux/mailbox_client.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+
+struct device;
+struct virtqueue;
+
+struct dcp_ipcc {
+ struct mbox_client client;
+ struct mbox_chan *power_chan;
+ struct virtqueue *resp_vq;
+ struct completion power_notify_done;
+ /* Serializes concurrent power-notify transactions. */
+ struct mutex power_notify_lock;
+ /* Protects callback state and response queue access. */
+ spinlock_t state_lock;
+ bool shutting_down;
+ int power_notify_irq;
+};
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev, unsigned int irq_index);
+void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq);
+void dcp_ipcc_deinit(struct dcp_ipcc *ipcc);
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc);
+int dcp_ipcc_enable_irq_wake(struct dcp_ipcc *ipcc);
+void dcp_ipcc_disable_irq_wake(struct dcp_ipcc *ipcc);
+
+#endif /* __DCP_IPCC_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (7 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:52 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
` (14 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Translate DRM display state into HFI commands for the firmware-owned
DCP pipeline. Since Linux cannot program the display hardware directly,
KMS operations need command helpers for resources, CRTCs, planes,
bandwidth, and frame sequencing.
Implement generic display command helpers and encode capability,
resource, layer, mixer, bandwidth, frame-trigger, and scan-sequence
state into HFI properties.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 4 +
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 32 +++
drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c | 191 ++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c | 64 ++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c | 366 ++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c | 63 +++++
6 files changed, 720 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 9c698c4f4949..bdc5e3b8efa7 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -73,6 +73,10 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_core.o \
+ dcp/dcp_hfi_crtc.o \
+ dcp/dcp_hfi_encoder.o \
+ dcp/dcp_hfi_kms.o \
+ dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 055e9c584892..6d3eadb5e811 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -210,6 +210,38 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
enum hfi_cmd_buff_type cmd_type, u32 object_id,
enum hfi_packet_payload_type payload_type,
void *payload, u32 payload_size, u32 tx_flags);
+int dcp_hfi_display_event_register(struct dcp_hfi *hfi, u32 session_id,
+ u32 event_id);
+int dcp_hfi_display_event_deregister(struct dcp_hfi *hfi, u32 session_id,
+ u32 event_id);
+int dcp_hfi_display_event_register_async(struct dcp_hfi *hfi, u32 session_id,
+ u32 event_id);
+int dcp_hfi_display_event_deregister_async(struct dcp_hfi *hfi,
+ u32 session_id, u32 event_id);
+int dcp_hfi_crtc_set_resource(struct dcp_hfi *hfi, u32 session_id,
+ u32 lm_idx, u32 width, u32 height,
+ u32 roi_x, u32 roi_y);
+int dcp_hfi_crtc_set_dest_roi(struct dcp_hfi *hfi, u32 session_id,
+ u32 x, u32 y, u32 width, u32 height);
+int dcp_hfi_crtc_set_scan_sequence_id(struct dcp_hfi *hfi, u32 session_id,
+ u32 seq_id);
+int dcp_hfi_crtc_set_power_mode(struct dcp_hfi *hfi, u32 session_id,
+ enum hfi_display_power_mode mode);
+int dcp_hfi_crtc_set_autorefresh(struct dcp_hfi *hfi, u32 session_id,
+ bool enable, u32 frame_count);
+int dcp_hfi_crtc_set_bw(struct dcp_hfi *hfi, u32 session_id,
+ u64 dram_ib, u64 dram_ab, u64 llcc_ib,
+ u64 llcc_ab, u64 core_clk);
+int dcp_hfi_submit_prop_pkt(struct dcp_hfi *hfi, const void *pkt,
+ size_t pkt_size);
+int dcp_hfi_plane_disable(struct dcp_hfi *hfi, u32 session_id, u32 pipe_idx);
+int dcp_hfi_display_commit(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_send_device_init(struct dcp_hfi *hfi);
+int dcp_hfi_get_capabilities(struct dcp_hfi *hfi, struct dcp_caps *caps);
+int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
+int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
+ u32 prop_id, u32 value);
+
#endif /* __DCP_HFI_H__ */
void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
void (*cb)(void *priv, u32 display_id,
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c
new file mode 100644
index 000000000000..c31a0628f4c9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for display events and CRTC properties.
+ */
+
+#include <linux/device.h>
+
+#include "dcp_hfi_priv.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_display_events.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_properties_display.h"
+
+int dcp_hfi_display_event_register(struct dcp_hfi *hfi, u32 session_id,
+ u32 event_cmd)
+{
+ int ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_EVENT_REGISTER,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_U32,
+ &event_cmd, sizeof(event_cmd),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ return ret;
+}
+
+int dcp_hfi_display_event_deregister(struct dcp_hfi *hfi, u32 session_id,
+ u32 event_cmd)
+{
+ int ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_EVENT_DEREGISTER,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_U32,
+ &event_cmd, sizeof(event_cmd),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ return ret;
+}
+
+int dcp_hfi_display_event_register_async(struct dcp_hfi *hfi,
+ u32 session_id, u32 event_cmd)
+{
+ return dcp_hfi_send_no_wait_atomic_flags(hfi,
+ HFI_COMMAND_DISPLAY_EVENT_REGISTER,
+ HFI_CMD_BUFF_DISPLAY, session_id, HFI_PAYLOAD_U32,
+ &event_cmd, sizeof(event_cmd), HFI_TX_FLAGS_NON_DISCARDABLE);
+}
+
+int dcp_hfi_display_event_deregister_async(struct dcp_hfi *hfi,
+ u32 session_id, u32 event_cmd)
+{
+ return dcp_hfi_send_no_wait_atomic_flags(hfi,
+ HFI_COMMAND_DISPLAY_EVENT_DEREGISTER,
+ HFI_CMD_BUFF_DISPLAY, session_id, HFI_PAYLOAD_U32,
+ &event_cmd, sizeof(event_cmd), HFI_TX_FLAGS_NON_DISCARDABLE);
+}
+
+int dcp_hfi_crtc_set_resource(struct dcp_hfi *hfi, u32 session_id,
+ u32 lm_idx, u32 width, u32 height,
+ u32 roi_x, u32 roi_y)
+{
+ u32 payload[7];
+
+ payload[0] = 1;
+ HFI_PACK(&payload[1], HFI_PROPERTY_DISPLAY_SET_RESOURCE_DATA,
+ HFI_RESOURCE_LM, lm_idx, width, height,
+ (roi_x << 16) | (roi_y & 0xFFFF));
+
+ return dcp_hfi_send_no_wait_flags(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload),
+ HFI_TX_FLAGS_NON_DISCARDABLE);
+}
+
+int dcp_hfi_crtc_set_dest_roi(struct dcp_hfi *hfi, u32 session_id,
+ u32 x, u32 y, u32 width, u32 height)
+{
+ u32 payload[9];
+
+ payload[0] = 1;
+ HFI_PACK(&payload[1], HFI_PROPERTY_DISPLAY_DEST_ROI,
+ session_id, PANEL_ROI, 1, x, y, width, height);
+
+ return dcp_hfi_send_no_wait_flags(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_U32_ARRAY, payload,
+ sizeof(payload), HFI_TX_FLAGS_NON_DISCARDABLE);
+}
+
+int dcp_hfi_crtc_set_scan_sequence_id(struct dcp_hfi *hfi, u32 session_id,
+ u32 seq_id)
+{
+ u32 payload[3];
+
+ payload[0] = 1;
+ payload[1] = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_SCAN_SEQUENCE_ID, 0, 1);
+ payload[2] = seq_id;
+
+ return dcp_hfi_send_no_wait(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload));
+}
+
+int dcp_hfi_crtc_set_power_mode(struct dcp_hfi *hfi, u32 session_id,
+ enum hfi_display_power_mode mode)
+{
+ u32 payload[3];
+
+ payload[0] = 1;
+ payload[1] = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_POWER_MODE, 0, 1);
+ payload[2] = (u32)mode;
+
+ return dcp_hfi_send_no_wait(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload));
+}
+
+int dcp_hfi_crtc_set_autorefresh(struct dcp_hfi *hfi, u32 session_id,
+ bool enable, u32 frame_count)
+{
+ u32 payload[4];
+
+ payload[0] = 1;
+ payload[1] = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_AUTOREFRESH_CFG, 0, 2);
+ payload[2] = enable ? 1 : 0;
+ payload[3] = frame_count;
+
+ return dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_crtc_set_bw(struct dcp_hfi *hfi, u32 session_id,
+ u64 dram_ib, u64 dram_ab,
+ u64 llcc_ib, u64 llcc_ab,
+ u64 core_clk)
+{
+ u32 payload[16];
+ u32 *p = payload;
+
+ *p++ = 5;
+
+#define PACK_U64_PROP(prop_id, val) \
+ do { \
+ typeof(val) __val = (val); \
+ *p++ = HFI_PACK_KEY((prop_id), 0, 2); \
+ *p++ = lower_32_bits(__val); \
+ *p++ = upper_32_bits(__val); \
+ } while (0)
+
+ PACK_U64_PROP(HFI_PROPERTY_DISPLAY_DRAM_IB, dram_ib);
+ PACK_U64_PROP(HFI_PROPERTY_DISPLAY_DRAM_AB, dram_ab);
+ PACK_U64_PROP(HFI_PROPERTY_DISPLAY_LLCC_IB, llcc_ib);
+ PACK_U64_PROP(HFI_PROPERTY_DISPLAY_LLCC_AB, llcc_ab);
+ PACK_U64_PROP(HFI_PROPERTY_DISPLAY_CORE_CLK, core_clk);
+
+#undef PACK_U64_PROP
+
+ return dcp_hfi_send_no_wait(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload,
+ (u32)((p - payload) * sizeof(u32)));
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c
new file mode 100644
index 000000000000..45d4ab2e5a87
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * HFI commands for display commits and encoder properties.
+ */
+
+#include <linux/device.h>
+
+#include "dcp_hfi_priv.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_properties_display.h"
+#include "hfi/hfi_packet.h"
+
+int dcp_hfi_display_commit(struct dcp_hfi *hfi, u32 session_id)
+{
+ u32 commit_flag = HFI_COMMIT;
+ int ret;
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_FRAME_TRIGGER,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32,
+ &commit_flag, sizeof(commit_flag),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_COMMIT_MS);
+ if (ret)
+ dev_err(hfi->dev, "HFI FRAME_TRIGGER(COMMIT) failed: %d\n",
+ ret);
+
+ return ret;
+}
+
+int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
+ u32 prop_id, u32 value)
+{
+ u32 payload[3];
+ int ret;
+
+ payload[0] = 1;
+ payload[1] = HFI_PACK_KEY(prop_id, 0, 1);
+ payload[2] = value;
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ if (ret)
+ dev_err(hfi->dev,
+ "HFI SET_PROPERTY prop=0x%x failed: %d\n",
+ prop_id, ret);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c
new file mode 100644
index 000000000000..ac8000a8ee90
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c
@@ -0,0 +1,366 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for device initialization, capability parsing, and resource setup.
+ */
+
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_device.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_pack_unpack_common.h"
+#include "hfi/hfi_properties_device.h"
+#include "hfi/hfi_properties_display.h"
+#include "hfi/hfi_utils.h"
+
+static void dcp_hfi_caps_cache_init(struct dcp_hfi_caps_cache *cache)
+{
+ init_completion(&cache->device_done);
+ init_completion(&cache->display_done);
+}
+
+static void dcp_hfi_caps_cache_start(struct dcp_hfi *hfi)
+{
+ memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+ dcp_hfi_caps_cache_init(&hfi->caps_cache);
+}
+
+static void dcp_hfi_caps_cache_stop(struct dcp_hfi *hfi)
+{
+ memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
+}
+
+int dcp_hfi_send_device_init(struct dcp_hfi *hfi)
+{
+ int ret;
+
+ mutex_lock(&hfi->caps_cache_lock);
+ dcp_hfi_caps_cache_start(hfi);
+
+ /* Payload and flags follow the HFI device-command definition. */
+ dev_dbg(hfi->dev,
+ "HFI DEVICE_INIT cmd=0x%x flags=0x%x timeout=%ums\n",
+ HFI_COMMAND_DEVICE_INIT,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED | HFI_TX_FLAGS_NON_DISCARDABLE,
+ DCP_HFI_TIMEOUT_INIT_MS);
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DEVICE_INIT,
+ HFI_CMD_BUFF_DEVICE, 0,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_INIT_MS);
+ if (ret) {
+ dev_err(hfi->dev, "HFI DEVICE_INIT failed: %d\n", ret);
+ dcp_hfi_caps_cache_stop(hfi);
+ } else {
+ dev_dbg(hfi->dev, "HFI DEVICE_INIT succeeded\n");
+ }
+
+ mutex_unlock(&hfi->caps_cache_lock);
+ return ret;
+}
+
+static void dcp_hfi_parse_init_caps_kv(struct dcp_caps *caps,
+ u32 prop_key,
+ const u32 *payload,
+ u32 max_words)
+{
+ u32 prop_id = HFI_PROP_ID(prop_key);
+ u32 count, i;
+
+ if (!payload || !max_words)
+ return;
+
+ switch (prop_id) {
+ case HFI_PROPERTY_DEVICE_INIT_DCP_HW_VERSION:
+ caps->dcp_hw_rev = payload[0];
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_MDSS_HW_VERSION:
+ caps->hw_rev = payload[0];
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_DCP_FW_VERSION:
+ caps->fw_rev = payload[0];
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_VIG_INDICES:
+ if (max_words < 1)
+ break;
+ count = payload[0];
+ if (count > max_words - 1)
+ count = max_words - 1;
+ caps->num_vig_pipes = 0;
+ caps->virt_vig_count = 0;
+ for (i = 0; i < count; i++) {
+ u32 idx = payload[1 + i];
+
+ if (DCP_REC_ID(idx) == 0) {
+ if (caps->num_vig_pipes < DCP_MAX_SSPP_COUNT)
+ caps->vig_pipe_ids[caps->num_vig_pipes++] = idx;
+ } else {
+ if (caps->virt_vig_count < DCP_MAX_SSPP_COUNT)
+ caps->vig_r1_indices[caps->virt_vig_count++] = idx;
+ }
+ }
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_DMA_INDICES:
+ if (max_words < 1)
+ break;
+ count = payload[0];
+ if (count > max_words - 1)
+ count = max_words - 1;
+ caps->num_dma_pipes = 0;
+ caps->virt_dma_count = 0;
+ for (i = 0; i < count; i++) {
+ u32 idx = payload[1 + i];
+
+ if (DCP_REC_ID(idx) == 0) {
+ if (caps->num_dma_pipes < DCP_MAX_SSPP_COUNT)
+ caps->dma_pipe_ids[caps->num_dma_pipes++] = idx;
+ } else {
+ if (caps->virt_dma_count < DCP_MAX_SSPP_COUNT)
+ caps->dma_r1_indices[caps->virt_dma_count++] = idx;
+ }
+ }
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_MAX_DISPLAY_COUNT:
+ caps->num_displays = payload[0];
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_DSI_INDICES:
+ if (max_words < 1)
+ break;
+ count = min_t(u32, payload[0], DCP_MAX_BLOCKS);
+ if (count > max_words - 1)
+ count = max_words - 1;
+ caps->dsi_count = count;
+ for (i = 0; i < count; i++)
+ caps->dsi_indices[i] = payload[1 + i];
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_MAX_DSI_RESOLUTION:
+ {
+ u32 w = (payload[0] >> 16) & 0xFFFF;
+ u32 h = payload[0] & 0xFFFF;
+
+ if (w > caps->max_width)
+ caps->max_width = w;
+ if (h > caps->max_height)
+ caps->max_height = h;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void dcp_hfi_log_pipe_ids(struct device *dev, const char *label,
+ const u32 *ids, u32 count)
+{
+ char buf[128];
+ int pos = 0;
+ u32 i;
+
+ for (i = 0; i < count && pos < (int)sizeof(buf) - 8; i++)
+ pos += scnprintf(buf + pos, sizeof(buf) - pos,
+ "%s0x%x", i ? "," : "", ids[i]);
+
+ dev_dbg(dev, "%s: count=%u ids=[%s]\n", label, count, buf);
+}
+
+static int dcp_hfi_parse_kv_buf(struct dcp_hfi *hfi, struct dcp_caps *caps,
+ const u8 *buf, u32 buf_len)
+{
+ struct hfi_util_kv_parser kv_parser;
+ u32 prop_key, *prop_payload, max_words, last_read;
+ int ret;
+
+ ret = hfi_util_kv_parser_init(&kv_parser, buf_len, (u32 *)buf);
+ if (ret) {
+ dev_err(hfi->dev, "KV parser init failed: %d\n", ret);
+ return ret;
+ }
+
+ last_read = 0;
+ ret = hfi_util_kv_parser_get_next(&kv_parser, last_read,
+ &prop_key, &prop_payload, &max_words);
+ while (!ret && prop_key) {
+ dcp_hfi_parse_init_caps_kv(caps, prop_key,
+ prop_payload, max_words);
+ last_read = HFI_PROP_SZ(prop_key) ?: max_words;
+ ret = hfi_util_kv_parser_get_next(&kv_parser, last_read,
+ &prop_key, &prop_payload,
+ &max_words);
+ }
+ return ret;
+}
+
+int dcp_hfi_get_capabilities(struct dcp_hfi *hfi, struct dcp_caps *caps)
+{
+ struct dcp_hfi_caps_cache *cache;
+ int ret = 0, opt_ret;
+
+ memset(caps, 0, sizeof(*caps));
+
+ mutex_lock(&hfi->caps_cache_lock);
+ cache = &hfi->caps_cache;
+
+ if (!wait_for_completion_timeout(&cache->device_done,
+ msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
+ dev_err(hfi->dev,
+ "DEVICE_INIT_DEVICE_CAPS timeout after %ums\n",
+ DCP_HFI_TIMEOUT_CAPS_MS);
+ ret = -ETIMEDOUT;
+ goto out_free_cache;
+ }
+
+ if (!cache->device_len) {
+ dev_err(hfi->dev, "DEVICE_INIT_DEVICE_CAPS: empty payload\n");
+ ret = -ENODATA;
+ goto out_free_cache;
+ }
+
+ ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->device_buf,
+ cache->device_len);
+ if (ret)
+ goto out_free_cache;
+
+ if (!wait_for_completion_timeout(&cache->display_done,
+ msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
+ dev_warn(hfi->dev,
+ "DEVICE_INIT_DISPLAY_CAPS not received\n");
+ } else if (cache->display_len > 0) {
+ opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->display_buf,
+ cache->display_len);
+ if (opt_ret) {
+ dev_warn(hfi->dev,
+ "DISPLAY_CAPS parse failed: %d (non-fatal)\n",
+ opt_ret);
+ }
+ }
+ if (cache->vig_len > 0) {
+ opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->vig_buf,
+ cache->vig_len);
+ if (opt_ret)
+ dev_warn(hfi->dev,
+ "VIG_CAPS parse failed: %d (non-fatal)\n",
+ opt_ret);
+ else
+ dcp_hfi_log_pipe_ids(hfi->dev, "VIG_CAPS",
+ caps->vig_pipe_ids,
+ caps->num_vig_pipes);
+ }
+
+ if (cache->dma_len > 0) {
+ opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->dma_buf,
+ cache->dma_len);
+ if (opt_ret)
+ dev_warn(hfi->dev,
+ "DMA_CAPS parse failed: %d (non-fatal)\n",
+ opt_ret);
+ else
+ dcp_hfi_log_pipe_ids(hfi->dev, "DMA_CAPS",
+ caps->dma_pipe_ids,
+ caps->num_dma_pipes);
+ }
+
+ if (cache->vig_r1_len > 0) {
+ opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->vig_r1_buf,
+ cache->vig_r1_len);
+ if (opt_ret)
+ dev_warn(hfi->dev,
+ "VIG_R1_CAPS parse failed: %d (non-fatal)\n",
+ opt_ret);
+ else
+ dcp_hfi_log_pipe_ids(hfi->dev, "VIG_R1_CAPS",
+ caps->vig_r1_indices,
+ caps->virt_vig_count);
+ }
+
+ if (cache->dma_r1_len > 0) {
+ opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->dma_r1_buf,
+ cache->dma_r1_len);
+ if (opt_ret)
+ dev_warn(hfi->dev,
+ "DMA_R1_CAPS parse failed: %d (non-fatal)\n",
+ opt_ret);
+ else
+ dcp_hfi_log_pipe_ids(hfi->dev, "DMA_R1_CAPS",
+ caps->dma_r1_indices,
+ caps->virt_dma_count);
+ }
+ caps->max_planes = caps->num_vig_pipes + caps->num_dma_pipes;
+ if (!caps->num_displays) {
+ dev_err(hfi->dev, "invalid DCP caps: zero display count\n");
+ ret = -EINVAL;
+ goto out_free_cache;
+ }
+
+ if (!caps->max_width || !caps->max_height) {
+ dev_err(hfi->dev, "invalid DCP caps: max resolution %ux%u\n",
+ caps->max_width, caps->max_height);
+ ret = -EINVAL;
+ goto out_free_cache;
+ }
+
+ if (!caps->max_planes) {
+ dev_err(hfi->dev, "invalid DCP caps: no scanout pipes\n");
+ ret = -EINVAL;
+ goto out_free_cache;
+ }
+
+ dev_dbg(hfi->dev,
+ "DCP caps: fw=0x%x hw=0x%x dcp=0x%x disp=%u max=%ux%u vig=%u dma=%u\n",
+ caps->fw_rev, caps->hw_rev, caps->dcp_hw_rev,
+ caps->num_displays, caps->max_width, caps->max_height,
+ caps->num_vig_pipes, caps->num_dma_pipes);
+
+out_free_cache:
+ dcp_hfi_caps_cache_stop(hfi);
+ mutex_unlock(&hfi->caps_cache_lock);
+ return ret;
+}
+
+int dcp_hfi_send_resource_register(struct dcp_hfi *hfi)
+{
+ static const u32 resource_ids[] = {
+ HFI_PROPERTY_DISPLAY_DRAM_IB,
+ HFI_PROPERTY_DISPLAY_DRAM_AB,
+ HFI_PROPERTY_DISPLAY_LLCC_IB,
+ HFI_PROPERTY_DISPLAY_LLCC_AB,
+ HFI_PROPERTY_DISPLAY_CORE_IB,
+ HFI_PROPERTY_DISPLAY_CORE_AB,
+ HFI_PROPERTY_DISPLAY_CORE_CLK,
+ };
+ u32 payload[1 + ARRAY_SIZE(resource_ids)];
+ u32 i;
+ int ret;
+
+ payload[0] = ARRAY_SIZE(resource_ids);
+ for (i = 0; i < ARRAY_SIZE(resource_ids); i++)
+ payload[1 + i] = resource_ids[i];
+
+ dev_dbg(hfi->dev, "sending HFI_COMMAND_DEVICE_RESOURCE_REGISTER (%zu resources)\n",
+ ARRAY_SIZE(resource_ids));
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DEVICE_RESOURCE_REGISTER,
+ HFI_CMD_BUFF_DEVICE, 0,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_RESOURCE_REGISTER_MS);
+ if (ret)
+ dev_err(hfi->dev,
+ "HFI RESOURCE_REGISTER failed: %d\n", ret);
+ else
+ dev_dbg(hfi->dev, "HFI RESOURCE_REGISTER succeeded\n");
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c
new file mode 100644
index 000000000000..960628ccd98e
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for plane properties and layer detach.
+ */
+
+#include <linux/device.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+
+int dcp_hfi_submit_prop_pkt(struct dcp_hfi *hfi,
+ const void *pkt, size_t pkt_size)
+{
+ struct dcp_hfi_buf *buf;
+
+ if (!hfi || !pkt || !pkt_size)
+ return -EINVAL;
+
+ buf = dcp_hfi_buf_alloc(hfi);
+ if (!buf) {
+ dev_err(hfi->dev, "HFI prop pkt: TX buffer pool exhausted\n");
+ return -ENOMEM;
+ }
+
+ if (pkt_size > DCP_HFI_BUF_SIZE) {
+ dev_err(hfi->dev, "HFI prop pkt too large: %zu > %d\n",
+ pkt_size, DCP_HFI_BUF_SIZE);
+ dcp_hfi_buf_free(hfi, buf);
+ return -EINVAL;
+ }
+
+ memcpy(buf->data, pkt, pkt_size);
+
+ return dcp_hfi_send_cmd_async(hfi, buf, pkt_size, NULL);
+}
+
+int dcp_hfi_plane_disable(struct dcp_hfi *hfi, u32 session_id, u32 pipe_idx)
+{
+ u32 payload[3];
+ int ret;
+
+ payload[0] = 1;
+ payload[1] = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_DETACH_LAYER, 0, 1);
+ payload[2] = pipe_idx;
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_SET_PROPERTY,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ payload, sizeof(payload),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ if (ret)
+ dev_warn(hfi->dev,
+ "HFI DETACH_LAYER pipe=%u failed: %d\n",
+ pipe_idx, ret);
+ return ret;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (8 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 11:13 ` sashiko-bot
2026-10-27 11:06 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state Jayant Shekhar
` (13 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Translate DSI host and panel operations into HFI commands. Firmware
owns the DSI hardware, so mode validation, display power, panel setup,
and DCS transfers must cross the HFI interface instead of accessing
controller registers from Linux.
Implement DSI mode validation, display session and power management,
panel initialization, and DCS transfer helpers while keeping them
separate from generic display commands.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 2 +
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 81 ++++
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 25 ++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c | 252 ++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++++++++++++++++++++
5 files changed, 952 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index bdc5e3b8efa7..77c268aabca6 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -74,6 +74,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_core.o \
dcp/dcp_hfi_crtc.o \
+ dcp/dcp_hfi_dsi.o \
+ dcp/dcp_hfi_dsi_panel.o \
dcp/dcp_hfi_encoder.o \
dcp/dcp_hfi_kms.o \
dcp/dcp_hfi_plane.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
new file mode 100644
index 000000000000..a8898e469b44
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
@@ -0,0 +1,81 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI panel configuration shared by the HFI panel implementation and
+ * the DRM DSI bridge.
+ */
+
+#ifndef __DCP_DSI_PANEL_H__
+#define __DCP_DSI_PANEL_H__
+
+#include <linux/dma-mapping.h>
+#include <linux/sizes.h>
+#include <linux/types.h>
+
+#define DCP_DSI_MAX_PHY_TIMINGS 32
+
+/**
+ * struct dcp_dsi_hfi_panel_config - Complete HFI PANEL_INIT configuration.
+ *
+ * This contains the bridge-owned panel configuration plus the
+ * mode-dependent values assembled before the HFI PANEL_INIT command.
+ */
+struct dcp_dsi_hfi_panel_config {
+ u32 display_type;
+ u32 num_ctrls;
+ u32 ctrl_indices[4];
+ u32 num_phys;
+ u32 phy_indices[4];
+ u32 vsync_source;
+ u32 te_dcs_command;
+ u32 dma_trigger;
+ u32 stream_trigger;
+ u32 wr_mem_start;
+ u32 wr_mem_continue;
+ u32 dma_sched_line;
+ u32 dma_sched_window;
+ u32 phy_type;
+ bool cphy_mode;
+ bool lp11_init;
+ bool ulps_supported;
+ bool bllp_eof_power_mode;
+ bool bllp_power_mode;
+ u32 lane_map;
+ u32 color_order;
+ u32 esd_mode;
+ u32 jitter_numer;
+ u32 jitter_denom;
+ dma_addr_t dcs_tx_dva;
+ dma_addr_t dcs_tx_iova;
+ u32 dcs_tx_buf_size;
+ u32 phy_timings[DCP_DSI_MAX_PHY_TIMINGS];
+ u32 num_phy_timings;
+ bool dsc_enabled;
+ u32 dsc_version_major;
+ u32 dsc_version_minor;
+ u32 dsc_scr_version;
+ u32 dsc_slice_height;
+ u32 dsc_slice_width;
+ u32 dsc_slices_per_pkt;
+ u32 dsc_bits_per_component;
+ u32 dsc_pps_delay_ms;
+ u32 dsc_bits_per_pixel;
+ u32 dsc_chroma_format;
+ u32 dsc_color_space;
+ u32 dsc_block_prediction_enable;
+ /* Optional DSC rate-control override panel configuration. */
+ bool dsc_rc_override;
+ u8 dsc_rc_min_qp[15];
+ u8 dsc_rc_max_qp[15];
+ u8 dsc_rc_bpg_offsets[15];
+ u32 topology_mixer_count;
+ u32 topology_enc_count;
+ u32 topology_intf_count;
+};
+
+/* DCS buffers use IOVAs directly in the firmware command descriptor. */
+#define DCP_DSI_DCS_TX_BUF_SIZE SZ_4K
+#define DCP_DSI_DCS_RX_BUF_SIZE SZ_4K
+
+#endif /* __DCP_DSI_PANEL_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 6d3eadb5e811..88d73de69383 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -242,6 +242,31 @@ int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
u32 prop_id, u32 value);
+struct dcp_dsi_hfi_panel_config;
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+ const struct drm_display_mode *mode,
+ const struct mipi_dsi_device *dsi,
+ const struct dcp_dsi_hfi_panel_config *cfg);
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx,
+ u32 display_id, u32 *session_id);
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+ const struct drm_display_mode *mode,
+ u64 clk_rate_hz);
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+ const struct drm_display_mode *mode,
+ u64 clk_rate_hz);
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+ u32 packet_id);
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id);
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id);
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+ const struct mipi_dsi_msg *msg,
+ void *tx_buf, dma_addr_t tx_iova,
+ void *rx_buf, dma_addr_t rx_iova,
+ u32 post_wait_ms, u32 ctrl_idx);
#endif /* __DCP_HFI_H__ */
void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
void (*cb)(void *priv, u32 display_id,
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
new file mode 100644
index 000000000000..fbeec03cf90d
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for the DSI display-session lifecycle.
+ * Panel initialization and DCS transfers are implemented in dcp_hfi_dsi_panel.c.
+ */
+
+#include <linux/device.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_display.h"
+
+static void dcp_hfi_fill_mode_info(struct hfi_display_mode_info *info,
+ const struct drm_display_mode *mode)
+{
+ memset(info, 0, sizeof(*info));
+ info->size = sizeof(*info);
+ info->h_active = mode->hdisplay;
+ info->h_back_porch = mode->htotal - mode->hsync_end;
+ info->h_sync_width = mode->hsync_end - mode->hsync_start;
+ info->h_front_porch = mode->hsync_start - mode->hdisplay;
+ info->h_skew = mode->hskew;
+ info->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
+ info->v_active = mode->vdisplay;
+ info->v_back_porch = mode->vtotal - mode->vsync_end;
+ info->v_sync_width = mode->vsync_end - mode->vsync_start;
+ info->v_front_porch = mode->vsync_start - mode->vdisplay;
+ info->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
+ info->refresh_rate = drm_mode_vrefresh(mode);
+ info->clk_rate_hz_lo = lower_32_bits((u64)mode->clock * 1000);
+ info->clk_rate_hz_hi = upper_32_bits((u64)mode->clock * 1000);
+}
+
+static void dcp_hfi_dump_mode_info(struct dcp_hfi *hfi, const char *cmd,
+ u32 session_id,
+ const struct hfi_display_mode_info *info)
+{
+ dev_dbg(hfi->dev,
+ "HFI %s s=%u h=%u/%u/%u/%u v=%u/%u/%u/%u p=%u/%u r=%u clk=%llu sz=%u\n",
+ cmd, session_id,
+ info->h_active, info->h_back_porch, info->h_sync_width,
+ info->h_front_porch,
+ info->v_active, info->v_back_porch, info->v_sync_width,
+ info->v_front_porch,
+ info->h_sync_polarity, info->v_sync_polarity,
+ info->refresh_rate,
+ (u64)info->clk_rate_hz_lo | ((u64)info->clk_rate_hz_hi << 32),
+ info->size);
+}
+
+int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
+ const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+ struct hfi_display_mode_info info;
+ int ret;
+
+ dcp_hfi_fill_mode_info(&info, mode);
+ info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+ info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+ dcp_hfi_dump_mode_info(hfi, "HFI MODE_VALIDATE", session_id, &info);
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_MODE_VALIDATE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ &info, sizeof(info),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_MODE_MS);
+
+ return ret;
+}
+
+int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
+ const struct drm_display_mode *mode, u64 clk_rate_hz)
+{
+ struct hfi_display_mode_info info;
+ int ret;
+
+ dcp_hfi_fill_mode_info(&info, mode);
+ info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
+ info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
+ dcp_hfi_dump_mode_info(hfi, "HFI SET_MODE", session_id, &info);
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_SET_MODE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ &info, sizeof(info),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_MODE_MS);
+
+ return ret;
+}
+
+int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id)
+{
+ return dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_POWER_REGISTER,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
+ u32 packet_id)
+{
+ struct dcp_hfi_buf *buf;
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ int ret;
+
+ buf = dcp_hfi_buf_alloc(hfi);
+ if (!buf)
+ return -ENOMEM;
+
+ hdl.cmd_buffer = buf->data;
+ hdl.size = DCP_HFI_BUF_SIZE;
+
+ hdr_info.num_packets = 0;
+ hdr_info.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+ hdr_info.object_id = session_id;
+ hdr_info.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl, &hdr_info);
+ if (ret)
+ goto err_free_buf;
+
+ pkt_info.cmd = HFI_COMMAND_DISPLAY_POWER_CONTROL;
+ pkt_info.id = session_id & 0xFFFF;
+ pkt_info.flags = HFI_RX_FLAGS_SUCCESS;
+ pkt_info.packet_id = packet_id;
+ pkt_info.payload_type = HFI_PAYLOAD_NONE;
+ pkt_info.payload_size = 0;
+ pkt_info.payload_ptr = NULL;
+
+ ret = hfi_create_packet_header(&hdl, &pkt_info);
+ if (ret)
+ goto err_free_buf;
+
+ return dcp_hfi_send_cmd_async(hfi, buf, hfi_get_header_size(&hdl), NULL);
+
+err_free_buf:
+ dcp_hfi_buf_free(hfi, buf);
+ return ret;
+}
+
+int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx, u32 display_id,
+ u32 *session_id)
+{
+ int ret;
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_ENABLE,
+ HFI_CMD_BUFF_DISPLAY, display_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ if (ret) {
+ dev_err(hfi->dev,
+ "HFI DISPLAY_ENABLE intf=%u display_id=%u failed: %d\n",
+ intf_idx, display_id, ret);
+ return ret;
+ }
+
+ *session_id = display_id;
+
+ return 0;
+}
+
+int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id)
+{
+ return dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_POST_ENABLE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+ return dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_DISABLE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id)
+{
+ return dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_POST_DISABLE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+}
+
+int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)
+{
+ int ret;
+
+ ret = dcp_hfi_display_disable(hfi, session_id);
+ if (ret) {
+ dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = dcp_hfi_display_post_disable(hfi, session_id);
+ if (ret) {
+ dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
+ return ret;
+ }
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_CLOSE,
+ HFI_CMD_BUFF_DISPLAY, session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_MS);
+ if (ret)
+ dev_warn(hfi->dev, "HFI DISPLAY_CLOSE failed: %d\n", ret);
+
+ return ret;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
new file mode 100644
index 000000000000..b27fa8fa3160
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
@@ -0,0 +1,592 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * HFI commands for panel initialization and DCS transfers.
+ * Firmware owns the DSI controller and PHY.
+ */
+
+#include <linux/device.h>
+#include <linux/math64.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_mipi_dsi.h>
+
+#include "dcp_dsi_panel.h"
+#include "dcp_hfi.h"
+#include "dcp_hfi_priv.h"
+#include "hfi/hfi_commands_display.h"
+#include "hfi/hfi_commands_panel.h"
+#include "hfi/hfi_defs_common.h"
+#include "hfi/hfi_defs_panel.h"
+#include "hfi/hfi_packet.h"
+#include "hfi/hfi_packer.h"
+#include "hfi/hfi_properties_panel.h"
+
+#define DCP_DSI_CTRL_CMD_READ BIT(0)
+#define DCP_DSI_CTRL_CMD_FETCH_MEMORY BIT(5)
+#define DCP_DSI_CTRL_CMD_LAST_COMMAND BIT(6)
+
+static u32 dcp_bpp_to_hfi(u32 bpp)
+{
+ switch (bpp) {
+ case 16: return HFI_PANEL_BPP_16;
+ case 18: return HFI_PANEL_BPP_18;
+ case 30: return HFI_PANEL_BPP_30;
+ case 24:
+ default: return HFI_PANEL_BPP_24;
+ }
+}
+
+static u32 dcp_dsi_traffic_mode(const struct mipi_dsi_device *dsi)
+{
+ if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO))
+ return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+ if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
+ return HFI_PANEL_TRAFFIC_BURST_MODE;
+ if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
+ return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_PULSE_MODE;
+ return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
+}
+
+static u32 dcp_lanes_to_hfi(u32 num_lanes)
+{
+ u32 mask = 0;
+
+ if (num_lanes >= 1)
+ mask |= HFI_PANEL_LANE_0;
+ if (num_lanes >= 2)
+ mask |= HFI_PANEL_LANE_1;
+ if (num_lanes >= 3)
+ mask |= HFI_PANEL_LANE_2;
+ if (num_lanes >= 4)
+ mask |= HFI_PANEL_LANE_3;
+
+ return mask ?: HFI_PANEL_LANE_0;
+}
+
+static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
+ struct dcp_hfi_completion *comp)
+{
+ unsigned long flags;
+
+ if (wait_for_completion_timeout(&comp->done,
+ msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
+ return false;
+
+ /* Serialize removal with response dispatch before the stack object dies. */
+ spin_lock_irqsave(&hfi->resp_drain_lock, flags);
+ dcp_hfi_pending_find(hfi, comp->packet_id);
+ spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
+ return true;
+}
+
+int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
+ const struct drm_display_mode *mode,
+ const struct mipi_dsi_device *dsi,
+ const struct dcp_dsi_hfi_panel_config *cfg)
+{
+ u32 num_lanes;
+ u32 bpp;
+ bool is_cmd_mode;
+ u32 display_type;
+ u32 vsync_source;
+ u32 traffic_mode;
+ u32 phy_type;
+
+ struct dcp_hfi_buf *buf1, *buf2;
+ struct hfi_cmd_buff_hdl hdl1, hdl2;
+ struct hfi_header_info hdr_info1, hdr_info2;
+ struct hfi_packet_info pkt_info;
+ struct dcp_hfi_completion comp1, comp2, comp3;
+ u32 payload[128];
+ u32 *p;
+ u32 packet_id, packet_id2, packet_id3;
+ u64 clk_hz;
+ int ret;
+ bool timeout1, timeout2, timeout3;
+ u32 link_bpp_x16;
+
+ if (!hfi || !mode || !dsi || !dsi->lanes || !cfg)
+ return -EINVAL;
+ if (cfg->num_ctrls > ARRAY_SIZE(cfg->ctrl_indices) ||
+ cfg->num_phys > ARRAY_SIZE(cfg->phy_indices))
+ return -EINVAL;
+
+ display_type = cfg->display_type;
+ vsync_source = cfg->vsync_source;
+ phy_type = cfg->phy_type;
+ num_lanes = dsi->lanes;
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0)
+ return -EINVAL;
+ is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
+ traffic_mode = dcp_dsi_traffic_mode(dsi);
+ link_bpp_x16 = bpp << 4;
+
+ buf1 = dcp_hfi_buf_alloc(hfi);
+ if (!buf1) {
+ dev_err(hfi->dev, "HFI PANEL_INIT: TX buffer exhausted\n");
+ return -ENOMEM;
+ }
+
+ hdl1.cmd_buffer = buf1->data;
+ hdl1.size = DCP_HFI_BUF_SIZE;
+
+ hdr_info1.num_packets = 0;
+ hdr_info1.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+ hdr_info1.object_id = intf_idx;
+ hdr_info1.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl1, &hdr_info1);
+ if (ret) {
+ dev_err(hfi->dev, "PANEL_INIT: hfi_create_header (A) failed: %d\n",
+ ret);
+ goto err_free_buf1;
+ }
+
+ {
+ u32 num_props = 0;
+
+ p = payload + 1;
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_TIMING_MODE_COUNT, 1);
+
+ /* DCS TX buffer is described by both DVA and IOVA properties. */
+ if (cfg->dcs_tx_iova && cfg->dcs_tx_dva &&
+ !(cfg->dcs_tx_iova & 0x3ff) && !(cfg->dcs_tx_dva & 0x3ff)) {
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_DVA,
+ lower_32_bits(cfg->dcs_tx_dva),
+ upper_32_bits(cfg->dcs_tx_dva),
+ cfg->dcs_tx_buf_size, 0, 0);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_IOVA,
+ lower_32_bits(cfg->dcs_tx_iova),
+ upper_32_bits(cfg->dcs_tx_iova),
+ cfg->dcs_tx_buf_size, 0, 0);
+ }
+ payload[0] = num_props;
+ }
+
+ packet_id = dcp_hfi_next_packet_id(hfi);
+ pkt_info.cmd = HFI_COMMAND_PANEL_INIT_PANEL_CAPS;
+ pkt_info.id = intf_idx & 0xFFFF;
+ pkt_info.flags = HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE;
+ pkt_info.packet_id = packet_id;
+ pkt_info.payload_type = HFI_PAYLOAD_U32_ARRAY;
+ pkt_info.payload_size = (u32)((p - payload) * sizeof(u32));
+ pkt_info.payload_ptr = payload;
+
+ ret = hfi_create_full_packet(&hdl1, &pkt_info);
+ if (ret)
+ goto err_free_buf1;
+
+ dev_dbg(hfi->dev,
+ "intf%u: DCP mode %dx%d@%dHz clk=%ukHz ht=%u hs=%u/%u vt=%u vs=%u/%u f=0x%x\n",
+ intf_idx, mode->hdisplay, mode->vdisplay,
+ drm_mode_vrefresh(mode), mode->clock,
+ mode->htotal, mode->hsync_start, mode->hsync_end,
+ mode->vtotal, mode->vsync_start, mode->vsync_end,
+ mode->flags);
+
+ {
+ u32 te_dcs = cfg->te_dcs_command;
+ u32 dma_trig = cfg->dma_trigger;
+ u32 str_trig = cfg->stream_trigger;
+ u32 wr_start = cfg->wr_mem_start;
+ u32 wr_cont = cfg->wr_mem_continue;
+ u32 lane_map = cfg->lane_map;
+ u32 color_ord = cfg->color_order;
+ u32 vc_id = dsi->channel;
+ u32 esd_mode = cfg->esd_mode;
+ u32 dma_win = cfg->dma_sched_window;
+ bool lp11 = cfg->lp11_init;
+ bool cphy = cfg->cphy_mode;
+ bool ulps = cfg->ulps_supported;
+ bool tx_eot = !(dsi->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET);
+ bool bllp_eof = cfg->bllp_eof_power_mode;
+ bool bllp_pwr = cfg->bllp_power_mode;
+ u32 num_ctrls = cfg->num_ctrls;
+ u32 num_phys = cfg->num_phys;
+ u32 num_props = 0;
+
+ p = payload + 1;
+
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_PHYSICAL_TYPE, phy_type);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_BPP, dcp_bpp_to_hfi(bpp));
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_LANES_STATE,
+ dcp_lanes_to_hfi(num_lanes));
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_OPERATING_MODE,
+ is_cmd_mode ? HFI_PANEL_CMD_MODE_8_BIT :
+ HFI_PANEL_VIDEO_MODE_8_BIT);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_DISPLAY_TYPE, display_type);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_VSYNC_SOURCE, vsync_source);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_TRAFFIC_MODE, traffic_mode);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_TE_DCS_COMMAND, te_dcs);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_DMA_TRIGGER, dma_trig);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_STREAM_TRIGGER, str_trig);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_WR_MEM_START, wr_start);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_WR_MEM_CONTINUE, wr_cont);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_LP11_INIT, lp11 ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_CPHY_MODE, cphy ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_ULPS_SUPPORTED,
+ ulps ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_TX_EOT_APPEND,
+ tx_eot ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_BLLP_EOF_POWER_MODE,
+ bllp_eof ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_BLLP_POWER_MODE,
+ bllp_pwr ? HFI_TRUE : HFI_FALSE);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_COLOR_ORDER, color_ord);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_VIRTUAL_CHANNEL_ID, vc_id);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_LANE_MAP, lane_map);
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_ESD_CONFIG,
+ 10 * sizeof(u32), esd_mode,
+ 0, 0, 0, 0, 0, 0, 0, 0);
+
+ num_props++;
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_CTRL_NUM, 0, 1 + num_ctrls);
+ *p++ = num_ctrls;
+ memcpy(p, cfg->ctrl_indices, num_ctrls * sizeof(*p));
+ p += num_ctrls;
+
+ num_props++;
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_PHY_NUM, 0, 1 + num_phys);
+ *p++ = num_phys;
+ memcpy(p, cfg->phy_indices, num_phys * sizeof(*p));
+ p += num_phys;
+
+ num_props++;
+ p += HFI_PACK(p, HFI_PROPERTY_PANEL_DMA_SCHEDULE_WINDOW, dma_win);
+ payload[0] = num_props;
+ }
+
+ packet_id3 = dcp_hfi_next_packet_id(hfi);
+ pkt_info.cmd = HFI_COMMAND_PANEL_INIT_GENERIC_CAPS;
+ pkt_info.flags = HFI_TX_FLAGS_NON_DISCARDABLE | HFI_TX_FLAGS_RESPONSE_REQUIRED;
+ pkt_info.packet_id = packet_id3;
+ pkt_info.payload_size = (u32)((p - payload) * sizeof(u32));
+ pkt_info.payload_ptr = payload;
+
+ ret = hfi_create_full_packet(&hdl1, &pkt_info);
+ if (ret)
+ goto err_free_buf1;
+
+ buf2 = dcp_hfi_buf_alloc(hfi);
+ if (!buf2) {
+ dev_err(hfi->dev, "HFI PANEL_INIT: TX buffer exhausted (B)\n");
+ ret = -ENOMEM;
+ goto err_free_buf1;
+ }
+
+ hdl2.cmd_buffer = buf2->data;
+ hdl2.size = DCP_HFI_BUF_SIZE;
+
+ hdr_info2.num_packets = 0;
+ hdr_info2.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+ hdr_info2.object_id = intf_idx;
+ hdr_info2.header_id = dcp_hfi_next_packet_id(hfi);
+
+ ret = hfi_create_header(&hdl2, &hdr_info2);
+ if (ret) {
+ dev_err(hfi->dev, "PANEL_INIT: hfi_create_header (B) failed: %d\n",
+ ret);
+ goto err_free_both;
+ }
+
+ {
+ u32 num_props = 7; /* timing properties; DPHY and DSC are optional */
+
+ if (cfg->dsc_enabled)
+ num_props++;
+ if (cfg->dsc_enabled && cfg->dsc_rc_override)
+ num_props++;
+ if (cfg->num_phy_timings)
+ num_props++;
+ p = payload;
+ *p++ = num_props;
+ }
+ p = payload + 1;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_INDEX, 0, 1);
+ *p++ = 0;
+
+ /* PANEL_CLOCKRATE is the panel-selected DSI bit clock per lane. */
+ if (cfg->dsc_enabled && cfg->dsc_bits_per_pixel)
+ link_bpp_x16 = cfg->dsc_bits_per_pixel;
+ clk_hz = dsi->hs_rate ? dsi->hs_rate :
+ div_u64((u64)mode->clock * 1000 * link_bpp_x16,
+ num_lanes * 16);
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_CLOCKRATE, 0, 2);
+ *p++ = lower_32_bits(clk_hz);
+ *p++ = upper_32_bits(clk_hz);
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_FRAMERATE, 0, 1);
+ *p++ = drm_mode_vrefresh(mode);
+
+ /* Firmware expects numerator/denominator timing jitter per mode. */
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_JITTER, 0, 2);
+ *p++ = cfg->jitter_numer;
+ *p++ = cfg->jitter_denom;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_RESOLUTION_DATA, 0, 14);
+ *p++ = mode->hdisplay; /* active_width */
+ *p++ = mode->vdisplay; /* active_height */
+ *p++ = mode->hsync_start - mode->hdisplay; /* h_front_porch */
+ *p++ = mode->htotal - mode->hsync_end; /* h_back_porch */
+ *p++ = 0; /* h_left_border */
+ *p++ = 0; /* h_right_border */
+ *p++ = mode->hsync_end - mode->hsync_start; /* h_pulse_width */
+ *p++ = mode->hskew; /* h_sync_skew */
+ *p++ = 0; /* h_sync_pulse (TODO: confirm) */
+ *p++ = mode->vsync_start - mode->vdisplay; /* v_front_porch */
+ *p++ = mode->vtotal - mode->vsync_end; /* v_back_porch */
+ *p++ = 0; /* v_top_border */
+ *p++ = 0; /* v_bottom_border */
+ *p++ = mode->vsync_end - mode->vsync_start; /* v_pulse_width */
+
+ /* Topology fields follow the firmware panel format. */
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_DISPLAY_TOPOLOGY, 0, 4);
+ *p++ = 1;
+ *p++ = cfg->topology_mixer_count;
+ *p++ = cfg->topology_enc_count;
+ *p++ = cfg->topology_intf_count;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_DEFAULT_TOPOLOGY_INDEX, 0, 1);
+ *p++ = 0;
+
+ if (cfg->dsc_enabled) {
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_COMPRESSION_DATA, 0, 12);
+ *p++ = HFI_PANEL_COMPRESSION_DSC;
+ *p++ = (cfg->dsc_version_major << 4) | cfg->dsc_version_minor;
+ *p++ = cfg->dsc_scr_version;
+ *p++ = cfg->dsc_slice_height;
+ *p++ = cfg->dsc_slice_width;
+ *p++ = cfg->dsc_slices_per_pkt;
+ *p++ = cfg->dsc_bits_per_component;
+ *p++ = cfg->dsc_pps_delay_ms;
+ *p++ = cfg->dsc_bits_per_pixel;
+ *p++ = cfg->dsc_chroma_format;
+ *p++ = cfg->dsc_color_space;
+ *p++ = cfg->dsc_block_prediction_enable;
+ }
+
+ if (cfg->dsc_enabled && cfg->dsc_rc_override) {
+ struct hfi_panel_compression_rc_override rc_override = {};
+
+ memcpy(rc_override.min_qp, cfg->dsc_rc_min_qp,
+ sizeof(rc_override.min_qp));
+ memcpy(rc_override.max_qp, cfg->dsc_rc_max_qp,
+ sizeof(rc_override.max_qp));
+ memcpy(rc_override.offsets, cfg->dsc_rc_bpg_offsets,
+ sizeof(rc_override.offsets));
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_COMPRESSION_RC_OVERRIDE, 0,
+ sizeof(rc_override) / sizeof(u32));
+ memcpy(p, &rc_override, sizeof(rc_override));
+ p += sizeof(rc_override) / sizeof(u32);
+ }
+
+ /* DCP computes PHY timings unless an explicit override is supplied. */
+ if (cfg->num_phy_timings) {
+ u32 n = min_t(u32, cfg->num_phy_timings, DCP_DSI_MAX_PHY_TIMINGS);
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_PANEL_DPHY_TIMINGS, 0, n + 1);
+ *p++ = n;
+ memcpy(p, cfg->phy_timings, n * sizeof(*p));
+ p += n;
+ }
+
+ packet_id2 = dcp_hfi_next_packet_id(hfi);
+ pkt_info.cmd = HFI_COMMAND_PANEL_INIT_TIMING_MODE_CAPS;
+ pkt_info.flags = HFI_TX_FLAGS_NON_DISCARDABLE | HFI_TX_FLAGS_RESPONSE_REQUIRED;
+ pkt_info.packet_id = packet_id2;
+ pkt_info.payload_size = (u32)((p - payload) * sizeof(u32));
+ pkt_info.payload_ptr = payload;
+
+ ret = hfi_create_full_packet(&hdl2, &pkt_info);
+ if (ret)
+ goto err_free_both;
+
+ /*
+ * Header A carries 2 packets (PANEL_CAPS, GENERIC_CAPS); firmware
+ * ACKs each individually by its own packet_id. comp1 is registered
+ * internally by dcp_hfi_send_cmd_async() below; comp3 must be
+ * pre-registered here or its response is dropped as "no pending
+ * completion". Header B carries only TIMING_MODE_CAPS (the topology
+ * property that drives DCP's MDP resource reservation) — comp2 is
+ * registered internally by its own dcp_hfi_send_cmd_async() call.
+ */
+ init_completion(&comp1.done);
+ comp1.resp_data = NULL;
+ comp1.resp_len = 0;
+ comp1.status = 0;
+ comp1.packet_id = packet_id;
+
+ init_completion(&comp2.done);
+ comp2.resp_data = NULL;
+ comp2.resp_len = 0;
+ comp2.status = 0;
+ comp2.packet_id = packet_id2;
+
+ init_completion(&comp3.done);
+ comp3.resp_data = NULL;
+ comp3.resp_len = 0;
+ comp3.status = 0;
+ comp3.packet_id = packet_id3;
+
+ ret = dcp_hfi_pending_add(hfi, &comp3);
+ if (ret) {
+ dev_err(hfi->dev,
+ "intf%u: PANEL_INIT: failed to register GENERIC_CAPS completion: %d\n",
+ intf_idx, ret);
+ goto err_free_both;
+ }
+
+ ret = dcp_hfi_send_cmd_async(hfi, buf1,
+ hfi_get_header_size(&hdl1), &comp1);
+ if (ret) {
+ dev_err(hfi->dev,
+ "intf%u: PANEL_INIT caps/generic (header A) failed\n",
+ intf_idx);
+ dcp_hfi_pending_find(hfi, packet_id3);
+ dcp_hfi_buf_free(hfi, buf2);
+ return ret;
+ }
+
+ /* Stack completions must be retired before returning after submission. */
+ ret = dcp_hfi_send_cmd_async(hfi, buf2,
+ hfi_get_header_size(&hdl2), &comp2);
+ if (ret) {
+ dev_err(hfi->dev,
+ "intf%u: HFI_COMMAND_PANEL_INIT_TIMING_MODE_CAPS (header B) failed\n",
+ intf_idx);
+ comp2.packet_id = 0; /* never registered/in-flight, nothing to wait on */
+ }
+
+ timeout1 = dcp_hfi_panel_wait(hfi, &comp1);
+ timeout3 = dcp_hfi_panel_wait(hfi, &comp3);
+ if (comp2.packet_id)
+ timeout2 = dcp_hfi_panel_wait(hfi, &comp2);
+ else
+ timeout2 = false;
+
+ if (timeout1 || timeout2 || timeout3) {
+ dev_err(hfi->dev,
+ "intf%u: HFI PANEL_INIT timed out (caps=%d timing_mode=%d generic=%d)\n",
+ intf_idx, !timeout1, !timeout2, !timeout3);
+ return -ETIMEDOUT;
+ }
+
+ if (ret)
+ return ret; /* header B send_cmd_async failure, comp1/comp3 now retired */
+
+ if (comp1.status || comp2.status || comp3.status)
+ dev_err(hfi->dev,
+ "intf%u: PANEL_INIT failed: caps=%d topology=%d generic=%d\n",
+ intf_idx, comp1.status, comp2.status, comp3.status);
+
+ return comp1.status ? comp1.status :
+ comp2.status ? comp2.status : comp3.status;
+
+err_free_both:
+ dcp_hfi_buf_free(hfi, buf2);
+err_free_buf1:
+ dcp_hfi_buf_free(hfi, buf1);
+ return ret;
+}
+
+ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
+ const struct mipi_dsi_msg *msg,
+ void *tx_buf, dma_addr_t tx_iova,
+ void *rx_buf, dma_addr_t rx_iova,
+ u32 post_wait_ms, u32 ctrl_idx)
+{
+ struct hfi_dsi_cmd_desc cmd_desc = {};
+ static atomic_t seq_no = ATOMIC_INIT(0);
+ int ret;
+
+ if (!msg || !msg->tx_buf || !msg->tx_len)
+ return -EINVAL;
+
+ if (msg->rx_len && (!msg->rx_buf || !rx_buf))
+ return -EINVAL;
+
+ if (msg->tx_len > DCP_DSI_DCS_TX_BUF_SIZE) {
+ dev_err(hfi->dev, "DCS TX too large: %zu > %u\n",
+ msg->tx_len, DCP_DSI_DCS_TX_BUF_SIZE);
+ return -EINVAL;
+ }
+
+ memcpy(tx_buf, msg->tx_buf, msg->tx_len);
+
+ cmd_desc.size = sizeof(cmd_desc);
+ cmd_desc.channel = msg->channel;
+ cmd_desc.type = msg->type;
+ cmd_desc.flags = msg->flags;
+ cmd_desc.tx_len = msg->tx_len;
+ cmd_desc.tx_buff_addr_lsb = lower_32_bits(tx_iova);
+ cmd_desc.tx_buff_addr_msb = upper_32_bits(tx_iova);
+ cmd_desc.ctrl_idx = ctrl_idx;
+ cmd_desc.ctrl_flags = DCP_DSI_CTRL_CMD_FETCH_MEMORY |
+ DCP_DSI_CTRL_CMD_LAST_COMMAND;
+ if (msg->rx_len)
+ cmd_desc.ctrl_flags |= DCP_DSI_CTRL_CMD_READ;
+ cmd_desc.last_command = 1;
+ cmd_desc.post_wait_ms = post_wait_ms;
+ cmd_desc.seq_no = atomic_inc_return(&seq_no);
+
+ if (msg->rx_len > 0) {
+ if (msg->rx_len > DCP_DSI_DCS_RX_BUF_SIZE)
+ return -EINVAL;
+ memset(rx_buf, 0, msg->rx_len);
+ cmd_desc.rx_len = msg->rx_len;
+ cmd_desc.rx_buff_addr_lsb = lower_32_bits(rx_iova);
+ cmd_desc.rx_buff_addr_msb = upper_32_bits(rx_iova);
+ }
+
+ ret = dcp_hfi_send_sync(hfi,
+ HFI_COMMAND_DISPLAY_TRANSFER_DCS_CMD,
+ HFI_CMD_BUFF_DISPLAY,
+ session_id,
+ HFI_PAYLOAD_U32_ARRAY,
+ &cmd_desc, sizeof(cmd_desc),
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ DCP_HFI_TIMEOUT_DISPLAY_DCS_MS);
+ if (ret) {
+ dev_err(hfi->dev,
+ "HFI DCS_TRANSFER type=0x%x len=%zu failed: %d\n",
+ msg->type, msg->tx_len, ret);
+ return ret;
+ }
+
+ if (msg->rx_len > 0)
+ memcpy(msg->rx_buf, rx_buf, msg->rx_len);
+
+ return msg->tx_len;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (9 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:49 ` Dmitry Baryshkov
2026-10-26 11:33 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
` (12 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Create the DRM CRTC objects and shared state needed to expose the
firmware-owned DCP pipeline through MSM DRM. Without CRTC state,
DRM cannot track firmware sessions, aggregate bandwidth, or validate
the display configuration.
Implement CRTC objects, display lookup and session state, CRTC
bandwidth aggregation, and the shared plane state used by validation
and plane programming.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 2 +
drivers/gpu/drm/msm/dcp/dcp_crtc.c | 725 ++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_crtc.h | 104 +++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 21 +
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 54 +++
drivers/gpu/drm/msm/dcp/dcp_plane_state.h | 37 ++
6 files changed, 943 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 77c268aabca6..80341ce07d53 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -72,6 +72,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
disp/mdp5/mdp5_smp.o \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
+ dcp/dcp_crtc.o \
+ dcp/dcp_dpu_kms.o \
dcp/dcp_hfi_core.o \
dcp/dcp_hfi_crtc.o \
dcp/dcp_hfi_dsi.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
new file mode 100644
index 000000000000..ec4f74c2920f
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
@@ -0,0 +1,725 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP CRTC implementation.
+ */
+
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_rect.h>
+#include <drm/drm_vblank.h>
+
+#include <drm/drm_managed.h>
+
+#include "msm_drv.h"
+
+#include "dcp_crtc.h"
+#include "dcp_drv.h"
+#include "dcp_hfi.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_plane_state.h"
+
+#include "hfi/hfi_defs_display.h"
+#include "hfi/hfi_properties_display.h"
+
+#define DCP_FRAME_DONE_TIMEOUT_MS 500
+
+static struct dcp_dpu_kms *dcp_crtc_get_kms(struct drm_crtc *crtc)
+{
+ struct msm_drm_private *priv = crtc->dev->dev_private;
+
+ return to_dcp_dpu_kms(priv->kms);
+}
+
+static struct drm_crtc_state *
+dcp_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
+{
+ struct dcp_crtc_state *cstate;
+
+ if (WARN_ON(!crtc->state))
+ return NULL;
+
+ cstate = kzalloc_obj(*cstate, GFP_KERNEL);
+ if (!cstate)
+ return NULL;
+
+ __drm_atomic_helper_crtc_duplicate_state(crtc, &cstate->base);
+
+ cstate->topology_mixer_count =
+ to_dcp_crtc_state(crtc->state)->topology_mixer_count;
+ cstate->topology_bits_per_component =
+ to_dcp_crtc_state(crtc->state)->topology_bits_per_component;
+ memcpy(cstate->mixer_roi, to_dcp_crtc_state(crtc->state)->mixer_roi,
+ sizeof(cstate->mixer_roi));
+ cstate->update_roi = to_dcp_crtc_state(crtc->state)->update_roi;
+ return &cstate->base;
+}
+
+static void dcp_crtc_atomic_destroy_state(struct drm_crtc *crtc,
+ struct drm_crtc_state *state)
+{
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
+
+ __drm_atomic_helper_crtc_destroy_state(state);
+ kfree(cstate);
+}
+
+static struct drm_crtc_state *
+dcp_crtc_atomic_create_state(struct drm_crtc *crtc)
+{
+ struct dcp_crtc_state *cstate;
+
+ cstate = kzalloc_obj(*cstate, GFP_KERNEL);
+ if (!cstate)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_crtc_state_init(&cstate->base, crtc);
+ cstate->topology_mixer_count = 1;
+ cstate->topology_bits_per_component = 8;
+ return &cstate->base;
+}
+
+static void dcp_crtc_aggregate_bw(struct drm_crtc_state *crtc_state,
+ bool enable)
+{
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
+ struct drm_plane *plane;
+ const struct drm_plane_state *pstate;
+
+ cstate->dram_ib = 0;
+ cstate->dram_ab = 0;
+ cstate->llcc_ib = 0;
+ cstate->llcc_ab = 0;
+ cstate->core_clk = 0;
+
+ if (!enable)
+ return;
+
+ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ if (!pstate->visible || !pstate->fb)
+ continue;
+
+ const struct dcp_plane_state *dcp_pstate = to_dcp_plane_state(pstate);
+
+ cstate->dram_ib = max(cstate->dram_ib, dcp_pstate->fetch_bw);
+ cstate->dram_ab += dcp_pstate->fetch_bw;
+ cstate->llcc_ib = max(cstate->llcc_ib, dcp_pstate->fetch_bw);
+ cstate->llcc_ab += dcp_pstate->fetch_bw;
+ cstate->core_clk = max(cstate->core_clk, dcp_pstate->clk);
+ }
+}
+
+static enum drm_mode_status
+dcp_crtc_mode_valid(struct drm_crtc *crtc,
+ const struct drm_display_mode *mode)
+{
+ struct dcp_dpu_kms *kms = dcp_crtc_get_kms(crtc);
+ u32 max_width, max_height;
+
+ dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
+ &max_width, &max_height);
+
+ return drm_mode_validate_size(mode, max_width, max_height);
+}
+
+static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ u32 mixer_count;
+ u32 session_id;
+ u32 i;
+ int ret;
+
+ if (!crtc->state->active)
+ return;
+ if (!dcp_kms->commit_pm_active) {
+ cstate->hfi_error = -EIO;
+ return;
+ }
+
+ if (!dcp_crtc_get_session(crtc, &session_id)) {
+ cstate->hfi_error = -ENODEV;
+ return;
+ }
+
+ if (cstate->core_clk > 0 || cstate->dram_ib > 0) {
+ ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id,
+ cstate->dram_ib, cstate->dram_ab,
+ cstate->llcc_ib, cstate->llcc_ab,
+ cstate->core_clk);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+ }
+
+ mixer_count = cstate->topology_mixer_count;
+ if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
+ cstate->hfi_error = -EINVAL;
+ return;
+ }
+ for (i = 0; i < mixer_count; i++) {
+ struct dcp_mixer_roi *roi = &cstate->mixer_roi[i];
+
+ if (!roi->w || !roi->h) {
+ drm_err(crtc->dev, "CRTC%u: invalid zero-sized LM%u ROI\n",
+ dcp_crtc->display_id, i);
+ cstate->hfi_error = -EINVAL;
+ return;
+ }
+
+ ret = dcp_hfi_crtc_set_resource(dcp_kms->hfi, session_id,
+ i, roi->w, roi->h, roi->x, roi->y);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+ }
+
+ ret = dcp_hfi_crtc_set_dest_roi(dcp_kms->hfi, session_id,
+ cstate->update_roi.x, cstate->update_roi.y,
+ cstate->update_roi.w, cstate->update_roi.h);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+}
+
+static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ unsigned long flags;
+ u32 session_id;
+ int ret;
+
+ if (!crtc->state->enable)
+ return;
+ drm_dbg_driver(crtc->dev, "DCP_FLUSH CRTC%u mask=0x%x err=%d\n",
+ dcp_crtc->display_id, crtc->state->plane_mask,
+ cstate->hfi_error);
+
+ if (!dcp_crtc_get_session(crtc, &session_id)) {
+ drm_dbg_driver(crtc->dev,
+ "CRTC%u: atomic flush without HFI session\n",
+ dcp_crtc->display_id);
+ goto flush_done;
+ }
+
+ if (cstate->hfi_error)
+ goto flush_done;
+
+ ret = dcp_hfi_crtc_set_scan_sequence_id(dcp_kms->hfi, session_id,
+ ++dcp_crtc->commit_seq_id);
+ if (ret)
+ goto flush_error;
+
+ ret = dcp_hfi_crtc_set_autorefresh(dcp_kms->hfi,
+ session_id, true, 1);
+ if (ret)
+ goto flush_error;
+
+ goto flush_done;
+
+flush_error:
+ cstate->hfi_error = ret;
+
+flush_done:
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ dcp_crtc->event = crtc->state->event;
+ crtc->state->event = NULL;
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+
+ if (!crtc)
+ return;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ dcp_crtc->session_id = session_id;
+ dcp_crtc->session_valid = true;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+}
+
+void dcp_crtc_clear_session(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+
+ if (!crtc)
+ return;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ dcp_crtc->session_valid = false;
+ dcp_crtc->session_id = 0;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+}
+
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+ bool valid;
+
+ if (!crtc)
+ return false;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ valid = dcp_crtc->session_valid;
+ if (valid && session_id)
+ *session_id = dcp_crtc->session_id;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+
+ return valid;
+}
+
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+ u32 mixer_count,
+ u32 bits_per_component)
+{
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
+
+ cstate->topology_mixer_count = mixer_count;
+ cstate->topology_bits_per_component = bits_per_component;
+}
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (cstate->hfi_error) {
+ drm_err(crtc->dev,
+ "DCP_TRIGGER CRTC%u suppressed after HFI error %d\n",
+ dcp_crtc->display_id, cstate->hfi_error);
+ return;
+ }
+
+ if (!dcp_crtc_get_session(crtc, &session_id)) {
+ drm_warn(crtc->dev,
+ "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
+ dcp_crtc->display_id);
+ return;
+ }
+
+ /* MSM serializes commits, so only one frame completion may be pending. */
+ if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
+ drm_err(crtc->dev,
+ "CRTC%u: frame completion already pending\n",
+ dcp_crtc->display_id);
+ cstate->hfi_error = -EBUSY;
+ return;
+ }
+ dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
+
+ ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
+ if (ret) {
+ drm_err(crtc->dev,
+ "HFI commit failed for CRTC%u (session=%u): %d\n",
+ dcp_crtc->display_id, session_id, ret);
+ atomic_dec(&dcp_crtc->pending_kickoff_cnt);
+ wake_up(&dcp_crtc->frame_done_wq);
+ }
+}
+
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ unsigned long timeout = msecs_to_jiffies(DCP_FRAME_DONE_TIMEOUT_MS);
+
+ if (!atomic_read(&dcp_crtc->pending_kickoff_cnt))
+ return;
+
+ if (!wait_event_timeout(dcp_crtc->frame_done_wq,
+ atomic_read(&dcp_crtc->pending_kickoff_cnt) == 0,
+ timeout)) {
+ drm_err(crtc->dev,
+ "frame done timeout for CRTC%u\n",
+ dcp_crtc->display_id);
+ atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+ }
+}
+
+void dcp_crtc_complete_commit(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ unsigned long flags;
+
+ if (!crtc->state->active)
+ dcp_crtc_aggregate_bw(crtc->state, false);
+
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ if (dcp_crtc->event) {
+ drm_crtc_send_vblank_event(crtc, dcp_crtc->event);
+ dcp_crtc->event = NULL;
+ }
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ u32 session_id;
+ int ret;
+
+ if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))
+ return -ENODEV;
+
+ if (enable)
+ ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
+ session_id, HFI_EVENT_VSYNC);
+ else
+ ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
+ session_id, HFI_EVENT_VSYNC);
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(dcp_crtc->vblank_enabled, enable);
+ return 0;
+}
+
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ if (!READ_ONCE(dcp_crtc->vblank_enabled))
+ return;
+
+ if (ktime_compare(dcp_crtc->vblank_cb_time, ktime_set(0, 0)) == 0)
+ dcp_crtc->vblank_cb_time = ktime_get();
+ else
+ dcp_crtc->vblank_cb_count++;
+ drm_crtc_handle_vblank(crtc);
+}
+
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0);
+ wake_up(&dcp_crtc->frame_done_wq);
+}
+
+static struct drm_crtc *dcp_crtc_find_by_session_id(struct dcp_drv *drv,
+ u32 session_id)
+{
+ struct dcp_dpu_kms *dcp_kms;
+ struct drm_crtc *crtc;
+ u32 active_session_id;
+
+ if (!drv || !drv->kms)
+ return NULL;
+
+ dcp_kms = drv->kms;
+
+ crtc = dcp_dpu_kms_get_crtc(dcp_kms, session_id);
+ if (!crtc || !dcp_crtc_get_session(crtc, &active_session_id) ||
+ active_session_id != session_id)
+ return NULL;
+
+ return crtc;
+}
+
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
+{
+ struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
+ struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc)
+ dcp_crtc_vblank_callback(crtc);
+ else if (dev)
+ drm_err_ratelimited(dev,
+ "unknown CRTC for HFI VSYNC session %u\n",
+ session_id);
+}
+
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id)
+{
+ struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
+ struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc) {
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ /* A single pending commit makes equality valid across sequence wrap. */
+ if (sequence_id != dcp_crtc->pending_seq_id) {
+ drm_warn(crtc->dev,
+ "CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u expected=%u\n",
+ dcp_crtc->display_id, sequence_id,
+ dcp_crtc->pending_seq_id);
+ return;
+ }
+ dcp_crtc_frame_done_callback(crtc);
+ } else if (dev)
+ drm_err_ratelimited(dev,
+ "unknown CRTC for HFI frame-done session %u seq=%u\n",
+ session_id, sequence_id);
+}
+
+static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state,
+ struct dcp_crtc_state *cstate)
+{
+ const struct drm_plane_state *pstate;
+ struct drm_plane *plane;
+ const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ u32 mixer_count = cstate->topology_mixer_count;
+ u32 mixer_width = mode->hdisplay / mixer_count;
+ u32 i;
+
+ if (!mixer_width)
+ return -EINVAL;
+
+ cstate->update_roi.x = 0;
+ cstate->update_roi.y = 0;
+ cstate->update_roi.w = mode->hdisplay;
+ cstate->update_roi.h = mode->vdisplay;
+ for (i = 0; i < mixer_count; i++) {
+ cstate->mixer_roi[i].x = (mode->hdisplay * i) / mixer_count;
+ cstate->mixer_roi[i].y = 0;
+ cstate->mixer_roi[i].w = (mode->hdisplay * (i + 1)) / mixer_count -
+ cstate->mixer_roi[i].x;
+ cstate->mixer_roi[i].h = mode->vdisplay;
+ drm_dbg_driver(crtc->dev,
+ "DCP_CHECK CRTC%u LM%u roi=(%u,%u %ux%u)\n",
+ to_dcp_crtc(crtc)->display_id, i,
+ cstate->mixer_roi[i].x, cstate->mixer_roi[i].y,
+ cstate->mixer_roi[i].w, cstate->mixer_roi[i].h);
+ }
+
+ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ bool fullscreen;
+ bool assigned = false;
+ u32 mixer_idx;
+
+ if (!pstate || !pstate->visible || !pstate->fb)
+ continue;
+
+ for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
+ struct dcp_mixer_roi *mixer = &cstate->mixer_roi[mixer_idx];
+
+ if (pstate->crtc_x >= mixer->x &&
+ pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w) {
+ assigned = true;
+ break;
+ }
+ }
+
+ /* Any plane may cover the complete split display. Plane type is
+ * not a resource-selection mechanism in the universal-plane API.
+ */
+ fullscreen = pstate->crtc_x == 0 && pstate->crtc_y == 0 &&
+ pstate->crtc_w == mode->hdisplay &&
+ pstate->crtc_h == mode->vdisplay;
+
+ if (!assigned && !fullscreen && mixer_count > 1) {
+ /*
+ * Split-pipe/source-split and multirect allocation are
+ * deferred until the atomic pipe resource manager is added.
+ * Reject planes needing multiple mixer rectangles for now.
+ */
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct drm_crtc_state *crtc_state =
+ drm_atomic_get_new_crtc_state(state, crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
+ struct drm_plane *plane;
+ const struct drm_plane_state *pstate;
+ u32 max_width, max_height;
+ u32 mixer_count;
+ int num_planes = 0;
+ int ret;
+
+ drm_dbg_atomic(crtc->dev, "CRTC%u: atomic_check enable=%d active=%d mode=%s\n",
+ crtc->base.id, crtc_state->enable, crtc_state->active,
+ crtc_state->mode.name[0] ? crtc_state->mode.name : "none");
+
+ if (!crtc_state->enable || !crtc_state->active)
+ return 0;
+
+ mixer_count = cstate->topology_mixer_count;
+ if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
+ drm_err(crtc->dev,
+ "CRTC%u: invalid mixer count %u (limit 1..%u)\n",
+ crtc->base.id, mixer_count, DCP_MAX_MIXERS);
+ return -EINVAL;
+ }
+
+ ret = dcp_crtc_setup_mixer_rois(crtc, crtc_state, cstate);
+ if (ret)
+ return ret;
+
+ dcp_mode_limits(&crtc_state->adjusted_mode,
+ dcp_kms->caps->max_width, dcp_kms->caps->max_height,
+ &max_width, &max_height);
+
+ if (crtc_state->adjusted_mode.hdisplay > max_width ||
+ crtc_state->adjusted_mode.vdisplay > max_height) {
+ drm_err(crtc->dev, "CRTC%u: mode %dx%d exceeds DCP caps %dx%d\n",
+ crtc->base.id,
+ crtc_state->adjusted_mode.hdisplay,
+ crtc_state->adjusted_mode.vdisplay,
+ dcp_kms->caps->max_width,
+ dcp_kms->caps->max_height);
+ return -E2BIG;
+ }
+
+ /* Limit visible planes to the firmware blend-stage count. */
+ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ if (!pstate || !pstate->visible)
+ continue;
+ num_planes++;
+ }
+
+ if (num_planes > DCP_MAX_BLEND_STAGES) {
+ drm_err(crtc->dev, "CRTC%u: %d planes exceeds max blend stages %d\n",
+ crtc->base.id, num_planes,
+ DCP_MAX_BLEND_STAGES);
+ return -EINVAL;
+ }
+
+ dcp_crtc_aggregate_bw(crtc_state, true);
+
+ return 0;
+}
+
+static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+ return drm_crtc_vblank_count(crtc);
+}
+
+static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (!dcp_crtc_get_session(crtc, &session_id))
+ return;
+
+ ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+ HFI_MODE_DPMS_ON);
+ if (ret) {
+ drm_err(crtc->dev, "CRTC%u: DPMS ON failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void dcp_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (dcp_crtc_get_session(crtc, &session_id)) {
+ ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id, 0, 0, 0, 0, 0);
+ if (ret) {
+ drm_err(crtc->dev,
+ "CRTC%u: clearing bandwidth vote failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+
+ ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+ HFI_MODE_DPMS_OFF);
+ if (ret) {
+ drm_err(crtc->dev, "CRTC%u: DPMS OFF failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+ }
+
+ drm_crtc_vblank_off(crtc);
+}
+
+static const struct drm_crtc_funcs dcp_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_create_state = dcp_crtc_atomic_create_state,
+ .atomic_duplicate_state = dcp_crtc_atomic_duplicate_state,
+ .atomic_destroy_state = dcp_crtc_atomic_destroy_state,
+ .enable_vblank = msm_crtc_enable_vblank,
+ .disable_vblank = msm_crtc_disable_vblank,
+ .get_vblank_counter = dcp_crtc_get_vblank_counter,
+};
+
+static const struct drm_crtc_helper_funcs dcp_crtc_helper_funcs = {
+ .atomic_check = dcp_crtc_atomic_check,
+ .atomic_enable = dcp_crtc_atomic_enable,
+ .atomic_disable = dcp_crtc_atomic_disable,
+ .atomic_begin = dcp_crtc_atomic_begin,
+ .atomic_flush = dcp_crtc_atomic_flush,
+ .mode_valid = dcp_crtc_mode_valid,
+};
+
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+ struct drm_plane *primary_plane,
+ u32 display_id)
+{
+ struct drm_device *dev = kms->dev;
+ struct dcp_crtc *dcp_crtc;
+ int ret;
+
+ dcp_crtc = drmm_kzalloc(dev, sizeof(*dcp_crtc), GFP_KERNEL);
+ if (!dcp_crtc)
+ return ERR_PTR(-ENOMEM);
+
+ dcp_crtc->display_id = display_id;
+
+ spin_lock_init(&dcp_crtc->spin_lock);
+ init_waitqueue_head(&dcp_crtc->frame_done_wq);
+ atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+
+ ret = drmm_crtc_init_with_planes(dev, &dcp_crtc->base,
+ primary_plane, NULL,
+ &dcp_crtc_funcs,
+ NULL);
+ if (ret) {
+ drm_err(dev, "CRTC%u init failed: %d\n", display_id, ret);
+ return ERR_PTR(ret);
+ }
+
+ drm_crtc_helper_add(&dcp_crtc->base, &dcp_crtc_helper_funcs);
+
+ drm_dbg_driver(dev, "CRTC%u initialized\n", display_id);
+ return &dcp_crtc->base;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.h b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
new file mode 100644
index 000000000000..ae7046e8d97a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP CRTC implementation.
+ */
+
+#ifndef __DCP_CRTC_H__
+#define __DCP_CRTC_H__
+
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_crtc.h>
+
+struct dcp_dpu_kms;
+struct drm_plane;
+
+#define DCP_MAX_MIXERS 6
+#define DCP_MAX_BLEND_STAGES 12
+
+static inline void dcp_mode_limits(const struct drm_display_mode *mode,
+ u32 cap_width, u32 cap_height,
+ u32 *max_width, u32 *max_height)
+{
+ *max_width = cap_width;
+ *max_height = cap_height;
+
+ if (mode->hdisplay < mode->vdisplay && cap_width > cap_height) {
+ *max_width = cap_height;
+ *max_height = cap_width;
+ }
+}
+
+struct dcp_mixer_roi {
+ u32 x;
+ u32 y;
+ u32 w;
+ u32 h;
+};
+
+struct dcp_crtc {
+ struct drm_crtc base;
+ u32 display_id;
+ u32 session_id;
+ bool session_valid;
+
+ struct drm_pending_vblank_event *event;
+ wait_queue_head_t frame_done_wq;
+ atomic_t pending_kickoff_cnt;
+ /* Protects CRTC-local vblank and commit state. */
+ spinlock_t spin_lock;
+ u32 vblank_cb_count;
+ bool vblank_enabled;
+ ktime_t vblank_cb_time;
+ bool enabled;
+ u32 commit_seq_id;
+ u32 pending_seq_id;
+};
+
+struct dcp_crtc_state {
+ struct drm_crtc_state base;
+
+ u32 topology_mixer_count;
+ u32 topology_bits_per_component;
+ struct dcp_mixer_roi mixer_roi[DCP_MAX_MIXERS];
+
+ struct dcp_mixer_roi update_roi;
+ int hfi_error;
+ u64 bw_ctl;
+
+ u64 dram_ib;
+ u64 dram_ab;
+ u64 llcc_ib;
+ u64 llcc_ab;
+ u64 core_clk;
+};
+
+#define to_dcp_crtc(x) container_of(x, struct dcp_crtc, base)
+#define to_dcp_crtc_state(x) container_of(x, struct dcp_crtc_state, base)
+
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+ struct drm_plane *primary_plane,
+ u32 display_id);
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc);
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc);
+void dcp_crtc_complete_commit(struct drm_crtc *crtc);
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable);
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc);
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc);
+
+struct dcp_drv;
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id);
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id);
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id);
+void dcp_crtc_clear_session(struct drm_crtc *crtc);
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id);
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+ u32 mixer_count,
+ u32 bits_per_component);
+
+#endif /* __DCP_CRTC_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
new file mode 100644
index 000000000000..44b1c2200aee
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS driver.
+ */
+
+#include <drm/drm_crtc.h>
+
+#include "dcp_dpu_kms.h"
+
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
+ u32 display_id)
+{
+ unsigned int i;
+
+ for (i = 0; i < dcp_kms->num_crtcs; i++)
+ if (dcp_kms->crtcs[i].display_id == display_id)
+ return dcp_kms->crtcs[i].crtc;
+
+ return NULL;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
new file mode 100644
index 000000000000..f8010fc5d32f
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS state.
+ */
+
+#ifndef __DCP_DPU_KMS_H__
+#define __DCP_DPU_KMS_H__
+
+#include <linux/spinlock.h>
+
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+struct dcp_hfi;
+struct dcp_caps;
+struct dcp_dsi_bridge;
+struct dcp_drv;
+struct drm_crtc;
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000
+#define DCP_DPU_MAX_CRTCS 4
+#define DCP_DPU_MAX_PLANES 12
+
+struct dcp_dpu_kms {
+ struct msm_kms base;
+ struct drm_device *dev;
+ struct device *pm_dev;
+ struct dcp_drv *drv;
+ struct dcp_hfi *hfi;
+ struct dcp_caps *caps;
+ struct device_node *dpus_np;
+ struct device *iommu_dev;
+ bool rpm_enabled;
+ bool commit_pm_active;
+ struct dcp_dsi_bridge *dsi_bridge[2];
+ struct {
+ struct drm_crtc *crtc;
+ u32 display_id;
+ } crtcs[DCP_DPU_MAX_CRTCS];
+ unsigned int num_crtcs;
+};
+
+#define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
+ u32 display_id);
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
+ struct drm_crtc *crtc);
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms);
+extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
+int dcp_kms_init(struct drm_device *ddev);
+
+#endif /* __DCP_DPU_KMS_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane_state.h b/drivers/gpu/drm/msm/dcp/dcp_plane_state.h
new file mode 100644
index 000000000000..aafa93cb69b9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane_state.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state definitions.
+ */
+
+#ifndef __DCP_PLANE_STATE_H__
+#define __DCP_PLANE_STATE_H__
+
+#include <drm/drm_plane.h>
+
+#define DCP_PLANE_PROP_PKT_MAX_SIZE 512
+
+struct dcp_plane_state {
+ struct drm_plane_state base;
+
+ u64 fetch_bw;
+ u64 clk;
+
+ u8 prop_pkt[DCP_PLANE_PROP_PKT_MAX_SIZE];
+ size_t prop_pkt_size;
+ u32 prop_pkt_id;
+
+ bool needs_dirtyfb;
+
+ u32 src_img_w;
+ u32 src_img_h;
+
+ u32 hfi_pipe_id;
+ u32 hfi_format;
+};
+
+#define to_dcp_plane_state(x) \
+ container_of(x, struct dcp_plane_state, base)
+
+#endif /* __DCP_PLANE_STATE_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 12/23] drm/msm/dcp: Add DCP planes
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (10 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 11:57 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
` (11 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Expose DCP overlay planes through the DRM plane API. Userspace needs
normal atomic plane state while the firmware-owned pipeline requires
validated formats, assigned pipes, mapped buffers, bandwidth votes,
and HFI property packets.
Implement plane validation, format and scaling checks, firmware pipe
assignment, framebuffer mapping, bandwidth calculation, atomic state
handling, and HFI updates and disables.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_plane.c | 1056 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_plane.h | 36 ++
3 files changed, 1093 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 80341ce07d53..b425fefda890 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -74,6 +74,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_crtc.o \
dcp/dcp_dpu_kms.o \
+ dcp/dcp_plane.o \
dcp/dcp_hfi_core.o \
dcp/dcp_hfi_crtc.o \
dcp/dcp_hfi_dsi.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.c b/drivers/gpu/drm/msm/dcp/dcp_plane.c
new file mode 100644
index 000000000000..af8aa2162b03
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
@@ -0,0 +1,1056 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state validation and HFI property packet construction.
+ */
+
+#include <linux/math64.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+
+#include "msm_drv.h"
+
+#include "dcp_crtc.h"
+#include "dcp_hfi.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_plane.h"
+
+#define DCP_ZPOS_MAX 255
+#define DCP_MAX_IMG_WIDTH 0x3FFF
+#define DCP_MAX_IMG_HEIGHT 0x3FFF
+#define DCP_SSPP_MAX_PITCH_SIZE 0xFF00
+#define DCP_MAX_UPSCALE_RATIO 20
+#define DCP_MAX_DOWNSCALE_RATIO 4
+
+static const u32 dcp_plane_formats[] = {
+ DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_RGB888, DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
+ DRM_FORMAT_ARGB1555, DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_RGBA5551, DRM_FORMAT_BGRA5551,
+ DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551,
+ DRM_FORMAT_ARGB4444, DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_RGBA4444, DRM_FORMAT_BGRA4444,
+ DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444,
+ DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB2101010,
+ DRM_FORMAT_ABGR2101010, DRM_FORMAT_XBGR2101010,
+ DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+ DRM_FORMAT_P010, DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
+};
+
+static const u64 dcp_format_modifiers[] = {
+ DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static struct dcp_dpu_kms *_dcp_plane_get_kms(struct drm_plane *plane)
+{
+ struct msm_drm_private *priv = plane->dev->dev_private;
+
+ return to_dcp_dpu_kms(priv->kms);
+}
+
+static bool dcp_plane_hfi_pipe_valid(struct dcp_dpu_kms *dcp_kms, u32 pipe_id)
+{
+ const struct dcp_caps *caps;
+ u32 i;
+
+ if (!dcp_kms->caps)
+ return false;
+
+ caps = dcp_kms->caps;
+ for (i = 0; i < caps->num_vig_pipes; i++)
+ if (caps->vig_pipe_ids[i] == pipe_id)
+ return true;
+
+ for (i = 0; i < caps->num_dma_pipes; i++)
+ if (caps->dma_pipe_ids[i] == pipe_id)
+ return true;
+
+ return false;
+}
+
+static bool dcp_plane_pipe_is_vig(const struct dcp_caps *caps, u32 pipe_id)
+{
+ u32 i;
+
+ for (i = 0; i < caps->num_vig_pipes; i++)
+ if (caps->vig_pipe_ids[i] == pipe_id)
+ return true;
+
+ return false;
+}
+
+static bool dcp_plane_pipe_busy(struct drm_atomic_commit *state,
+ struct drm_plane *self, u32 pipe_id)
+{
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct drm_plane *iter_plane;
+ struct drm_plane_state *new_plane_state;
+ int i;
+
+ drm_for_each_plane(plane, state->dev) {
+ if (plane == self)
+ continue;
+
+ plane_state = plane->state;
+ for_each_new_plane_in_state(state, iter_plane, new_plane_state, i) {
+ if (iter_plane == plane) {
+ plane_state = new_plane_state;
+ break;
+ }
+ }
+
+ if (plane_state && plane_state->crtc && plane_state->visible &&
+ to_dcp_plane_state(plane_state)->hfi_pipe_id == pipe_id)
+ return true;
+ }
+
+ return false;
+}
+
+static u32 dcp_plane_assign_pipe(struct dcp_dpu_kms *dcp_kms,
+ struct drm_atomic_commit *state,
+ struct drm_plane *plane,
+ struct dcp_plane_state *pstate,
+ bool yuv, bool scaling)
+{
+ const struct dcp_caps *caps = dcp_kms->caps;
+ u32 i, pipe_id;
+
+ /* YUV scanout requires a VIG pipe with the CSC path. */
+ if (dcp_plane_hfi_pipe_valid(dcp_kms, pstate->hfi_pipe_id) &&
+ (!yuv || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+ (!scaling || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+ !dcp_plane_pipe_busy(state, plane, pstate->hfi_pipe_id))
+ return pstate->hfi_pipe_id;
+
+ /*
+ * R1/virtual pipes are intentionally not allocated as independent
+ * planes yet. They require transactional physical-pipe/R1 ownership
+ * and multirect programming, which will be added with the resource
+ * manager.
+ */
+ for (i = 0; i < caps->num_vig_pipes; i++) {
+ pipe_id = caps->vig_pipe_ids[i];
+ if (!dcp_plane_pipe_busy(state, plane, pipe_id))
+ return pipe_id;
+ }
+
+ if (yuv || scaling)
+ return U32_MAX;
+
+ for (i = 0; i < caps->num_dma_pipes; i++) {
+ pipe_id = caps->dma_pipe_ids[i];
+ if (!dcp_plane_pipe_busy(state, plane, pipe_id))
+ return pipe_id;
+ }
+
+ return U32_MAX;
+}
+
+static const struct {
+ u32 fourcc;
+ u64 modifier;
+ u32 hfi_fmt;
+} dcp_fmt_table[] = {
+ { DRM_FORMAT_ABGR8888, DRM_FORMAT_MOD_QCOM_COMPRESSED, HFI_COLOR_FORMAT_UBWC_RGBA8888 },
+ { DRM_FORMAT_XBGR8888, DRM_FORMAT_MOD_QCOM_COMPRESSED, HFI_COLOR_FORMAT_UBWC_RGBX8888 },
+ { DRM_FORMAT_ABGR2101010, DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ HFI_COLOR_FORMAT_UBWC_RGBA10_10_10_2 },
+ { DRM_FORMAT_XBGR2101010, DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ HFI_COLOR_FORMAT_UBWC_RGBX10_10_10_2 },
+ { DRM_FORMAT_BGR565, DRM_FORMAT_MOD_QCOM_COMPRESSED, HFI_COLOR_FORMAT_UBWC_RGB565 },
+ { DRM_FORMAT_NV12, DRM_FORMAT_MOD_QCOM_COMPRESSED, HFI_COLOR_FORMAT_UBWC_NV12 },
+ { DRM_FORMAT_P010, DRM_FORMAT_MOD_QCOM_COMPRESSED, HFI_COLOR_FORMAT_UBWC_P010 },
+
+ { DRM_FORMAT_RGB565, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGR565 },
+ { DRM_FORMAT_BGR565, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGB565 },
+ { DRM_FORMAT_RGB888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGR888 },
+ { DRM_FORMAT_BGR888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGB888 },
+ { DRM_FORMAT_ARGB8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRA8888 },
+ { DRM_FORMAT_ABGR8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBA8888 },
+ { DRM_FORMAT_RGBA8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ABGR8888 },
+ { DRM_FORMAT_BGRA8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ARGB8888 },
+ { DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRX8888 },
+ { DRM_FORMAT_XBGR8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBX8888 },
+ { DRM_FORMAT_RGBX8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XBGR8888 },
+ { DRM_FORMAT_BGRX8888, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XRGB8888 },
+ { DRM_FORMAT_ARGB2101010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRA10_10_10_2 },
+ { DRM_FORMAT_ABGR2101010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBA10_10_10_2 },
+ { DRM_FORMAT_RGBA1010102, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ABGR2_10_10_10 },
+ { DRM_FORMAT_BGRA1010102, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ARGB2_10_10_10 },
+ { DRM_FORMAT_XRGB2101010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRX10_10_10_2 },
+ { DRM_FORMAT_XBGR2101010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBX10_10_10_2 },
+ { DRM_FORMAT_RGBX1010102, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XBGR2_10_10_10 },
+ { DRM_FORMAT_BGRX1010102, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XRGB2_10_10_10 },
+ { DRM_FORMAT_ARGB1555, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRA5551 },
+ { DRM_FORMAT_ABGR1555, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBA5551 },
+ { DRM_FORMAT_RGBA5551, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ABGR1555 },
+ { DRM_FORMAT_BGRA5551, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ARGB1555 },
+ { DRM_FORMAT_XRGB1555, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRX5551 },
+ { DRM_FORMAT_XBGR1555, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBX5551 },
+ { DRM_FORMAT_RGBX5551, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XBGR1555 },
+ { DRM_FORMAT_BGRX5551, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XRGB1555 },
+ { DRM_FORMAT_ARGB4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRA4444 },
+ { DRM_FORMAT_ABGR4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBA4444 },
+ { DRM_FORMAT_RGBA4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ABGR4444 },
+ { DRM_FORMAT_BGRA4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_ARGB4444 },
+ { DRM_FORMAT_XRGB4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_BGRX4444 },
+ { DRM_FORMAT_XBGR4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_RGBX4444 },
+ { DRM_FORMAT_RGBX4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XBGR4444 },
+ { DRM_FORMAT_BGRX4444, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_XRGB4444 },
+ { DRM_FORMAT_YUV420, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_YU12 },
+ { DRM_FORMAT_YVU420, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_YV12 },
+ { DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_NV12 },
+ { DRM_FORMAT_NV21, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_NV21 },
+ { DRM_FORMAT_P010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_P010 },
+};
+
+static u32 dcp_drm_to_hfi_format(u32 fourcc, u64 modifier)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_fmt_table); i++) {
+ if (dcp_fmt_table[i].fourcc == fourcc &&
+ dcp_fmt_table[i].modifier == modifier)
+ return dcp_fmt_table[i].hfi_fmt;
+ }
+
+ return U32_MAX;
+}
+
+static u32 dcp_drm_to_hfi_rotation(u32 drm_rotation)
+{
+ u32 hfi_rot = 0;
+
+ if (drm_rotation & DRM_MODE_ROTATE_0)
+ hfi_rot |= HFI_DISPLAY_ROTATION_0;
+ if (drm_rotation & DRM_MODE_ROTATE_90)
+ hfi_rot |= HFI_DISPLAY_ROTATION_90;
+ if (drm_rotation & DRM_MODE_ROTATE_180)
+ hfi_rot |= HFI_DISPLAY_ROTATION_180;
+ if (drm_rotation & DRM_MODE_ROTATE_270)
+ hfi_rot |= HFI_DISPLAY_ROTATION_270;
+ if (drm_rotation & DRM_MODE_REFLECT_X)
+ hfi_rot |= HFI_DISPLAY_REFLECT_X;
+ if (drm_rotation & DRM_MODE_REFLECT_Y)
+ hfi_rot |= HFI_DISPLAY_REFLECT_Y;
+
+ if (!hfi_rot)
+ hfi_rot = HFI_DISPLAY_ROTATION_0;
+
+ return hfi_rot;
+}
+
+static u32 dcp_drm_to_hfi_blend_op(u32 drm_blend_mode)
+{
+ switch (drm_blend_mode) {
+ case DRM_MODE_BLEND_PIXEL_NONE: return HFI_BLEND_OP_OPAQUE;
+ case DRM_MODE_BLEND_PREMULTI: return HFI_BLEND_OP_PREMULTIPLIED;
+ case DRM_MODE_BLEND_COVERAGE: return HFI_BLEND_OP_COVERAGE;
+ default: return HFI_BLEND_OP_NOT_DEFINED;
+ }
+}
+
+static u16 dcp_compute_bg_alpha(u32 pixel_blend_mode, u16 fg_alpha)
+{
+ switch (pixel_blend_mode) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ return DRM_BLEND_ALPHA_OPAQUE - fg_alpha;
+ case DRM_MODE_BLEND_PREMULTI:
+ case DRM_MODE_BLEND_COVERAGE:
+ return (fg_alpha != DRM_BLEND_ALPHA_OPAQUE) ? fg_alpha : 0;
+ default:
+ return DRM_BLEND_ALPHA_OPAQUE;
+ }
+}
+
+static u32 dcp_hfi_scale_alpha(const struct drm_plane_state *state, u32 alpha)
+{
+ if (to_dcp_crtc_state(state->crtc->state)->topology_bits_per_component >= 10)
+ return alpha;
+
+ return alpha << 8;
+}
+
+static u64 dcp_plane_calc_bw(u32 bpp,
+ const struct drm_display_mode *mode,
+ const struct drm_rect *src,
+ const struct drm_rect *dst)
+{
+ int src_w = drm_rect_width(src) >> 16;
+ int src_h = drm_rect_height(src) >> 16;
+ int dst_h = drm_rect_height(dst);
+ int fps = drm_mode_vrefresh(mode);
+ int vbp = mode->vtotal - mode->vsync_end;
+ int vpw = mode->vsync_end - mode->vsync_start;
+ int vfp = mode->vsync_start - mode->vdisplay;
+ u32 hw_latency = 25;
+ u64 scale_factor = (src_h > dst_h) ? mult_frac(src_h, 1, dst_h) : 1;
+ u64 plane_bw, prefill_bw;
+
+ plane_bw = (u64)src_w * mode->vtotal * fps * bpp * scale_factor;
+ prefill_bw = plane_bw * hw_latency;
+
+ if ((vbp + vpw) > hw_latency)
+ do_div(prefill_bw, (vbp + vpw));
+ else if ((vbp + vpw + vfp) < hw_latency)
+ do_div(prefill_bw, (vbp + vpw + vfp));
+ else
+ do_div(prefill_bw, hw_latency);
+
+ /* HFI DRAM/LLCC votes are expressed in Kbps. */
+ return div_u64(max(plane_bw, prefill_bw), 1000);
+}
+
+static u64 dcp_plane_calc_clk(const struct drm_display_mode *mode,
+ const struct drm_rect *src,
+ const struct drm_rect *dst)
+{
+ int src_h = drm_rect_height(src) >> 16;
+ int dst_w = drm_rect_width(dst);
+ int dst_h = drm_rect_height(dst);
+ int fps = drm_mode_vrefresh(mode);
+ u64 clk = (u64)dst_w * mode->vtotal * fps;
+
+ if (src_h > dst_h) {
+ clk *= src_h;
+ do_div(clk, dst_h);
+ }
+ return clk;
+}
+
+void dcp_plane_calc_perf(const struct drm_plane_state *state,
+ const struct drm_display_mode *mode,
+ struct dcp_plane_perf *perf)
+{
+ struct drm_rect src, dst;
+ u32 bpp;
+
+ memset(perf, 0, sizeof(*perf));
+
+ if (!state || !state->visible || !state->fb || !mode || !mode->clock ||
+ !drm_mode_vrefresh(mode))
+ return;
+
+ src = drm_plane_state_src(state);
+ dst = drm_plane_state_dest(state);
+ bpp = drm_format_info_bpp(state->fb->format, 0);
+
+ perf->fetch_bw = dcp_plane_calc_bw(bpp, mode, &src, &dst);
+ perf->clk = dcp_plane_calc_clk(mode, &src, &dst);
+}
+
+static inline u32 *prop_u32(u32 *p, u32 prop_id, u32 lid, u32 val)
+{
+ *p++ = HFI_PACK_KEY(prop_id, 0, 2);
+ *p++ = lid;
+ *p++ = val;
+ return p;
+}
+
+static inline u32 *prop_roi(u32 *p, u32 prop_id, u32 lid,
+ u32 x, u32 y, u32 w, u32 h)
+{
+ *p++ = HFI_PACK_KEY(prop_id, 0, 5);
+ *p++ = lid;
+ *p++ = x; *p++ = y; *p++ = w; *p++ = h;
+ return p;
+}
+
+static inline u32 *prop_attach(u32 *p, u32 lid)
+{
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_ATTACH_LAYER, 0, 1);
+ *p++ = lid;
+ return p;
+}
+
+static inline u32 *prop_src_addr(u32 *p, u32 lid,
+ const dma_addr_t *iova, u32 hfi_fmt)
+{
+ /* The firmware format is struct hfi_plane_buff: all low words,
+ * followed by all high words, then the common descriptor fields.
+ */
+ p += HFI_PACK(p, HFI_PROPERTY_LAYER_SRC_ADDR, lid,
+ lower_32_bits(iova[0]), lower_32_bits(iova[1]),
+ lower_32_bits(iova[2]), lower_32_bits(iova[3]),
+ upper_32_bits(iova[0]), upper_32_bits(iova[1]),
+ upper_32_bits(iova[2]), upper_32_bits(iova[3]),
+ 0, 0, 0, hfi_fmt);
+ return p;
+}
+
+static inline u32 *prop_stride(u32 *p, u32 lid, const u32 *pitches)
+{
+ int i;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_LAYER_STRIDE, 0, 5);
+ *p++ = lid;
+ for (i = 0; i < 4; i++)
+ *p++ = pitches[i];
+ return p;
+}
+
+static inline u32 *prop_cache_attr(u32 *p, u32 lid, u32 state, u32 op)
+{
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_LAYER_CACHE_ATTR, 0, 3);
+ *p++ = lid;
+ *p++ = state;
+ *p++ = op;
+ return p;
+}
+
+static int dcp_plane_build_one_hfi_props_packet(struct dcp_plane_state *pstate,
+ const struct drm_plane_state *state,
+ u32 session_id, u32 packet_id,
+ u8 *packet)
+{
+ struct drm_framebuffer *fb = state->fb;
+ struct hfi_cmd_buff_hdl hdl;
+ struct hfi_header_info hdr_info;
+ struct hfi_packet_info pkt_info;
+ u32 payload[DCP_PLANE_PROP_PKT_MAX_SIZE / sizeof(u32)];
+ u32 *p = payload;
+ u32 *num_props_ptr;
+ u32 num_props = 0;
+ u32 hfi_format;
+ u32 hfi_rotation;
+ u32 hfi_blend_op;
+ u32 pitches[4] = { 0 };
+ dma_addr_t fb_iova[HFI_MAX_PLANES] = { 0 };
+ u32 src_x, src_y, src_w, src_h;
+ u32 dst_x, dst_y, dst_w, dst_h;
+ u32 hfi_alpha, hfi_bg_alpha;
+ u32 lid = pstate->hfi_pipe_id;
+ int i, ret;
+ u32 payload_words;
+
+ hfi_format = pstate->hfi_format;
+
+ hfi_rotation = dcp_drm_to_hfi_rotation(state->rotation);
+ hfi_blend_op = dcp_drm_to_hfi_blend_op(state->pixel_blend_mode);
+
+ src_x = state->src_x >> 16;
+ src_y = state->src_y >> 16;
+ src_w = state->src_w >> 16;
+ src_h = state->src_h >> 16;
+ dst_x = state->crtc_x;
+ dst_y = state->crtc_y;
+ dst_w = state->crtc_w;
+ dst_h = state->crtc_h;
+
+ hfi_alpha = dcp_hfi_scale_alpha(state, state->alpha);
+ hfi_bg_alpha = dcp_hfi_scale_alpha(state,
+ dcp_compute_bg_alpha(state->pixel_blend_mode,
+ state->alpha));
+
+ for (i = 0; i < 4 && i < fb->format->num_planes; i++)
+ pitches[i] = fb->pitches[i];
+
+ for (i = 0; i < HFI_MAX_PLANES && i < fb->format->num_planes; i++)
+ fb_iova[i] = msm_framebuffer_iova(fb, i);
+
+ num_props_ptr = p++;
+
+ p = prop_roi(p, HFI_PROPERTY_LAYER_SRC_ROI, lid,
+ src_x, src_y, src_w, src_h);
+ num_props++;
+
+ p = prop_roi(p, HFI_PROPERTY_LAYER_DEST_ROI, lid,
+ dst_x, dst_y, dst_w, dst_h);
+ num_props++;
+
+ p = prop_attach(p, lid);
+ num_props++;
+
+ p = prop_src_addr(p, lid, fb_iova, hfi_format);
+ num_props++;
+
+ p = prop_stride(p, lid, pitches);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_SRC_FORMAT, lid, hfi_format);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_ROTATION, lid, hfi_rotation);
+ num_props++;
+
+ p = prop_cache_attr(p, lid,
+ HFI_CACHE_STATE_DISABLE,
+ HFI_CACHE_OP_TYPE_NONE);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_ZPOS, lid,
+ state->normalized_zpos);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_ALPHA, lid, hfi_alpha);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_BG_ALPHA, lid, hfi_bg_alpha);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_BLEND_TYPE, lid, hfi_blend_op);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_SRC_IMG_SIZE_W, lid,
+ pstate->src_img_w);
+ num_props++;
+
+ p = prop_u32(p, HFI_PROPERTY_LAYER_SRC_IMG_SIZE_H, lid,
+ pstate->src_img_h);
+ num_props++;
+
+ *num_props_ptr = num_props;
+
+ payload_words = p - payload;
+
+ hdl.cmd_buffer = packet;
+ hdl.size = DCP_PLANE_PROP_PKT_MAX_SIZE;
+
+ hdr_info.num_packets = 0;
+ hdr_info.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
+ hdr_info.object_id = session_id;
+ hdr_info.header_id = packet_id;
+
+ ret = hfi_create_header(&hdl, &hdr_info);
+ if (ret)
+ return ret;
+
+ pkt_info.cmd = HFI_COMMAND_DISPLAY_SET_PROPERTY;
+ pkt_info.id = session_id & 0xFFFF;
+ pkt_info.flags = HFI_TX_FLAGS_NON_DISCARDABLE;
+ pkt_info.packet_id = packet_id;
+ pkt_info.payload_type = HFI_PAYLOAD_U32_ARRAY;
+ pkt_info.payload_size = payload_words * sizeof(u32);
+ pkt_info.payload_ptr = payload;
+
+ ret = hfi_create_full_packet(&hdl, &pkt_info);
+ if (ret) {
+ pstate->prop_pkt_size = 0;
+ return ret;
+ }
+
+ pstate->prop_pkt_size = hfi_get_header_size(&hdl);
+
+ drm_dbg_driver(state->plane->dev,
+ "DCP_COMMIT p=%u pipe=%u src=%ux%u+%ux%u dst=%ux%u+%ux%u fmt=0x%x mod=0x%llx alpha=%u bg_alpha=%u props=%u words=%u size=%zu\n",
+ state->plane->base.id, lid, src_x, src_y, src_w, src_h,
+ dst_x, dst_y, dst_w, dst_h, hfi_format, fb->modifier,
+ hfi_alpha, hfi_bg_alpha, num_props, payload_words,
+ pstate->prop_pkt_size);
+ return 0;
+}
+
+static int dcp_plane_build_hfi_props_packet(struct dcp_plane_state *pstate,
+ const struct drm_plane_state *state,
+ u32 session_id, u32 packet_id)
+{
+ int ret;
+
+ pstate->prop_pkt_size = 0;
+
+ ret = dcp_plane_build_one_hfi_props_packet(pstate, state, session_id,
+ packet_id, pstate->prop_pkt);
+ return ret;
+}
+
+static int dcp_plane_prepare_fb(struct drm_plane *plane,
+ struct drm_plane_state *new_state)
+{
+ struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
+ int ret;
+
+ drm_dbg_driver(plane->dev,
+ "plane%u: prepare_fb fb=%u\n",
+ plane->base.id,
+ new_state->fb ? new_state->fb->base.id : 0);
+
+ if (!new_state->fb)
+ return 0;
+
+ ret = drm_gem_plane_helper_prepare_fb(plane, new_state);
+ if (ret)
+ return ret;
+
+ ret = msm_framebuffer_prepare(new_state->fb, pstate->needs_dirtyfb);
+ if (ret) {
+ drm_err(plane->dev, "plane%u: framebuffer prepare failed: %d\n",
+ plane->base.id, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dcp_plane_cleanup_fb(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct dcp_plane_state *pstate = to_dcp_plane_state(old_state);
+
+ drm_dbg_driver(plane->dev,
+ "plane%u: cleanup_fb fb=%u\n",
+ plane->base.id,
+ old_state->fb ? old_state->fb->base.id : 0);
+
+ if (!old_state->fb)
+ return;
+
+ msm_framebuffer_cleanup(old_state->fb, pstate->needs_dirtyfb);
+}
+
+static int dcp_plane_atomic_check(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_state =
+ drm_atomic_get_new_plane_state(state, plane);
+ struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
+ struct dcp_dpu_kms *dcp_kms = _dcp_plane_get_kms(plane);
+ struct drm_crtc_state *crtc_state;
+ struct drm_rect src;
+ struct drm_rect fb_rect = { 0 };
+ u32 max_width, max_height;
+ int min_scale = DRM_PLANE_NO_SCALING;
+ int max_scale = DRM_PLANE_NO_SCALING;
+ bool scaling;
+ int ret, i;
+ struct dcp_plane_perf perf;
+
+ drm_dbg_driver(plane->dev,
+ "DCP_CHECK plane%u pipe=%u src=(%u,%u %ux%u) dst=(%d,%d %ux%u)\n",
+ plane->base.id, pstate->hfi_pipe_id,
+ new_state->src_x >> 16, new_state->src_y >> 16,
+ new_state->src_w >> 16, new_state->src_h >> 16,
+ new_state->crtc_x, new_state->crtc_y,
+ new_state->crtc_w, new_state->crtc_h);
+
+ pstate->prop_pkt_size = 0;
+ pstate->fetch_bw = 0;
+ pstate->clk = 0;
+
+ if (!new_state->crtc)
+ return 0;
+
+ crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
+ if (IS_ERR(crtc_state)) {
+ ret = PTR_ERR(crtc_state);
+ drm_err(plane->dev, "plane%u: failed to get CRTC state: %d\n",
+ plane->base.id, ret);
+ return ret;
+ }
+
+ if (!new_state->fb)
+ return 0;
+
+ scaling = new_state->src_w != ((u32)new_state->crtc_w << 16) ||
+ new_state->src_h != ((u32)new_state->crtc_h << 16);
+
+ pstate->hfi_pipe_id = dcp_plane_assign_pipe(dcp_kms, state, plane,
+ pstate, new_state->fb->format->is_yuv,
+ scaling);
+ if (pstate->hfi_pipe_id == U32_MAX) {
+ drm_err(plane->dev, "plane%u: no compatible HFI pipe available\n",
+ plane->base.id);
+ return -ENOSPC;
+ }
+
+ if (dcp_plane_pipe_is_vig(dcp_kms->caps, pstate->hfi_pipe_id)) {
+ min_scale = FRAC_16_16(1, DCP_MAX_UPSCALE_RATIO);
+ max_scale = DCP_MAX_DOWNSCALE_RATIO << 16;
+ }
+
+ ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
+ min_scale, max_scale,
+ true, true);
+ if (ret) {
+ drm_err(plane->dev, "plane%u: plane state check failed: %d\n",
+ plane->base.id, ret);
+ return ret;
+ }
+
+ if (!new_state->visible)
+ return 0;
+ if (new_state->crtc_x < 0 || new_state->crtc_y < 0 ||
+ new_state->crtc_w <= 0 || new_state->crtc_h <= 0 ||
+ (u64)new_state->crtc_x + new_state->crtc_w >
+ crtc_state->adjusted_mode.hdisplay ||
+ (u64)new_state->crtc_y + new_state->crtc_h >
+ crtc_state->adjusted_mode.vdisplay) {
+ drm_err(plane->dev,
+ "plane%u: destination out of bounds: (%d,%d %ux%u), mode %ux%u\n",
+ plane->base.id, new_state->crtc_x, new_state->crtc_y,
+ new_state->crtc_w, new_state->crtc_h,
+ crtc_state->adjusted_mode.hdisplay,
+ crtc_state->adjusted_mode.vdisplay);
+ return -EINVAL;
+ }
+
+ if (new_state->normalized_zpos >= DCP_MAX_BLEND_STAGES) {
+ drm_err(plane->dev,
+ "plane%u: zpos %u exceeds blend stages %u\n",
+ plane->base.id, new_state->normalized_zpos,
+ DCP_MAX_BLEND_STAGES);
+ return -EINVAL;
+ }
+
+ dcp_plane_calc_perf(new_state, &crtc_state->adjusted_mode, &perf);
+ pstate->fetch_bw = perf.fetch_bw;
+ pstate->clk = perf.clk;
+
+ pstate->hfi_format = dcp_drm_to_hfi_format(new_state->fb->format->format,
+ new_state->fb->modifier);
+ if (pstate->hfi_format == U32_MAX) {
+ drm_dbg_atomic(plane->dev, "plane%u: unsupported format 0x%x mod 0x%llx\n",
+ plane->base.id,
+ new_state->fb->format->format,
+ new_state->fb->modifier);
+ drm_err(plane->dev,
+ "plane%u: unsupported format 0x%x modifier 0x%llx\n",
+ plane->base.id, new_state->fb->format->format,
+ new_state->fb->modifier);
+ return -EINVAL;
+ }
+
+ fb_rect.x2 = new_state->fb->width;
+ fb_rect.y2 = new_state->fb->height;
+
+ if (drm_rect_width(&fb_rect) > DCP_MAX_IMG_WIDTH ||
+ drm_rect_height(&fb_rect) > DCP_MAX_IMG_HEIGHT) {
+ drm_err(plane->dev,
+ "plane%u: framebuffer too large: %ux%u (limit %ux%u)\n",
+ plane->base.id, new_state->fb->width, new_state->fb->height,
+ DCP_MAX_IMG_WIDTH, DCP_MAX_IMG_HEIGHT);
+ return -E2BIG;
+ }
+
+ dcp_mode_limits(&crtc_state->adjusted_mode,
+ dcp_kms->caps->max_width, dcp_kms->caps->max_height,
+ &max_width, &max_height);
+
+ if (new_state->fb->width > max_width ||
+ new_state->fb->height > max_height) {
+ drm_err(plane->dev,
+ "plane%u: framebuffer %ux%u exceeds caps %ux%u\n",
+ plane->base.id, new_state->fb->width, new_state->fb->height,
+ dcp_kms->caps->max_width, dcp_kms->caps->max_height);
+ return -E2BIG;
+ }
+
+ for (i = 0; i < new_state->fb->format->num_planes; i++) {
+ if (new_state->fb->pitches[i] > DCP_SSPP_MAX_PITCH_SIZE) {
+ drm_err(plane->dev,
+ "plane%u: pitch[%d]=%u exceeds limit %u\n",
+ plane->base.id, i, new_state->fb->pitches[i],
+ DCP_SSPP_MAX_PITCH_SIZE);
+ return -E2BIG;
+ }
+ }
+
+ src = drm_plane_state_src(new_state);
+
+ pstate->src_img_w = new_state->fb->width;
+ pstate->src_img_h = new_state->fb->height;
+
+ if (new_state->fb->format->is_yuv) {
+ u32 src_x = src.x1 >> 16;
+ u32 src_y = src.y1 >> 16;
+ u32 src_w = drm_rect_width(&src) >> 16;
+ u32 src_h = drm_rect_height(&src) >> 16;
+
+ if (src_w < 2 || src_h < 2) {
+ drm_err(plane->dev,
+ "plane%u: YUV source too small: %ux%u\n",
+ plane->base.id, src_w, src_h);
+ return -EINVAL;
+ }
+
+ if (src_x & 0x1 || src_y & 0x1 || src_w & 0x1 || src_h & 0x1) {
+ drm_err(plane->dev,
+ "plane%u: unaligned YUV source: (%u,%u %ux%u)\n",
+ plane->base.id, src_x, src_y, src_w, src_h);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static void dcp_plane_set_hfi_error(const struct drm_plane_state *state, int ret)
+{
+ struct dcp_crtc_state *cstate;
+
+ cstate = to_dcp_crtc_state(state->crtc->state);
+ if (!cstate->hfi_error)
+ cstate->hfi_error = ret;
+}
+
+static int dcp_plane_submit_update(struct drm_plane *plane,
+ struct drm_plane_state *new_state)
+{
+ struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
+ struct dcp_dpu_kms *dcp_kms = _dcp_plane_get_kms(plane);
+ u32 session_id;
+ int ret;
+
+ if (!dcp_crtc_get_session(new_state->crtc, &session_id)) {
+ drm_dbg_driver(plane->dev,
+ "plane%u: update before HFI session open, deferring\n",
+ plane->base.id);
+ return 0;
+ }
+
+ pstate->prop_pkt_id =
+ (u32)atomic_inc_return(&dcp_kms->hfi->packet_id);
+
+ ret = dcp_plane_build_hfi_props_packet(pstate, new_state, session_id,
+ pstate->prop_pkt_id);
+ if (ret)
+ return ret;
+
+ ret = dcp_hfi_submit_prop_pkt(dcp_kms->hfi, pstate->prop_pkt,
+ pstate->prop_pkt_size);
+ if (ret)
+ return ret;
+
+ drm_dbg_driver(plane->dev,
+ "plane%u: submitted HFI update pipe=%u bytes=%zu\n",
+ plane->base.id, pstate->hfi_pipe_id, pstate->prop_pkt_size);
+
+ return 0;
+}
+
+static int dcp_plane_submit_disable(struct drm_plane *plane,
+ struct drm_plane_state *old_state)
+{
+ struct dcp_plane_state *pstate = to_dcp_plane_state(old_state);
+ struct dcp_dpu_kms *dcp_kms = _dcp_plane_get_kms(plane);
+ u32 session_id;
+
+ if (!old_state->crtc)
+ return 0;
+
+ if (!dcp_crtc_get_session(old_state->crtc, &session_id)) {
+ drm_dbg_driver(plane->dev,
+ "plane%u: disable after HFI session close, skipping detach\n",
+ plane->base.id);
+ return 0;
+ }
+
+ return dcp_hfi_plane_disable(dcp_kms->hfi, session_id,
+ pstate->hfi_pipe_id);
+}
+
+static void dcp_plane_atomic_update(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *new_state =
+ drm_atomic_get_new_plane_state(state, plane);
+ struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
+ int ret;
+
+ drm_dbg_driver(plane->dev,
+ "plane%u: atomic_update visible=%d fb=%u pipe_id=%u\n",
+ plane->base.id, new_state->visible,
+ new_state->fb ? new_state->fb->base.id : 0,
+ pstate->hfi_pipe_id);
+
+ pstate->prop_pkt_size = 0;
+
+ if (!new_state->visible || !new_state->fb)
+ return;
+
+ ret = dcp_plane_submit_update(plane, new_state);
+ if (ret) {
+ drm_err(plane->dev,
+ "plane%u: HFI update failed: %d\n", plane->base.id, ret);
+ dcp_plane_set_hfi_error(new_state, ret);
+ return;
+ }
+}
+
+static void dcp_plane_atomic_disable(struct drm_plane *plane,
+ struct drm_atomic_commit *state)
+{
+ struct drm_plane_state *old_state =
+ drm_atomic_get_old_plane_state(state, plane);
+ struct dcp_plane_state *pstate = to_dcp_plane_state(old_state);
+ int ret;
+
+ drm_dbg_driver(plane->dev,
+ "plane%u: atomic_disable pipe_id=%u\n",
+ plane->base.id, pstate->hfi_pipe_id);
+
+ ret = dcp_plane_submit_disable(plane, old_state);
+ if (ret) {
+ drm_err(plane->dev,
+ "plane%u: HFI detach failed: %d\n", plane->base.id, ret);
+ dcp_plane_set_hfi_error(old_state, ret);
+ }
+
+ pstate->prop_pkt_size = 0;
+ pstate->prop_pkt_id = 0;
+}
+
+static struct drm_plane_state *
+dcp_plane_atomic_duplicate_state(struct drm_plane *plane)
+{
+ struct dcp_plane_state *pstate, *old_pstate;
+
+ if (WARN_ON(!plane->state))
+ return NULL;
+
+ old_pstate = to_dcp_plane_state(plane->state);
+ pstate = kzalloc_obj(*pstate, GFP_KERNEL);
+ if (!pstate)
+ return NULL;
+
+ __drm_atomic_helper_plane_duplicate_state(plane, &pstate->base);
+ pstate->hfi_pipe_id = old_pstate->hfi_pipe_id;
+ pstate->src_img_w = old_pstate->src_img_w;
+ pstate->src_img_h = old_pstate->src_img_h;
+ pstate->fetch_bw = old_pstate->fetch_bw;
+ pstate->clk = old_pstate->clk;
+ pstate->needs_dirtyfb = old_pstate->needs_dirtyfb;
+ pstate->prop_pkt_size = 0;
+ pstate->prop_pkt_id = old_pstate->prop_pkt_id;
+
+ return &pstate->base;
+}
+
+static void dcp_plane_atomic_destroy_state(struct drm_plane *plane,
+ struct drm_plane_state *state)
+{
+ __drm_atomic_helper_plane_destroy_state(state);
+ kfree(to_dcp_plane_state(state));
+}
+
+static struct drm_plane_state *
+dcp_plane_atomic_create_state(struct drm_plane *plane)
+{
+ struct dcp_plane_state *pstate;
+ u32 hfi_pipe_id = to_dcp_plane(plane)->hfi_pipe_id;
+
+ pstate = kzalloc_obj(*pstate, GFP_KERNEL);
+ if (!pstate)
+ return NULL;
+
+ __drm_atomic_helper_plane_state_init(&pstate->base, plane);
+ pstate->hfi_pipe_id = hfi_pipe_id;
+
+ return &pstate->base;
+}
+
+static void dcp_plane_atomic_print_state(struct drm_printer *p,
+ const struct drm_plane_state *state)
+{
+ const struct dcp_plane_state *pstate = to_dcp_plane_state(state);
+
+ drm_printf(p, "\tprop_pkt_size=%zu\n", pstate->prop_pkt_size);
+ drm_printf(p, "\thfi_pipe_id=%u\n", pstate->hfi_pipe_id);
+
+ if (pstate->prop_pkt_size) {
+ u32 hfi_rot = dcp_drm_to_hfi_rotation(state->rotation);
+
+ drm_printf(p, "\thfi_format=0x%x\n", pstate->hfi_format);
+ drm_printf(p, "\thfi_rotation=0x%x\n", hfi_rot);
+ }
+}
+
+static bool dcp_plane_format_mod_supported(struct drm_plane *plane,
+ u32 format, uint64_t modifier)
+{
+ if (modifier == DRM_FORMAT_MOD_LINEAR)
+ return true;
+
+ if (modifier == DRM_FORMAT_MOD_QCOM_COMPRESSED)
+ return dcp_drm_to_hfi_format(format, modifier) != U32_MAX;
+
+ return false;
+}
+
+static const struct drm_plane_helper_funcs dcp_plane_helper_funcs = {
+ .prepare_fb = dcp_plane_prepare_fb,
+ .cleanup_fb = dcp_plane_cleanup_fb,
+ .atomic_check = dcp_plane_atomic_check,
+ .atomic_update = dcp_plane_atomic_update,
+ .atomic_disable = dcp_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs dcp_plane_funcs = {
+ .update_plane = drm_atomic_helper_update_plane,
+ .disable_plane = drm_atomic_helper_disable_plane,
+ .atomic_create_state = dcp_plane_atomic_create_state,
+ .atomic_duplicate_state = dcp_plane_atomic_duplicate_state,
+ .atomic_destroy_state = dcp_plane_atomic_destroy_state,
+ .format_mod_supported = dcp_plane_format_mod_supported,
+ .atomic_print_state = dcp_plane_atomic_print_state,
+};
+
+struct drm_plane *dcp_plane_init(struct drm_device *dev,
+ u32 hfi_pipe_id,
+ enum drm_plane_type type,
+ unsigned long possible_crtcs)
+{
+ struct dcp_plane *dcp_plane;
+ struct drm_plane *plane;
+ int ret;
+
+ dcp_plane = drmm_universal_plane_alloc(dev, struct dcp_plane, base,
+ possible_crtcs,
+ &dcp_plane_funcs,
+ dcp_plane_formats,
+ ARRAY_SIZE(dcp_plane_formats),
+ dcp_format_modifiers,
+ type, NULL);
+ if (IS_ERR(dcp_plane))
+ return ERR_CAST(dcp_plane);
+
+ plane = &dcp_plane->base;
+ drm_plane_helper_add(plane, &dcp_plane_helper_funcs);
+
+ ret = drm_plane_create_zpos_property(plane, 0, 0, DCP_ZPOS_MAX);
+ if (ret)
+ drm_dbg_driver(plane->dev, "plane%u: zpos property failed\n", hfi_pipe_id);
+
+ drm_plane_create_alpha_property(plane);
+
+ drm_plane_create_blend_mode_property(plane,
+ BIT(DRM_MODE_BLEND_PIXEL_NONE) |
+ BIT(DRM_MODE_BLEND_PREMULTI) |
+ BIT(DRM_MODE_BLEND_COVERAGE));
+
+ drm_plane_create_rotation_property(plane,
+ DRM_MODE_ROTATE_0,
+ DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 |
+ DRM_MODE_ROTATE_180 | DRM_MODE_ROTATE_270 |
+ DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y);
+
+ drm_plane_enable_fb_damage_clips(plane);
+
+ dcp_plane->hfi_pipe_id = hfi_pipe_id;
+ if (plane->state)
+ to_dcp_plane_state(plane->state)->hfi_pipe_id = hfi_pipe_id;
+
+ drm_dbg_driver(dev, "plane initialized (type=%d, hfi_pipe_id=%u)\n",
+ type, hfi_pipe_id);
+ return plane;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.h b/drivers/gpu/drm/msm/dcp/dcp_plane.h
new file mode 100644
index 000000000000..377a156c046a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane implementation and API.
+ */
+
+#ifndef __DCP_PLANE_H__
+#define __DCP_PLANE_H__
+
+#include "dcp_plane_state.h"
+
+struct dcp_dpu_kms;
+struct drm_display_mode;
+
+struct dcp_plane_perf {
+ u64 fetch_bw;
+ u64 clk;
+};
+
+struct dcp_plane {
+ struct drm_plane base;
+ u32 hfi_pipe_id;
+};
+
+#define to_dcp_plane(x) container_of(x, struct dcp_plane, base)
+
+void dcp_plane_calc_perf(const struct drm_plane_state *state,
+ const struct drm_display_mode *mode,
+ struct dcp_plane_perf *perf);
+struct drm_plane *dcp_plane_init(struct drm_device *dev,
+ u32 hfi_pipe_id,
+ enum drm_plane_type type,
+ unsigned long possible_crtcs);
+
+#endif /* __DCP_PLANE_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 13/23] drm/msm/dcp: Route HFI display events
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (11 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 12:08 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder Jayant Shekhar
` (10 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Route asynchronous display events from DCP firmware into the DRM CRTC
objects. Without this mapping, VSYNC and frame-scan completion cannot
drive DRM vblank and page-flip completion.
Match firmware display events to the corresponding CRTC session and
deliver VSYNC and frame-scan completion notifications through the
MSM DRM callbacks.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
index 774a5afac0f0..e54ccfafdae0 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -19,6 +19,7 @@
#include "dcp_hfi.h"
#include "dcp_hfi_priv.h"
#include "dcp_drv.h"
+#include "dcp_crtc.h"
#include "hfi/hfi_packet.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
@@ -260,18 +261,35 @@ static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
}
+static void dcp_hfi_dispatch_frame_done(struct dcp_hfi *hfi,
+ const struct hfi_packet_info *pkt_info)
+{
+ const struct hfi_display_frame_event_data *frame = NULL;
+ u32 sequence_id = 0;
+
+ if (pkt_info->payload_ptr &&
+ pkt_info->payload_size >= sizeof(*frame)) {
+ frame = pkt_info->payload_ptr;
+ sequence_id = frame->bufferflip_index;
+ }
+ dcp_crtc_hfi_frame_done_cb(hfi->drv, pkt_info->id & 0xFFFF,
+ sequence_id);
+}
+
/* Route asynchronous events and capability packets before flag handling. */
static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
const struct hfi_packet_info *pkt_info)
{
switch (pkt_info->cmd) {
case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+ dcp_crtc_hfi_vblank_cb(hfi->drv, pkt_info->id & 0xFFFF);
return true;
case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
return true;
case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+ dcp_hfi_dispatch_frame_done(hfi, pkt_info);
return true;
case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS:
@@ -975,6 +993,9 @@ int dcp_hfi_init(struct dcp_drv *drv,
struct dcp_hfi *hfi;
int i, ret;
+ if (!dma_dev)
+ return -ENODEV;
+
hfi = devm_kzalloc(&vdev->dev, sizeof(*hfi), GFP_KERNEL);
if (!hfi)
return -ENOMEM;
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (12 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support Jayant Shekhar
` (9 subsequent siblings)
23 siblings, 0 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Create the DRM encoder required to connect a DCP CRTC to its
firmware-backed output bridge. The encoder must fit the existing MSM
DRM object model while remaining independent of bridge discovery and
KMS registration.
Implement the DCP encoder wrapper, allocation, and DRM object
initialization.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_encoder.c | 29 +++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_encoder.h | 16 ++++++++++++++++
3 files changed, 46 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index b425fefda890..4024f6b1e3cf 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -83,6 +83,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_kms.o \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
+ dcp/dcp_encoder.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_encoder.c b/drivers/gpu/drm/msm/dcp/dcp_encoder.c
new file mode 100644
index 000000000000..e7a64c960bdc
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_encoder.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP encoder wrapper for the CRTC-to-bridge connection.
+ * Hardware programming and mode sequencing are handled by the bridge and HFI.
+ */
+
+#include <drm/drm_encoder.h>
+#include <drm/drm_managed.h>
+
+#include "dcp_encoder.h"
+
+struct drm_encoder *dcp_encoder_init(struct drm_device *dev, int enc_type)
+{
+ struct drm_encoder *encoder;
+ int ret;
+
+ encoder = drmm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
+ if (!encoder)
+ return ERR_PTR(-ENOMEM);
+
+ ret = drmm_encoder_init(dev, encoder, NULL, enc_type, "encoder%d",
+ dev->mode_config.num_encoder);
+ if (ret)
+ return ERR_PTR(ret);
+
+ return encoder;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_encoder.h b/drivers/gpu/drm/msm/dcp/dcp_encoder.h
new file mode 100644
index 000000000000..de40c9bca4bb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_encoder.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP encoder interface between the CRTC and bridge chain.
+ * Hardware programming is delegated to DCP firmware.
+ */
+
+#ifndef __DCP_ENCODER_H__
+#define __DCP_ENCODER_H__
+
+#include <drm/drm_encoder.h>
+
+struct drm_encoder *dcp_encoder_init(struct drm_device *dev, int enc_type);
+
+#endif /* __DCP_ENCODER_H__ */
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (13 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 11:08 ` Dmitry Baryshkov
2026-10-26 12:37 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
` (8 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Expose the DCP DSI host and panel as DRM-facing objects. The DCP
display path needs a host, panel configuration, bridge state, DSC
setup, DCS transfer buffers, and panel lifetime management before it
can drive a physical display.
Implement the MIPI DSI host, panel-device support, bridge state,
DSC configuration, DCS transfers, and DSI buffer management.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h | 76 ++
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 13 +
4 files changed, 1200 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 4024f6b1e3cf..9f0a9bfeb2a7 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_crtc.o \
dcp/dcp_dpu_kms.o \
dcp/dcp_plane.o \
+ dcp/dcp_dsi_bridge.o \
dcp/dcp_hfi_core.o \
dcp/dcp_hfi_crtc.o \
dcp/dcp_hfi_dsi.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
new file mode 100644
index 000000000000..b3e782b8f047
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
@@ -0,0 +1,1110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge driver.
+ *
+ * Implements a DRM bridge for DSI displays in the DCP firmware-delegated
+ * architecture. DCP firmware owns the DSI PHY and controller; this driver
+ * uses HFI commands to configure DSI parameters rather than direct register
+ * writes.
+ */
+
+#include <linux/component.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+
+#include <video/mipi_display.h>
+
+#include "dcp_crtc.h"
+#include "dcp_dsi_bridge.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_defs_panel.h"
+
+struct dcp_dsi_bridge_state {
+ struct drm_bridge_state base;
+ struct dcp_dsi_panel_state panel_state;
+ int prepare_error;
+};
+
+#define to_dcp_dsi_bridge_state(s) \
+ container_of(s, struct dcp_dsi_bridge_state, base)
+
+static struct dcp_dsi_bridge_state *
+dcp_dsi_bridge_get_state(struct dcp_dsi_bridge *bridge)
+{
+ return to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->bridge.base.state));
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_create_state(struct drm_bridge *bridge)
+{
+ struct dcp_dsi_bridge_state *state;
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return ERR_PTR(-ENOMEM);
+
+ __drm_atomic_helper_bridge_state_init(&state->base, bridge);
+ state->panel_state.topology_mixer_count =
+ dsi_bridge->hfi_panel_config.topology_mixer_count;
+ state->panel_state.topology_enc_count =
+ dsi_bridge->hfi_panel_config.topology_enc_count;
+ state->panel_state.topology_intf_count =
+ dsi_bridge->hfi_panel_config.topology_intf_count;
+ return &state->base;
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+ struct dcp_dsi_bridge_state *state, *old_state;
+
+ if (WARN_ON(!bridge->base.state))
+ return NULL;
+
+ state = kzalloc_obj(*state);
+ if (!state)
+ return NULL;
+
+ old_state = to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->base.state));
+ __drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+ state->panel_state = old_state->panel_state;
+ return &state->base;
+}
+
+static void dcp_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+ struct drm_bridge_state *bridge_state)
+{
+ kfree(to_dcp_dsi_bridge_state(bridge_state));
+}
+
+static void
+dcp_dsi_bridge_build_hfi_config(struct dcp_dsi_bridge *dsi_bridge,
+ const struct dcp_dsi_bridge_state *state,
+ struct dcp_dsi_hfi_panel_config *cfg)
+{
+ *cfg = dsi_bridge->hfi_panel_config;
+ cfg->topology_mixer_count = state->panel_state.topology_mixer_count;
+ cfg->topology_enc_count = state->panel_state.topology_enc_count;
+ cfg->topology_intf_count = state->panel_state.topology_intf_count;
+}
+
+static void dcp_dsi_host_dev_release(struct device *dev)
+{
+ of_node_put(dev->of_node);
+}
+
+static bool dcp_dsi_bridge_cmd_mode(const struct dcp_dsi_bridge *dsi_bridge)
+{
+ return dsi_bridge->dsi_device &&
+ !(dsi_bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+}
+
+static u64 dcp_dsi_link_clock(const struct dcp_dsi_bridge *dsi_bridge,
+ const struct dcp_dsi_hfi_panel_config *cfg,
+ const struct drm_display_mode *mode)
+{
+ const struct mipi_dsi_device *dsi = dsi_bridge->dsi_device;
+ int bpp;
+ u32 bpp_x16;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0)
+ return 0;
+
+ bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
+ cfg->dsc_bits_per_pixel : bpp << 4;
+
+ if (dsi->hs_rate)
+ return dsi->hs_rate;
+
+ return div_u64((u64)mode->clock * 1000 * bpp_x16, dsi->lanes * 16);
+}
+
+static void dcp_dsi_power_control_work(struct work_struct *work)
+{
+ struct dcp_dsi_bridge *dsi_bridge =
+ container_of(work, struct dcp_dsi_bridge, power_control_work);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+ dcp_hfi_display_power_control_ack(kms->hfi,
+ dsi_bridge->session_id,
+ dsi_bridge->power_packet_id);
+
+ complete(&dsi_bridge->power_done);
+}
+
+static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
+ u32 power_mask, bool enable,
+ u32 packet_id)
+{
+ struct dcp_dpu_kms *kms = priv;
+ struct dcp_dsi_bridge *dsi_bridge = NULL;
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
+ struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
+
+ if (b && b->session_id == display_id) {
+ dsi_bridge = b;
+ break;
+ }
+ }
+
+ if (!dsi_bridge) {
+ dev_warn(kms->pm_dev,
+ "DSI: power_control for unknown display_id=%u\n", display_id);
+ return;
+ }
+
+ dsi_bridge->power_packet_id = packet_id;
+ schedule_work(&dsi_bridge->power_control_work);
+}
+
+static const struct component_ops dcp_dsi_component_ops;
+
+static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+ struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
+ int ret;
+
+ if (!dsi->lanes || dsi->lanes > 4)
+ return -EINVAL;
+
+ cfg->dsc_enabled = false;
+ cfg->dsc_version_major = 0;
+ cfg->dsc_version_minor = 0;
+ cfg->dsc_scr_version = 0;
+ cfg->dsc_slice_height = 0;
+ cfg->dsc_slice_width = 0;
+ cfg->dsc_slices_per_pkt = 0;
+ cfg->dsc_bits_per_component = 0;
+ cfg->dsc_pps_delay_ms = 0;
+ cfg->dsc_bits_per_pixel = 0;
+ cfg->dsc_chroma_format = 0;
+ cfg->dsc_color_space = 0;
+ cfg->dsc_block_prediction_enable = 0;
+ cfg->dsc_rc_override = false;
+ memset(cfg->dsc_rc_min_qp, 0, sizeof(cfg->dsc_rc_min_qp));
+ memset(cfg->dsc_rc_max_qp, 0, sizeof(cfg->dsc_rc_max_qp));
+ memset(cfg->dsc_rc_bpg_offsets, 0, sizeof(cfg->dsc_rc_bpg_offsets));
+
+ switch (dsi->format) {
+ case MIPI_DSI_FMT_RGB888:
+ case MIPI_DSI_FMT_RGB666:
+ case MIPI_DSI_FMT_RGB666_PACKED:
+ case MIPI_DSI_FMT_RGB565:
+ case MIPI_DSI_FMT_RGB101010:
+ break;
+ default:
+ dev_err(&dsi->dev, "unsupported pixel format %u\n", dsi->format);
+ return -EINVAL;
+ }
+
+ dsi_bridge->dsi_device = dsi;
+
+ if (dsi->dsc) {
+ struct drm_dsc_config *dsc = dsi->dsc;
+ u32 chroma_format = 0;
+ u32 color_space = 0;
+
+ if (dsc->native_422)
+ chroma_format = 1;
+ else if (dsc->native_420)
+ chroma_format = 2;
+ if (dsc->native_422 || dsc->native_420)
+ color_space = 1;
+
+ cfg->dsc_enabled = true;
+ cfg->dsc_version_major = dsc->dsc_version_major;
+ cfg->dsc_version_minor = dsc->dsc_version_minor;
+ cfg->dsc_scr_version = 0;
+ cfg->dsc_slice_height = dsc->slice_height;
+ cfg->dsc_slice_width = dsc->slice_width;
+ cfg->dsc_slices_per_pkt = 1;
+ cfg->dsc_bits_per_component = dsc->bits_per_component;
+ cfg->dsc_pps_delay_ms = 0;
+ cfg->dsc_bits_per_pixel = dsc->bits_per_pixel;
+ cfg->dsc_chroma_format = chroma_format;
+ cfg->dsc_color_space = color_space;
+ cfg->dsc_block_prediction_enable = dsc->block_pred_enable;
+
+ dev_dbg(&dsi->dev,
+ "DSI%u: DSC enabled: version=%u.%u slice=%ux%u bpc=%u bpp=%u.%u\n",
+ dsi_bridge->intf_idx, dsc->dsc_version_major,
+ dsc->dsc_version_minor, dsc->slice_width,
+ dsc->slice_height, dsc->bits_per_component,
+ dsc->bits_per_pixel >> 4, (dsc->bits_per_pixel & 0xf) * 100 / 16);
+ }
+
+ ret = component_add(&dsi_bridge->dsi_host_dev,
+ &dcp_dsi_component_ops);
+ if (ret)
+ return ret;
+ dsi_bridge->component_added = true;
+
+ return 0;
+}
+
+static int dcp_dsi_host_detach(struct mipi_dsi_host *host,
+ struct mipi_dsi_device *dsi)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+
+ if (dsi_bridge->component_added) {
+ component_del(&dsi_bridge->dsi_host_dev,
+ &dcp_dsi_component_ops);
+ dsi_bridge->component_added = false;
+ }
+ dsi_bridge->panel_bridge = NULL;
+ dsi_bridge->dsi_device = NULL;
+ dsi_bridge->hfi_panel_config.dsc_enabled = false;
+ return 0;
+}
+
+static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
+ const struct mipi_dsi_msg *msg)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ u32 session_id;
+ bool temporary_session = false;
+ ssize_t ret;
+
+ guard(mutex)(&dsi_bridge->session_lock);
+ if (!dsi_bridge->dcs_tx_buf)
+ return -ENODEV;
+
+ /* Serialize DCS transfers */
+ guard(mutex)(&dsi_bridge->dcs_lock);
+
+ if (dsi_bridge->session_open) {
+ session_id = dsi_bridge->session_id;
+ } else {
+ /*
+ * mipi_dsi_host_ops::transfer() may be called while the host is
+ * disabled. Open a transaction-only DCP session in that case;
+ * atomic_pre_enable() will create the full display session later.
+ */
+ ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
+ dsi_bridge->fw_display_id,
+ &session_id);
+ if (ret)
+ return ret;
+
+ temporary_session = true;
+ }
+
+ ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
+ dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
+ dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
+ dsi_bridge->hfi_panel_config.ctrl_indices[0]);
+ if (temporary_session) {
+ int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
+
+ if (ret >= 0 && close_ret)
+ ret = close_ret;
+ }
+ if (ret < 0)
+ dev_warn(kms->pm_dev,
+ "DSI%u: live DCS command transfer failed: %zd\n",
+ dsi_bridge->intf_idx, ret);
+
+ return ret;
+}
+
+static int dcp_dsi_component_bind(struct device *dev,
+ struct device *master, void *data)
+{
+ struct dcp_dsi_bridge *dsi_bridge =
+ container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+ dsi_bridge->panel_bridge =
+ devm_drm_of_get_bridge(dev, dsi_bridge->dcp_np,
+ dsi_bridge->dcp_port, 0);
+ if (IS_ERR(dsi_bridge->panel_bridge)) {
+ int ret = PTR_ERR(dsi_bridge->panel_bridge);
+
+ dsi_bridge->panel_bridge = NULL;
+ return ret;
+ }
+
+ return 0;
+}
+
+static void dcp_dsi_component_unbind(struct device *dev,
+ struct device *master, void *data)
+{
+ struct dcp_dsi_bridge *dsi_bridge =
+ container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+ dsi_bridge->panel_bridge = NULL;
+}
+
+static const struct component_ops dcp_dsi_component_ops = {
+ .bind = dcp_dsi_component_bind,
+ .unbind = dcp_dsi_component_unbind,
+};
+
+static const struct mipi_dsi_host_ops dcp_dsi_host_ops = {
+ .attach = dcp_dsi_host_attach,
+ .detach = dcp_dsi_host_detach,
+ .transfer = dcp_dsi_host_transfer,
+};
+
+static int dcp_dsi_bridge_attach(struct drm_bridge *bridge,
+ struct drm_encoder *encoder,
+ enum drm_bridge_attach_flags flags)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ struct drm_bridge *panel_bridge;
+ int ret;
+
+ panel_bridge = dsi_bridge->panel_bridge;
+ if (!panel_bridge)
+ return -EPROBE_DEFER;
+
+ ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
+ if (ret) {
+ drm_err(bridge->dev,
+ "DSI%u: panel bridge attach failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ return ret;
+ }
+
+ drm_dbg_driver(dsi_bridge->kms->dev,
+ "DSI%u: panel bridge attached\n",
+ dsi_bridge->intf_idx);
+
+ dcp_hfi_set_power_control_cb(dsi_bridge->kms->hfi,
+ dsi_bridge->kms, dcp_dsi_power_control_cb);
+
+ return 0;
+}
+
+static u32 dcp_dsi_bridge_get_mixer_count(struct dcp_dsi_bridge *dsi_bridge,
+ const struct dcp_dsi_hfi_panel_config *cfg,
+ const struct drm_display_mode *mode)
+{
+ u32 mixer_count = cfg->topology_mixer_count;
+
+ if (!mixer_count)
+ mixer_count = 1;
+
+ if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+ const struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+
+ if (dsc->slice_count)
+ mixer_count = dsc->slice_count;
+ else if (dsc->slice_width && mode->hdisplay)
+ mixer_count = DIV_ROUND_UP(mode->hdisplay,
+ dsc->slice_width);
+ }
+
+ return mixer_count;
+}
+
+static void dcp_dsi_bridge_publish_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ struct drm_crtc *crtc;
+
+ crtc = dcp_dpu_kms_get_crtc(kms, dsi_bridge->fw_display_id);
+ if (crtc)
+ dcp_crtc_set_session(crtc, dsi_bridge->session_id);
+ else
+ drm_err(kms->dev, "DSI%u: no CRTC for session %u\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_id);
+}
+
+static void dcp_dsi_bridge_clear_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+ dcp_crtc_clear_session(dcp_dpu_kms_get_crtc(dsi_bridge->kms,
+ dsi_bridge->fw_display_id));
+}
+
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+ struct dcp_dsi_hfi_panel_config *cfg,
+ const struct drm_display_mode *mode)
+{
+ struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+ enum drm_dsc_params_type params_type;
+ unsigned int i;
+ int ret;
+
+ dsc->pic_width = mode->hdisplay;
+ dsc->pic_height = mode->vdisplay;
+ dsc->convert_rgb = !dsc->native_422 && !dsc->native_420;
+ dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width *
+ dsc->bits_per_pixel, 8 * 16);
+ dsc->simple_422 = 0;
+ dsc->vbr_enable = 0;
+
+ if (dsc->dsc_version_major != 1)
+ return -EOPNOTSUPP;
+
+ switch (dsc->dsc_version_minor) {
+ case 1:
+ if (dsc->native_422 || dsc->native_420)
+ return -EOPNOTSUPP;
+ params_type = DRM_DSC_1_1_PRE_SCR;
+ break;
+ case 2:
+ if (dsc->native_422)
+ params_type = DRM_DSC_1_2_422;
+ else if (dsc->native_420)
+ params_type = DRM_DSC_1_2_420;
+ else
+ params_type = DRM_DSC_1_2_444;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ drm_dsc_set_const_params(dsc);
+ drm_dsc_set_rc_buf_thresh(dsc);
+ ret = drm_dsc_setup_rc_params(dsc, params_type);
+ if (ret)
+ return ret;
+
+ dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+ dsc->line_buf_depth = dsc->bits_per_component + 1;
+ ret = drm_dsc_compute_rc_parameters(dsc);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
+ cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
+ cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
+ cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
+ }
+
+ return 0;
+}
+
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+ const struct drm_display_mode *mode)
+{
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
+ struct dcp_dsi_hfi_panel_config cfg;
+ int ret;
+ u32 opened_session_id;
+
+ if (!mode)
+ return -ENODEV;
+
+ guard(mutex)(&dsi_bridge->session_lock);
+ if (dsi_bridge->session_open)
+ return 0;
+
+ dsi_bridge->session_id = dsi_bridge->fw_display_id;
+
+ if (!dsi_bridge->dsi_device)
+ return -ENODEV;
+
+ dcp_dsi_bridge_build_hfi_config(dsi_bridge, state, &cfg);
+
+ if (dsi_bridge->dsi_device->dsc) {
+ ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, &cfg, mode);
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: DSC setup failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ }
+
+ ret = dcp_hfi_panel_init(kms->hfi, dsi_bridge->intf_idx,
+ mode,
+ dsi_bridge->dsi_device,
+ &cfg);
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: HFI PANEL_INIT failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+
+ if (mode->clock) {
+ u64 link_clock;
+
+ link_clock = dcp_dsi_link_clock(dsi_bridge, &cfg,
+ mode);
+ ret = dcp_hfi_display_mode_validate(kms->hfi,
+ dsi_bridge->session_id,
+ mode, link_clock);
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: HFI MODE_VALIDATE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+
+ ret = dcp_hfi_display_set_mode(kms->hfi,
+ dsi_bridge->session_id,
+ mode,
+ dcp_dsi_link_clock(dsi_bridge, &cfg,
+ mode));
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: HFI SET_MODE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ } else {
+ drm_warn(kms->dev, "DSI%u: no valid mode, skipping SET_MODE\n",
+ dsi_bridge->intf_idx);
+ }
+
+ dcp_hfi_set_power_control_cb(kms->hfi, kms,
+ dcp_dsi_power_control_cb);
+
+ reinit_completion(&dsi_bridge->power_done);
+
+ ret = dcp_hfi_display_power_register(kms->hfi, dsi_bridge->session_id);
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: HFI POWER_REGISTER failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+
+ ret = dcp_hfi_display_enable(kms->hfi,
+ dsi_bridge->intf_idx,
+ dsi_bridge->fw_display_id,
+ &opened_session_id);
+ if (ret) {
+ drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ dsi_bridge->session_id = opened_session_id;
+ dsi_bridge->session_open = true;
+ dcp_dsi_bridge_publish_session(dsi_bridge);
+
+ drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_id);
+ return 0;
+
+err_close:
+ dcp_hfi_clear_power_control_cb(kms->hfi, kms);
+ dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+ dsi_bridge->session_id = 0;
+ dsi_bridge->session_open = false;
+ dcp_dsi_bridge_clear_session(dsi_bridge);
+
+ return ret;
+}
+
+static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ int ret;
+
+ ret = dsi_bridge->session_open ? 0 : -ENODEV;
+ dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+ if (ret)
+ drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ bool scan_start_registered = false;
+ bool scan_complete_registered = false;
+ bool autorefresh_enabled = false;
+ int ret;
+
+ drm_dbg_driver(kms->dev,
+ "DSI%u: bridge atomic_enable (session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+ if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
+ return;
+ if (!dsi_bridge->session_open)
+ return;
+
+ ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
+ if (ret) {
+ drm_err(bridge->dev,
+ "DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+
+ dcp_dsi_bridge_publish_session(dsi_bridge);
+
+ ret = dcp_hfi_display_event_register(kms->hfi,
+ dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_START);
+ if (ret)
+ goto err_close;
+ scan_start_registered = true;
+
+ ret = dcp_hfi_display_event_register(kms->hfi,
+ dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_COMPLETE);
+ if (ret)
+ goto err_close;
+ scan_complete_registered = true;
+
+ if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+ ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+ dsi_bridge->session_id, true, 1);
+ if (ret) {
+ drm_err(bridge->dev,
+ "DSI%u: initial autorefresh setup failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ autorefresh_enabled = true;
+ }
+
+ drm_dbg_driver(bridge->dev,
+ "DSI%u: enable complete (session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_id);
+ return;
+
+err_close:
+ if (autorefresh_enabled)
+ dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
+ false, 0);
+ if (scan_complete_registered)
+ dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+ HFI_EVENT_FRAME_SCAN_COMPLETE);
+ if (scan_start_registered)
+ dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+ HFI_EVENT_FRAME_SCAN_START);
+ drm_err(bridge->dev,
+ "DSI%u: bridge enable failed; session closed\n",
+ dsi_bridge->intf_idx);
+ dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+ {
+ guard(mutex)(&dsi_bridge->session_lock);
+ dsi_bridge->session_id = 0;
+ dsi_bridge->session_open = false;
+ }
+ dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static void dcp_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ int ret;
+
+ drm_dbg_driver(kms->dev,
+ "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_open,
+ dsi_bridge->session_id);
+
+ guard(mutex)(&dsi_bridge->session_lock);
+ if (!dsi_bridge->session_open)
+ return;
+
+ if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+ ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+ dsi_bridge->session_id, false, 0);
+ if (ret)
+ drm_warn(bridge->dev,
+ "DSI%u: autorefresh disable failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ }
+
+ dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+ HFI_EVENT_FRAME_SCAN_COMPLETE);
+ dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+ HFI_EVENT_FRAME_SCAN_START);
+
+ ret = dcp_hfi_display_disable(kms->hfi, dsi_bridge->session_id);
+ if (ret)
+ drm_warn(bridge->dev,
+ "DSI%u: HFI DISPLAY_DISABLE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ int ret;
+
+ drm_dbg_driver(kms->dev,
+ "DSI%u: atomic_post_disable\n",
+ dsi_bridge->intf_idx);
+
+ guard(mutex)(&dsi_bridge->session_lock);
+ if (!dsi_bridge->session_open)
+ return;
+
+ ret = dcp_hfi_display_post_disable(kms->hfi, dsi_bridge->session_id);
+ if (ret)
+ drm_warn(bridge->dev,
+ "DSI%u: HFI DISPLAY_POST_DISABLE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+
+ ret = dcp_hfi_send_sync(kms->hfi,
+ HFI_COMMAND_DISPLAY_CLOSE,
+ HFI_CMD_BUFF_DISPLAY, dsi_bridge->session_id,
+ HFI_PAYLOAD_NONE, NULL, 0,
+ HFI_TX_FLAGS_RESPONSE_REQUIRED |
+ HFI_TX_FLAGS_NON_DISCARDABLE,
+ NULL, 0,
+ 2000 /* ms */);
+ if (ret)
+ drm_warn(bridge->dev,
+ "DSI%u: HFI DISPLAY_CLOSE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+
+ dsi_bridge->session_id = 0;
+ dsi_bridge->session_open = false;
+
+ dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static int dcp_dsi_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 dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+ struct dcp_dsi_bridge_state *state =
+ to_dcp_dsi_bridge_state(bridge_state);
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ u32 max_width, max_height;
+ u32 mixer_count, bpc;
+
+ state->prepare_error = 0;
+
+ if (!mode->clock || !drm_mode_vrefresh(mode))
+ return -EINVAL;
+
+ if (dsi_bridge->dsi_device &&
+ (!dsi_bridge->dsi_device->lanes ||
+ !dcp_dsi_link_clock(dsi_bridge, &dsi_bridge->hfi_panel_config, mode)))
+ return -EINVAL;
+
+ if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+ state->panel_state.topology_mixer_count =
+ dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+ &dsi_bridge->hfi_panel_config, mode);
+ state->panel_state.topology_enc_count =
+ state->panel_state.topology_mixer_count;
+ state->panel_state.topology_intf_count = 1;
+ }
+
+ mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+ &dsi_bridge->hfi_panel_config, mode);
+ bpc = dsi_bridge->hfi_panel_config.dsc_bits_per_component ?
+ dsi_bridge->hfi_panel_config.dsc_bits_per_component : 8;
+ dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
+
+ dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
+ &max_width, &max_height);
+
+ if (mode->hdisplay > max_width || mode->vdisplay > max_height) {
+ drm_err(dsi_bridge->kms->dev,
+ "DSI%u: mode %dx%d exceeds DCP caps %dx%d\n",
+ dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
+ kms->caps->max_width, kms->caps->max_height);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static const struct drm_bridge_funcs dcp_dsi_bridge_funcs = {
+ .attach = dcp_dsi_bridge_attach,
+ .atomic_pre_enable = dcp_dsi_bridge_atomic_pre_enable,
+ .atomic_enable = dcp_dsi_bridge_atomic_enable,
+ .atomic_disable = dcp_dsi_bridge_atomic_disable,
+ .atomic_post_disable = dcp_dsi_bridge_atomic_post_disable,
+ .atomic_check = dcp_dsi_bridge_atomic_check,
+ .atomic_create_state = dcp_dsi_bridge_atomic_create_state,
+ .atomic_duplicate_state = dcp_dsi_bridge_atomic_duplicate_state,
+ .atomic_destroy_state = dcp_dsi_bridge_atomic_destroy_state,
+};
+
+#define DCP_DSI_DUAL_MAP_ALIGN SZ_1K
+
+static int dcp_dsi_dual_map_buf(struct dcp_dpu_kms *kms,
+ struct device *dma_dev, void *cpu_addr,
+ dma_addr_t iova, size_t size, int prot)
+{
+ struct sg_table sgt = { 0 };
+ struct iommu_domain *domain;
+ ssize_t mapped;
+ int ret;
+
+ if (!kms->iommu_dev || !cpu_addr || !iova ||
+ !IS_ALIGNED(iova, DCP_DSI_DUAL_MAP_ALIGN))
+ return -EINVAL;
+
+ domain = iommu_get_domain_for_dev(kms->iommu_dev);
+ if (!domain)
+ return -ENODEV;
+
+ ret = dma_get_sgtable(dma_dev, &sgt, cpu_addr, iova, size);
+ if (ret)
+ return ret;
+
+ mapped = iommu_map_sgtable(domain, iova, &sgt, prot);
+ if (mapped != PAGE_ALIGN(size)) {
+ if (mapped > 0)
+ iommu_unmap(domain, iova, mapped);
+ ret = mapped < 0 ? mapped : -EIO;
+ drm_err(kms->dev,
+ "failed to dual-map iova=%pad into iommu_dev domain: %zd\n",
+ &iova, mapped);
+ sg_free_table(&sgt);
+ return ret;
+ }
+
+ sg_free_table(&sgt);
+ return 0;
+}
+
+static void dcp_dsi_dual_unmap_buf(struct dcp_dpu_kms *kms, dma_addr_t iova,
+ size_t size)
+{
+ struct iommu_domain *domain;
+
+ if (!kms->iommu_dev || !iova)
+ return;
+
+ domain = iommu_get_domain_for_dev(kms->iommu_dev);
+ if (!domain)
+ return;
+
+ iommu_unmap(domain, iova, PAGE_ALIGN(size));
+}
+
+static void dcp_dsi_init_hfi_panel_defaults(struct dcp_dsi_hfi_panel_config *cfg,
+ u32 intf_idx)
+{
+ memset(cfg, 0, sizeof(*cfg));
+
+ cfg->display_type = HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0;
+ cfg->num_ctrls = 1;
+ cfg->ctrl_indices[0] = intf_idx;
+ cfg->num_phys = 1;
+ cfg->phy_indices[0] = intf_idx;
+
+ cfg->dma_trigger = HFI_PANEL_TRIGGER_SW;
+ cfg->stream_trigger = HFI_PANEL_TRIGGER_NONE;
+ cfg->te_dcs_command = 1;
+ cfg->wr_mem_start = 0x2C;
+ cfg->wr_mem_continue = 0x3C;
+
+ cfg->phy_type = HFI_PANEL_PHY_OLED;
+ cfg->bllp_eof_power_mode = true;
+ cfg->bllp_power_mode = true;
+ cfg->lane_map = 1;
+ cfg->color_order = 1;
+
+ cfg->jitter_numer = 4;
+ cfg->jitter_denom = 1;
+ cfg->dcs_tx_buf_size = DCP_DSI_DCS_TX_BUF_SIZE;
+
+ cfg->topology_mixer_count = 1;
+ cfg->topology_intf_count = 1;
+}
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+ struct device_node *np,
+ struct device_node *dcp_np,
+ u32 dcp_port, u32 intf_idx,
+ u32 fw_display_id)
+{
+ struct dcp_dsi_bridge *dsi_bridge;
+ struct dcp_dsi_hfi_panel_config *cfg;
+ struct device *dev = kms->pm_dev;
+ struct device *dma_dev = kms->hfi->dma_dev;
+ int ret;
+
+ dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
+ bridge, &dcp_dsi_bridge_funcs);
+ if (IS_ERR(dsi_bridge))
+ return ERR_CAST(dsi_bridge);
+
+ dsi_bridge->dcp_np = of_node_get(dcp_np);
+ dsi_bridge->dcp_port = dcp_port;
+ dsi_bridge->kms = kms;
+ dsi_bridge->intf_idx = intf_idx;
+ dsi_bridge->fw_display_id = fw_display_id;
+
+ INIT_WORK(&dsi_bridge->power_control_work, dcp_dsi_power_control_work);
+ init_completion(&dsi_bridge->power_done);
+ mutex_init(&dsi_bridge->session_lock);
+ mutex_init(&dsi_bridge->dcs_lock);
+
+ cfg = &dsi_bridge->hfi_panel_config;
+ dcp_dsi_init_hfi_panel_defaults(cfg, intf_idx);
+
+ /*
+ * Allocate the buffers for the firmware DMA domain here. The second
+ * mapping into the display domain is deferred until msm_kms_init_vm()
+ * has installed the final display IOMMU domain.
+ */
+ dsi_bridge->dcs_tx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+ &dsi_bridge->dcs_tx_iova, GFP_KERNEL);
+ if (!dsi_bridge->dcs_tx_buf) {
+ ret = -ENOMEM;
+ goto err_unwind_dma;
+ }
+
+ dsi_bridge->hfi_panel_config.dcs_tx_dva = dsi_bridge->dcs_tx_iova;
+ dsi_bridge->hfi_panel_config.dcs_tx_iova = dsi_bridge->dcs_tx_iova;
+
+ dsi_bridge->dcs_rx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+ &dsi_bridge->dcs_rx_iova, GFP_KERNEL);
+ if (!dsi_bridge->dcs_rx_buf) {
+ ret = -ENOMEM;
+ goto err_unwind_dma;
+ }
+
+ device_initialize(&dsi_bridge->dsi_host_dev);
+ dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+ dsi_bridge->dsi_host_dev.parent = dev;
+ dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
+ dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
+ dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
+ dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
+
+ ret = device_add(&dsi_bridge->dsi_host_dev);
+ if (ret) {
+ dev_err(dev,
+ "DSI%u: failed to add dsi_host_dev: %d\n", intf_idx, ret);
+ put_device(&dsi_bridge->dsi_host_dev);
+ goto err_unwind_dma;
+ }
+
+ ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
+ if (ret) {
+ dev_err(dev,
+ "DSI%u: failed to register MIPI DSI host: %d\n",
+ intf_idx, ret);
+ device_del(&dsi_bridge->dsi_host_dev);
+ put_device(&dsi_bridge->dsi_host_dev);
+ goto err_unwind_dma;
+ }
+
+ dsi_bridge->bridge.of_node = of_node_get(np);
+ dsi_bridge->bridge.type = DRM_MODE_CONNECTOR_DSI;
+
+ drm_bridge_add(&dsi_bridge->bridge);
+
+ return &dsi_bridge->bridge;
+
+err_unwind_dma:
+ if (dsi_bridge->dcs_rx_buf) {
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+ dsi_bridge->dcs_rx_buf,
+ dsi_bridge->dcs_rx_iova);
+ }
+ if (dsi_bridge->dcs_tx_buf) {
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+ dsi_bridge->dcs_tx_buf,
+ dsi_bridge->dcs_tx_iova);
+ }
+ cancel_work_sync(&dsi_bridge->power_control_work);
+ mutex_destroy(&dsi_bridge->session_lock);
+ mutex_destroy(&dsi_bridge->dcs_lock);
+ return ERR_PTR(ret);
+}
+
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+ struct device *dma_dev = kms->hfi->dma_dev;
+ int ret;
+
+ if (dsi_bridge->dcs_mapped)
+ return 0;
+
+ ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_tx_buf,
+ dsi_bridge->dcs_tx_iova,
+ DCP_DSI_DCS_TX_BUF_SIZE, IOMMU_READ);
+ if (ret)
+ return ret;
+
+ ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_rx_buf,
+ dsi_bridge->dcs_rx_iova,
+ DCP_DSI_DCS_RX_BUF_SIZE, IOMMU_WRITE);
+ if (ret) {
+ dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+ DCP_DSI_DCS_TX_BUF_SIZE);
+ return ret;
+ }
+
+ dsi_bridge->dcs_mapped = true;
+ drm_dbg_driver(kms->dev,
+ "DSI%u: mapped DCS buffers tx=%pad rx=%pad in display domain\n",
+ dsi_bridge->intf_idx, &dsi_bridge->dcs_tx_iova,
+ &dsi_bridge->dcs_rx_iova);
+
+ return 0;
+}
+
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+ struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+ if (!dsi_bridge->dcs_mapped)
+ return;
+
+ dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+ DCP_DSI_DCS_TX_BUF_SIZE);
+ dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
+ DCP_DSI_DCS_RX_BUF_SIZE);
+ dsi_bridge->dcs_mapped = false;
+}
+
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+ struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
+
+ dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
+ cancel_work_sync(&dsi_bridge->power_control_work);
+
+ drm_bridge_remove(&dsi_bridge->bridge);
+ of_node_put(dsi_bridge->bridge.of_node);
+ of_node_put(dsi_bridge->dcp_np);
+
+ mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+ device_del(&dsi_bridge->dsi_host_dev);
+ put_device(&dsi_bridge->dsi_host_dev);
+
+ dcp_dsi_bridge_unmap_buffers(dsi_bridge);
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+ dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
+
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+ dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
+
+ mutex_destroy(&dsi_bridge->session_lock);
+ mutex_destroy(&dsi_bridge->dcs_lock);
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
new file mode 100644
index 000000000000..f2c7abf23ac9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge state and panel configuration.
+ * DCP firmware owns the DSI PHY and controller; the bridge submits HFI commands.
+ */
+
+#ifndef __DCP_DSI_BRIDGE_H__
+#define __DCP_DSI_BRIDGE_H__
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_dsi_panel.h"
+
+struct dcp_dpu_kms;
+struct device_node;
+
+struct dcp_dsi_bridge {
+ struct drm_bridge bridge;
+ struct device dsi_host_dev;
+ struct mipi_dsi_host dsi_host;
+ struct mipi_dsi_device *dsi_device;
+ struct drm_bridge *panel_bridge;
+ bool component_added;
+
+ struct dcp_dpu_kms *kms;
+ struct device_node *dcp_np;
+ u32 dcp_port;
+ u32 intf_idx;
+ u32 fw_display_id;
+ u32 session_id;
+ bool session_open;
+ /* Protects session_id and session_open. */
+ struct mutex session_lock;
+
+ struct dcp_dsi_hfi_panel_config hfi_panel_config;
+ /* Serializes DCS transfers. */
+ struct mutex dcs_lock;
+
+ void *dcs_tx_buf;
+ dma_addr_t dcs_tx_iova;
+ void *dcs_rx_buf;
+ dma_addr_t dcs_rx_iova;
+ bool dcs_mapped;
+ struct work_struct power_control_work;
+ struct completion power_done;
+ u32 power_packet_id;
+};
+
+#define to_dcp_dsi_host(h) \
+ container_of(h, struct dcp_dsi_bridge, dsi_host)
+
+#define to_dcp_dsi_bridge(b) \
+ container_of(b, struct dcp_dsi_bridge, bridge)
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+ struct device_node *np,
+ struct device_node *dcp_np,
+ u32 dcp_port, u32 intf_idx,
+ u32 fw_display_id);
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge);
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge);
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+ const struct drm_display_mode *mode);
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge);
+
+#endif /* __DCP_DSI_BRIDGE_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
index a8898e469b44..cc4e9f4c6fc4 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
@@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
u32 topology_intf_count;
};
+/**
+ * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
+ *
+ * This state is owned by a DRM atomic bridge state. It is combined with
+ * the static panel configuration when constructing the HFI PANEL_INIT
+ * payload.
+ */
+struct dcp_dsi_panel_state {
+ u32 topology_mixer_count;
+ u32 topology_enc_count;
+ u32 topology_intf_count;
+};
+
/* DCS buffers use IOVAs directly in the firmware command descriptor. */
#define DCP_DSI_DCS_TX_BUF_SIZE SZ_4K
#define DCP_DSI_DCS_RX_BUF_SIZE SZ_4K
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (14 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 13:03 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
` (7 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Connect the DCP DSI host and panel bridge to the DCP KMS
pipeline. Without bridge integration, DRM cannot create display
sessions, attach connectors, or run the bridge enable and disable
sequence for atomic commits.
Set up and tear down the bridge and CRTC relationship, pass panel
configuration through KMS, and connect bridge-backed display operations
to the DCP HFI commands.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/dcp_crtc.c | 14 ++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 40 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 2 ++
3 files changed, 56 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
index ec4f74c2920f..173a07df71c5 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_crtc.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
@@ -147,6 +147,12 @@ static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
return;
}
+ ret = dcp_dpu_kms_prepare_crtc_session(dcp_kms, crtc);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+
if (!dcp_crtc_get_session(crtc, &session_id)) {
cstate->hfi_error = -ENODEV;
return;
@@ -559,6 +565,7 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
const struct drm_plane_state *pstate;
u32 max_width, max_height;
u32 mixer_count;
+ bool needs_dirtyfb;
int num_planes = 0;
int ret;
@@ -569,6 +576,8 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
if (!crtc_state->enable || !crtc_state->active)
return 0;
+ needs_dirtyfb = dcp_dpu_kms_needs_dirtyfb(dcp_kms, crtc);
+
mixer_count = cstate->topology_mixer_count;
if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
drm_err(crtc->dev,
@@ -598,8 +607,13 @@ static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
/* Limit visible planes to the firmware blend-stage count. */
drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ struct dcp_plane_state *dcp_pstate;
+
if (!pstate || !pstate->visible)
continue;
+
+ dcp_pstate = to_dcp_plane_state((struct drm_plane_state *)pstate);
+ dcp_pstate->needs_dirtyfb = needs_dirtyfb;
num_planes++;
}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
index 44b1c2200aee..61a4d8565359 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -7,6 +7,27 @@
#include <drm/drm_crtc.h>
#include "dcp_dpu_kms.h"
+#include "dcp_dsi_bridge.h"
+
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
+ struct drm_crtc *crtc)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+ struct dcp_dsi_bridge *bridge = dcp_kms->dsi_bridge[i];
+
+ if (!bridge)
+ continue;
+ if (dcp_dpu_kms_get_crtc(dcp_kms, bridge->fw_display_id) != crtc)
+ continue;
+
+ return dcp_dsi_bridge_prepare_session(bridge,
+ &crtc->state->adjusted_mode);
+ }
+
+ return -ENODEV;
+}
struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
u32 display_id)
@@ -19,3 +40,22 @@ struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
return NULL;
}
+
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
+ struct drm_crtc *crtc)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+ struct dcp_dsi_bridge *bridge = dcp_kms->dsi_bridge[i];
+
+ if (!bridge ||
+ dcp_dpu_kms_get_crtc(dcp_kms, bridge->fw_display_id) != crtc)
+ continue;
+
+ return bridge->dsi_device &&
+ !(bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+ }
+
+ return false;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
index f8010fc5d32f..333567a78ad7 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -44,6 +44,8 @@ struct dcp_dpu_kms {
#define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
u32 display_id);
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
+ struct drm_crtc *crtc);
int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
struct drm_crtc *crtc);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (15 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 13:24 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
` (6 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Register the DCP display pipeline from firmware capabilities and
the device-tree display graph. DCP exposes display resources through
firmware rather than host-visible display registers, so KMS objects
cannot be registered from a static hardware catalog alone.
Integrate power and IOMMU setup, parse capabilities and display
graph data, and create the DRM planes, CRTCs, encoders, bridges,
and connectors used by the DCP backend.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 625 ++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 3 +
2 files changed, 628 insertions(+)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
index 61a4d8565359..8a73a99b2c76 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -4,10 +4,29 @@
* DCP KMS driver.
*/
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/pm_runtime.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_bridge_connector.h>
#include <drm/drm_crtc.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_of.h>
+#include <drm/drm_plane.h>
+
+#include "msm_drv.h"
+#include "msm_gem.h"
+#include "msm_kms.h"
#include "dcp_dpu_kms.h"
#include "dcp_dsi_bridge.h"
+#include "dcp_crtc.h"
+#include "dcp_drv.h"
+#include "dcp_encoder.h"
+#include "dcp_hfi.h"
+#include "dcp_plane.h"
int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
struct drm_crtc *crtc)
@@ -59,3 +78,609 @@ bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
return false;
}
+
+void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+ if (!dcp_kms->dsi_bridge[i])
+ continue;
+
+ dcp_dsi_bridge_cleanup(dcp_kms->dsi_bridge[i]);
+ dcp_kms->dsi_bridge[i] = NULL;
+ }
+}
+
+static void dcp_dpu_kms_dump_caps(struct dcp_dpu_kms *dcp_kms)
+{
+ const struct dcp_caps *caps = dcp_kms->caps;
+ u32 i;
+
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: hw=%u.%u fw=%u displays=%u\n",
+ caps->dcp_hw_rev, caps->hw_rev, caps->fw_rev,
+ caps->num_displays);
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: max_planes=%u max=%ux%u\n",
+ caps->max_planes, caps->max_width, caps->max_height);
+
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: VIG pipes=%u virt=%u\n",
+ caps->num_vig_pipes, caps->virt_vig_count);
+ for (i = 0; i < min_t(u32, caps->num_vig_pipes,
+ DCP_MAX_SSPP_COUNT); i++)
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: VIG[%u] pipe=%u r1=%u\n", i,
+ caps->vig_pipe_ids[i], caps->vig_r1_indices[i]);
+
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: DMA pipes=%u virt=%u\n",
+ caps->num_dma_pipes, caps->virt_dma_count);
+ for (i = 0; i < min_t(u32, caps->num_dma_pipes,
+ DCP_MAX_SSPP_COUNT); i++)
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: DMA[%u] pipe=%u r1=%u\n", i,
+ caps->dma_pipe_ids[i], caps->dma_r1_indices[i]);
+
+ drm_dbg_driver(dcp_kms->dev, "DCP caps: DSI count=%u\n", caps->dsi_count);
+}
+
+static int dcp_dpu_kms_hw_init(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+ struct drm_device *dev = dcp_kms->dev;
+ struct drm_gpuvm *vm;
+ unsigned int i;
+ int ret;
+
+ kms->irq = IRQ_NOTCONNECTED;
+ dev->mode_config.cursor_width = 512;
+ dev->mode_config.cursor_height = 512;
+
+ ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
+ if (ret < 0) {
+ drm_err(dev, "failed to enable power: %d\n", ret);
+ return ret;
+ }
+
+ vm = msm_kms_init_vm(dev, NULL);
+ if (IS_ERR(vm)) {
+ drm_err(dev, "SMMU/IOMMU init failed: %pe\n", vm);
+ ret = PTR_ERR(vm);
+ goto err_cleanup;
+ }
+ kms->vm = vm;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+ if (!dcp_kms->dsi_bridge[i])
+ continue;
+
+ ret = dcp_dsi_bridge_map_buffers(dcp_kms->dsi_bridge[i]);
+ if (ret) {
+ drm_err(dev, "failed to map DSI%u DCS buffers: %d\n", i, ret);
+ goto err_cleanup;
+ }
+ }
+
+ if (!dcp_kms->hfi) {
+ drm_err(dev, "HFI unavailable, cannot initialize DCP KMS\n");
+ ret = -ENODEV;
+ goto err_cleanup;
+ }
+ if (!dcp_kms->caps) {
+ drm_err(dev, "DCP caps unavailable, cannot initialize DCP KMS\n");
+ ret = -ENODEV;
+ goto err_cleanup;
+ }
+
+ dcp_dpu_kms_dump_caps(dcp_kms);
+
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_width = max(dcp_kms->caps->max_width,
+ dcp_kms->caps->max_height);
+ dev->mode_config.max_height = dev->mode_config.max_width;
+ dev->max_vblank_count = 0xffffffff;
+ dev->vblank_disable_immediate = true;
+
+ ret = dcp_dpu_drm_obj_init(dcp_kms);
+ if (ret) {
+ dev_err_probe(dev->dev, ret, "DRM object init failed\n");
+ goto err_cleanup;
+ }
+
+ pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+ drm_dbg_driver(dev, "DCP hw_init complete\n");
+ return 0;
+
+err_cleanup:
+ for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
+ if (dcp_kms->dsi_bridge[i])
+ dcp_dsi_bridge_unmap_buffers(dcp_kms->dsi_bridge[i]);
+ }
+
+ if (kms->vm) {
+ struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+ if (msm_vm->mmu && msm_vm->mmu->funcs->detach)
+ msm_vm->mmu->funcs->detach(msm_vm->mmu);
+ drm_gpuvm_put(kms->vm);
+ kms->vm = NULL;
+ }
+ pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+ return ret;
+}
+
+static void dcp_dpu_kms_enable_commit(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
+ if (ret < 0) {
+ dcp_kms->commit_pm_active = false;
+ drm_err_ratelimited(dcp_kms->dev,
+ "pm_runtime_resume_and_get failed: %d\n",
+ ret);
+ return;
+ }
+
+ dcp_kms->commit_pm_active = true;
+}
+
+static void dcp_dpu_kms_disable_commit(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+ if (dcp_kms->commit_pm_active) {
+ pm_runtime_put_autosuspend(dcp_kms->pm_dev);
+ dcp_kms->commit_pm_active = false;
+ }
+}
+
+static int dcp_dpu_kms_check_mode_changed(struct msm_kms *kms,
+ struct drm_atomic_commit *state)
+{
+ struct drm_crtc_state *new_crtc_state, *old_crtc_state;
+ struct drm_crtc *crtc;
+ int i;
+
+ for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state,
+ new_crtc_state, i) {
+ if (!drm_atomic_crtc_needs_modeset(new_crtc_state))
+ continue;
+ if (!new_crtc_state->active)
+ continue;
+ new_crtc_state->planes_changed = true;
+ }
+
+ return 0;
+}
+
+static void dcp_dpu_kms_flush_commit(struct msm_kms *kms, unsigned int crtc_mask)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask) {
+ if (!crtc->state || !crtc->state->active)
+ continue;
+ dcp_crtc_commit_kickoff(crtc);
+ }
+}
+
+static void dcp_dpu_kms_wait_flush(struct msm_kms *kms, unsigned int crtc_mask)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask)
+ dcp_crtc_wait_for_commit_done(crtc);
+}
+
+static void dcp_dpu_kms_complete_commit(struct msm_kms *kms, unsigned int crtc_mask)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dcp_kms->dev, crtc, crtc_mask)
+ dcp_crtc_complete_commit(crtc);
+}
+
+static int dcp_dpu_kms_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc)
+{
+ return dcp_crtc_vblank(crtc, true);
+}
+
+static void dcp_dpu_kms_disable_vblank(struct msm_kms *kms,
+ struct drm_crtc *crtc)
+{
+ dcp_crtc_vblank(crtc, false);
+}
+
+static void dcp_dpu_kms_destroy(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+ dcp_dpu_kms_cleanup_bridges(dcp_kms);
+
+ if (kms->vm) {
+ struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+ if (msm_vm->mmu && msm_vm->mmu->funcs->detach) {
+ drm_dbg_driver(dcp_kms->dev,
+ "detaching IOMMU domain from dcp-dpus\n");
+ msm_vm->mmu->funcs->detach(msm_vm->mmu);
+ }
+
+ drm_gpuvm_put(kms->vm);
+ kms->vm = NULL;
+ }
+
+ if (dcp_kms->rpm_enabled) {
+ pm_runtime_disable(dcp_kms->pm_dev);
+ dcp_kms->rpm_enabled = false;
+ }
+
+ msm_kms_destroy(&dcp_kms->base);
+}
+
+static void dcp_dpu_kms_irq_preinstall(struct msm_kms *kms) {}
+static void dcp_dpu_kms_irq_uninstall(struct msm_kms *kms) {}
+
+static irqreturn_t dcp_dpu_kms_irq(struct msm_kms *kms)
+{
+ return IRQ_NONE;
+}
+
+static void dcp_dpu_kms_snapshot(struct msm_disp_state *disp_state,
+ struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
+
+ drm_dbg_driver(dcp_kms->dev,
+ "DCP snapshot: max %ux%u\n",
+ dcp_kms->caps->max_width,
+ dcp_kms->caps->max_height);
+}
+
+const struct msm_kms_funcs dcp_dpu_kms_funcs = {
+ .hw_init = dcp_dpu_kms_hw_init,
+ .irq_preinstall = dcp_dpu_kms_irq_preinstall,
+ .irq_uninstall = dcp_dpu_kms_irq_uninstall,
+ .irq = dcp_dpu_kms_irq,
+ .enable_commit = dcp_dpu_kms_enable_commit,
+ .disable_commit = dcp_dpu_kms_disable_commit,
+ .check_mode_changed = dcp_dpu_kms_check_mode_changed,
+ .flush_commit = dcp_dpu_kms_flush_commit,
+ .wait_flush = dcp_dpu_kms_wait_flush,
+ .complete_commit = dcp_dpu_kms_complete_commit,
+ .enable_vblank = dcp_dpu_kms_enable_vblank,
+ .disable_vblank = dcp_dpu_kms_disable_vblank,
+ .destroy = dcp_dpu_kms_destroy,
+ .snapshot = dcp_dpu_kms_snapshot,
+};
+
+static u32 dcp_dpu_kms_resolve_fw_display_id(struct dcp_dpu_kms *dcp_kms,
+ u32 intf_idx)
+{
+ const struct dcp_caps *caps = dcp_kms->caps;
+ u32 count;
+ const u32 *ids;
+ u32 id;
+
+ count = caps->dsi_count;
+ ids = caps->dsi_indices;
+
+ if (intf_idx >= count) {
+ drm_err(dcp_kms->dev,
+ "DSI%u: no catalog entry (%u)\n", intf_idx, count);
+ return U32_MAX;
+ }
+
+ id = ids[intf_idx];
+
+ if (id > 0xFF) {
+ drm_err(dcp_kms->dev,
+ "DSI%u: catalog id 0x%x exceeds 8-bit range\n",
+ intf_idx, id);
+ return U32_MAX;
+ }
+
+ return id;
+}
+
+int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
+ struct component_match **matchptr)
+{
+ struct device_node *dpu_np = dcp_kms->dpus_np;
+ struct device_node *ep_np, *dsi_np, *child_np;
+ struct of_endpoint ep;
+ int ret;
+
+ for_each_of_graph_port(dpu_np, port_np) {
+ u32 dsi_idx, fw_display_id;
+ struct drm_bridge *bridge;
+
+ ep_np = of_get_next_child(port_np, NULL);
+ if (!ep_np)
+ continue;
+
+ ret = of_graph_parse_endpoint(ep_np, &ep);
+ of_node_put(ep_np);
+ if (ret)
+ continue;
+
+ dsi_idx = ep.port;
+ if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge))
+ return -E2BIG;
+
+ if (dcp_kms->dsi_bridge[dsi_idx])
+ continue;
+
+ dsi_np = NULL;
+ for_each_available_child_of_node(dpu_np, child_np) {
+ u32 reg;
+
+ if (!of_node_name_prefix(child_np, "dsi") ||
+ of_property_read_u32(child_np, "reg", ®) ||
+ reg != dsi_idx)
+ continue;
+
+ dsi_np = of_node_get(child_np);
+ break;
+ }
+ of_node_put(child_np);
+ if (!dsi_np)
+ return -ENODEV;
+
+ fw_display_id =
+ dcp_dpu_kms_resolve_fw_display_id(dcp_kms, dsi_idx);
+ if (fw_display_id == U32_MAX) {
+ of_node_put(dsi_np);
+ return -EINVAL;
+ }
+
+ bridge = dcp_dsi_bridge_init(dcp_kms, dsi_np, dpu_np,
+ ep.port, dsi_idx, fw_display_id);
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ of_node_put(dsi_np);
+ return ret;
+ }
+
+ dcp_kms->dsi_bridge[dsi_idx] = to_dcp_dsi_bridge(bridge);
+ drm_of_component_match_add(dcp_kms->pm_dev, matchptr,
+ component_compare_of, dsi_np);
+ of_node_put(dsi_np);
+ }
+
+ return 0;
+}
+
+static int dcp_dpu_kms_setup_bridges(struct dcp_dpu_kms *dcp_kms,
+ struct device_node *dpu_np)
+{
+ struct drm_device *dev = dcp_kms->dev;
+ struct device_node *ep_np, *remote_np;
+ struct of_endpoint ep;
+ struct drm_encoder *encoder;
+ struct drm_bridge *bridge;
+ struct drm_connector *connector;
+ int ret = 0;
+ int dsi_idx = 0;
+
+ for_each_of_graph_port(dpu_np, port_np) {
+ ep_np = of_get_next_child(port_np, NULL);
+ if (!ep_np)
+ continue;
+
+ ret = of_graph_parse_endpoint(ep_np, &ep);
+ if (ret) {
+ of_node_put(ep_np);
+ continue;
+ }
+
+ remote_np = of_graph_get_remote_port_parent(ep_np);
+ of_node_put(ep_np);
+ if (!remote_np)
+ continue;
+
+ if (!of_device_is_available(remote_np)) {
+ of_node_put(remote_np);
+ continue;
+ }
+
+ dsi_idx = ep.port;
+ if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge)) {
+ of_node_put(remote_np);
+ drm_err(dev, "too many DSI outputs\n");
+ ret = -E2BIG;
+ goto out;
+ }
+
+ drm_dbg_driver(dev, "port@%u: DSI bridge intf_idx=%u\n",
+ ep.port, dsi_idx);
+ if (!dcp_kms->dsi_bridge[dsi_idx]) {
+ of_node_put(remote_np);
+ drm_err(dev, "port@%u: no DSI host for index %u\n",
+ ep.port, dsi_idx);
+ ret = -ENODEV;
+ goto out;
+ }
+ bridge = &dcp_kms->dsi_bridge[dsi_idx]->bridge;
+
+ of_node_put(remote_np);
+
+ encoder = dcp_encoder_init(dev, DRM_MODE_ENCODER_DSI);
+ if (IS_ERR(encoder)) {
+ ret = PTR_ERR(encoder);
+ drm_err(dev, "encoder init failed: %d\n", ret);
+ goto out;
+ }
+
+ ret = drm_bridge_attach(encoder, bridge, NULL,
+ DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+ if (ret) {
+ if (ret == -EPROBE_DEFER) {
+ drm_dbg_driver(dev,
+ "port@%u bridge deferred; panel not ready\n",
+ ep.port);
+ goto out;
+ }
+ drm_err(dev, "bridge attach failed: %d\n", ret);
+ goto out;
+ }
+
+ connector = drm_bridge_connector_init(dev, encoder);
+ if (IS_ERR(connector)) {
+ ret = PTR_ERR(connector);
+ drm_err(dev, "bridge connector init failed: %d\n", ret);
+ goto out;
+ }
+ }
+
+out:
+ if (ret)
+ dcp_dpu_kms_cleanup_bridges(dcp_kms);
+ return ret;
+}
+
+static int dcp_dpu_setup_pipeline(struct dcp_dpu_kms *dcp_kms,
+ struct device_node *dpu_np)
+{
+ struct drm_device *dev = dcp_kms->dev;
+ struct drm_plane *primary_planes[DCP_DPU_MAX_CRTCS];
+ struct drm_plane *plane;
+ struct drm_crtc *crtc;
+ struct drm_encoder *encoder;
+ int primary_idx = 0;
+ int max_crtc_count;
+ int total_planes;
+ int i, ret;
+
+ ret = dcp_dpu_kms_setup_bridges(dcp_kms, dpu_np);
+ if (ret)
+ return dev_err_probe(dev->dev, ret, "bridge setup failed\n");
+
+ max_crtc_count = 0;
+ drm_for_each_encoder(encoder, dev)
+ max_crtc_count++;
+
+ max_crtc_count = min(max_crtc_count, DCP_DPU_MAX_CRTCS);
+ max_crtc_count = min_t(int, max_crtc_count,
+ dcp_kms->caps->num_displays);
+ max_crtc_count = min_t(int, max_crtc_count,
+ ARRAY_SIZE(dcp_kms->dsi_bridge));
+
+ total_planes = min_t(int, dcp_kms->caps->max_planes,
+ DCP_DPU_MAX_CRTCS * DCP_DPU_MAX_PLANES);
+
+ for (i = 0; i < total_planes; i++) {
+ enum drm_plane_type type;
+ unsigned long possible_crtcs;
+
+ possible_crtcs = ((1UL << max_crtc_count) - 1)
+ << dev->mode_config.num_crtc;
+
+ if (primary_idx < max_crtc_count)
+ type = DRM_PLANE_TYPE_PRIMARY;
+ else
+ type = DRM_PLANE_TYPE_OVERLAY;
+
+ plane = dcp_plane_init(dev, U32_MAX, type, possible_crtcs);
+ if (IS_ERR(plane)) {
+ ret = PTR_ERR(plane);
+ drm_err(dev, "plane init failed: %d\n", ret);
+ goto err_cleanup_bridges;
+ }
+
+ if (type == DRM_PLANE_TYPE_PRIMARY)
+ primary_planes[primary_idx++] = plane;
+ }
+
+ max_crtc_count = min(max_crtc_count, primary_idx);
+
+ dcp_kms->num_crtcs = 0;
+ for (i = 0; i < max_crtc_count; i++) {
+ u32 display_id;
+
+ if (!dcp_kms->dsi_bridge[i]) {
+ drm_err(dev, "missing DSI bridge for CRTC%u\n", i);
+ ret = -ENODEV;
+ goto err_cleanup_bridges;
+ }
+
+ display_id = dcp_kms->dsi_bridge[i]->fw_display_id;
+ if (dcp_dpu_kms_get_crtc(dcp_kms, display_id)) {
+ drm_err(dev, "duplicate firmware display ID %u\n",
+ display_id);
+ ret = -EINVAL;
+ goto err_cleanup_bridges;
+ }
+
+ crtc = dcp_crtc_init(dcp_kms, primary_planes[i],
+ display_id);
+ if (IS_ERR(crtc)) {
+ ret = PTR_ERR(crtc);
+ drm_err(dev, "CRTC init failed: %d\n", ret);
+ goto err_cleanup_bridges;
+ }
+ dcp_kms->crtcs[dcp_kms->num_crtcs].crtc = crtc;
+ dcp_kms->crtcs[dcp_kms->num_crtcs++].display_id = display_id;
+ }
+
+ return 0;
+
+err_cleanup_bridges:
+ dcp_dpu_kms_cleanup_bridges(dcp_kms);
+ return ret;
+}
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms)
+{
+ struct drm_device *dev = dcp_kms->dev;
+ struct device_node *dpus_np = dcp_kms->dpus_np;
+ struct drm_encoder *encoder;
+ int enc_idx = 0;
+ int ret;
+
+ if (!dpus_np) {
+ drm_err(dev, "no DCP subsystem node — cannot create DRM objects\n");
+ return -ENODEV;
+ }
+
+ ret = dcp_dpu_setup_pipeline(dcp_kms, dpus_np);
+ if (ret)
+ return dev_err_probe(dev->dev, ret, "pipeline setup failed\n");
+
+ /* v1 - Encoders map one-to-one to CRTCs; cloning is unsupported. */
+ drm_for_each_encoder(encoder, dev) {
+ encoder->possible_crtcs = BIT(enc_idx);
+ encoder->possible_clones = 0;
+ enc_idx++;
+ }
+
+ drm_dbg_driver(dev,
+ "DCP KMS: DPU ready — %d CRTCs, %d planes, %d encoders\n",
+ dev->mode_config.num_crtc,
+ dev->mode_config.num_total_plane,
+ dev->mode_config.num_encoder);
+
+ return 0;
+}
+
+int dcp_kms_init(struct drm_device *ddev)
+{
+ struct msm_drm_private *priv = ddev->dev_private;
+ struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(priv->kms);
+ struct dcp_drv *drv = dcp_kms->drv;
+
+ dcp_kms->dev = ddev;
+ if (drv) {
+ drv->kms = dcp_kms;
+ dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
+ }
+
+ if (!dcp_kms->hfi)
+ return -ENODEV;
+ if (!dcp_kms->caps)
+ return -ENODEV;
+
+ return 0;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
index 333567a78ad7..857ff211e0aa 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -50,6 +50,9 @@ int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
struct drm_crtc *crtc);
int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms);
+void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms);
+int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
+ struct component_match **matchptr);
extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
int dcp_kms_init(struct drm_device *ddev);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (16 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 9:59 ` Dmitry Baryshkov
2026-10-26 10:27 ` Michael S. Tsirkin
2026-10-26 6:21 ` [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper Jayant Shekhar
` (5 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed
as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the
common VirtIO ID header so transport and function drivers use the
shared identifier.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
include/uapi/linux/virtio_ids.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index f9056af0c622..6c6f283733b1 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -70,6 +70,7 @@
#define VIRTIO_ID_GPIO 41 /* virtio gpio */
#define VIRTIO_ID_SPI 45 /* virtio spi */
#define VIRTIO_ID_MEDIA 48 /* virtio media */
+#define VIRTIO_ID_QPSFI 52 /* Qualcomm Platform Services Firmware Interface */
/*
* Virtio Transitional IDs
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (17 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:07 ` Dmitry Baryshkov
2026-10-26 14:05 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
` (4 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Add the Qualcomm-specific VirtIO transport required by DCP firmware.
The transport sends an IPCC power notification before accessing the
VirtIO MMIO registers, then registers a VirtIO device directly.
Implement MMIO configuration access, feature negotiation, virtqueue
setup, interrupt handling, reset, DMA setup, and suspend/resume
sequencing in the transport. Keep power-management ordering in the
transport so VirtIO queues are quiesced before DCP power changes.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_virtio.h | 23 +
drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++
3 files changed, 667 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 9f0a9bfeb2a7..e1a995a8c980 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
dcp/dcp_encoder.o \
+ dcp/dcp_virtio_transport.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
new file mode 100644
index 000000000000..a9c3b9aff61d
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+
+struct dcp_drv;
+struct dcp_ipcc;
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
+
+int dcp_virtio_transport_register(void);
+void dcp_virtio_transport_unregister(void);
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);
+
+#endif /* __DCP_VIRTIO_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
new file mode 100644
index 000000000000..02c0e2a9fafe
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * Qualcomm DCP VirtIO transport.
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/highmem.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <uapi/linux/virtio_mmio.h>
+#include <linux/virtio_ring.h>
+
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+
+#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE
+
+#define to_dcp_virtio_transport(_plat_dev) \
+ container_of(_plat_dev, struct dcp_virtio_transport, vdev)
+
+struct dcp_virtio_transport {
+ struct virtio_device vdev;
+ struct platform_device *pdev;
+
+ void __iomem *base;
+ unsigned long version;
+
+ bool wake_irq_enabled;
+ struct dcp_ipcc ipcc;
+};
+
+static u64 dcp_virtio_get_features(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ u64 features;
+
+ writel(1, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+ features = readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
+ features <<= 32;
+
+ writel(0, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
+ features |= readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
+
+ return features;
+}
+
+static int dcp_virtio_finalize_features(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ vring_transport_features(vdev);
+
+ if (transport->version >= 2 &&
+ !__virtio_test_bit(vdev, VIRTIO_F_VERSION_1)) {
+ dev_err(&vdev->dev, "New virtio-mmio devices (version >= 2) must provide VIRTIO_F_VERSION_1 feature!\n");
+ return -EINVAL;
+ }
+
+ writel(1, transport->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
+ writel((u32)(vdev->features >> 32),
+ transport->base + VIRTIO_MMIO_DRIVER_FEATURES);
+
+ writel(0, transport->base + VIRTIO_MMIO_DRIVER_FEATURES_SEL);
+ writel((u32)vdev->features,
+ transport->base + VIRTIO_MMIO_DRIVER_FEATURES);
+
+ return 0;
+}
+
+static void dcp_virtio_get(struct virtio_device *vdev, unsigned int offset,
+ void *buf, unsigned int len)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ void __iomem *base = transport->base + VIRTIO_MMIO_CONFIG;
+ u8 b;
+ __le16 w;
+ __le32 l;
+
+ if (transport->version == 1) {
+ u8 *ptr = buf;
+ int i;
+
+ for (i = 0; i < len; i++)
+ ptr[i] = readb(base + offset + i);
+ return;
+ }
+
+ switch (len) {
+ case 1:
+ b = readb(base + offset);
+ memcpy(buf, &b, sizeof(b));
+ break;
+ case 2:
+ w = cpu_to_le16(readw(base + offset));
+ memcpy(buf, &w, sizeof(w));
+ break;
+ case 4:
+ l = cpu_to_le32(readl(base + offset));
+ memcpy(buf, &l, sizeof(l));
+ break;
+ case 8:
+ l = cpu_to_le32(readl(base + offset));
+ memcpy(buf, &l, sizeof(l));
+ l = cpu_to_le32(ioread32(base + offset + sizeof(l)));
+ memcpy(buf + sizeof(l), &l, sizeof(l));
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return;
+ }
+}
+
+static void dcp_virtio_set(struct virtio_device *vdev, unsigned int offset,
+ const void *buf, unsigned int len)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ void __iomem *base = transport->base + VIRTIO_MMIO_CONFIG;
+ u8 b;
+ __le16 w;
+ __le32 l;
+
+ if (transport->version == 1) {
+ const u8 *ptr = buf;
+ int i;
+
+ for (i = 0; i < len; i++)
+ writeb(ptr[i], base + offset + i);
+
+ return;
+ }
+
+ switch (len) {
+ case 1:
+ memcpy(&b, buf, sizeof(b));
+ writeb(b, base + offset);
+ break;
+ case 2:
+ memcpy(&w, buf, sizeof(w));
+ writew(le16_to_cpu(w), base + offset);
+ break;
+ case 4:
+ memcpy(&l, buf, sizeof(l));
+ writel(le32_to_cpu(l), base + offset);
+ break;
+ case 8:
+ memcpy(&l, buf, sizeof(l));
+ writel(le32_to_cpu(l), base + offset);
+ memcpy(&l, buf + sizeof(l), sizeof(l));
+ writel(le32_to_cpu(l), base + offset + sizeof(l));
+ break;
+ default:
+ WARN_ON_ONCE(1);
+ return;
+ }
+}
+
+static u32 dcp_virtio_generation(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ if (transport->version == 1)
+ return 0;
+ else
+ return readl(transport->base + VIRTIO_MMIO_CONFIG_GENERATION);
+}
+
+static u8 dcp_virtio_get_status(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ return readl(transport->base + VIRTIO_MMIO_STATUS) & 0xff;
+}
+
+static void dcp_virtio_set_status(struct virtio_device *vdev, u8 status)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ if (WARN_ON_ONCE(!status))
+ return;
+
+ writel(status, transport->base + VIRTIO_MMIO_STATUS);
+}
+
+static void dcp_virtio_reset(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ writel(0, transport->base + VIRTIO_MMIO_STATUS);
+
+ if (transport->version >= 3) {
+ while (dcp_virtio_get_status(vdev))
+ fsleep(1000);
+ }
+}
+
+static bool dcp_virtio_notify(struct virtqueue *vq)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vq->vdev);
+
+ writel(vq->index, transport->base + VIRTIO_MMIO_QUEUE_NOTIFY);
+ return true;
+}
+
+static bool dcp_virtio_notify_with_data(struct virtqueue *vq)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vq->vdev);
+ u32 data = vring_notification_data(vq);
+
+ writel(data, transport->base + VIRTIO_MMIO_QUEUE_NOTIFY);
+
+ return true;
+}
+
+static irqreturn_t dcp_virtio_interrupt(int irq, void *opaque)
+{
+ struct dcp_virtio_transport *transport = opaque;
+ struct virtqueue *vq;
+ unsigned long status;
+ irqreturn_t ret = IRQ_NONE;
+
+ status = readl(transport->base + VIRTIO_MMIO_INTERRUPT_STATUS);
+ writel(status, transport->base + VIRTIO_MMIO_INTERRUPT_ACK);
+
+ if (unlikely(status & VIRTIO_MMIO_INT_CONFIG)) {
+ virtio_config_changed(&transport->vdev);
+ ret = IRQ_HANDLED;
+ }
+
+ if (likely(status & VIRTIO_MMIO_INT_VRING)) {
+ virtio_device_for_each_vq(&transport->vdev, vq)
+ ret |= vring_interrupt(irq, vq);
+ }
+
+ return ret;
+}
+
+static void dcp_virtio_del_vq(struct virtqueue *vq)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vq->vdev);
+ unsigned int index = vq->index;
+
+ writel(index, transport->base + VIRTIO_MMIO_QUEUE_SEL);
+ if (transport->version == 1) {
+ writel(0, transport->base + VIRTIO_MMIO_QUEUE_PFN);
+ } else {
+ writel(0, transport->base + VIRTIO_MMIO_QUEUE_READY);
+ WARN_ON(readl(transport->base + VIRTIO_MMIO_QUEUE_READY));
+ }
+
+ vring_del_virtqueue(vq);
+}
+
+static void dcp_virtio_del_vqs(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ struct virtqueue *vq, *n;
+ int irq = platform_get_irq(transport->pdev, 0);
+
+ list_for_each_entry_safe(vq, n, &vdev->vqs, list)
+ dcp_virtio_del_vq(vq);
+
+ if (transport->wake_irq_enabled) {
+ disable_irq_wake(irq);
+ transport->wake_irq_enabled = false;
+ }
+
+ free_irq(irq, transport);
+}
+
+static void dcp_virtio_synchronize_cbs(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ synchronize_irq(platform_get_irq(transport->pdev, 0));
+}
+
+static struct virtqueue *dcp_virtio_setup_vq(struct virtio_device *vdev, unsigned int index,
+ void (*callback)(struct virtqueue *vq),
+ const char *name, bool ctx)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ bool (*notify)(struct virtqueue *vq);
+ struct virtqueue *vq;
+ unsigned int num;
+ int err;
+
+ if (__virtio_test_bit(vdev, VIRTIO_F_NOTIFICATION_DATA))
+ notify = dcp_virtio_notify_with_data;
+ else
+ notify = dcp_virtio_notify;
+
+ writel(index, transport->base + VIRTIO_MMIO_QUEUE_SEL);
+
+ if (readl(transport->base + (transport->version == 1 ?
+ VIRTIO_MMIO_QUEUE_PFN : VIRTIO_MMIO_QUEUE_READY))) {
+ err = -ENOENT;
+ goto error_available;
+ }
+
+ num = readl(transport->base + VIRTIO_MMIO_QUEUE_NUM_MAX);
+ if (num == 0) {
+ err = -ENOENT;
+ goto error_new_virtqueue;
+ }
+
+ vq = vring_create_virtqueue(index, num, VIRTIO_MMIO_VRING_ALIGN, vdev,
+ true, true, ctx, notify, callback, name);
+ if (!vq) {
+ err = -ENOMEM;
+ goto error_new_virtqueue;
+ }
+
+ vq->num_max = num;
+
+ writel(virtqueue_get_vring_size(vq), transport->base + VIRTIO_MMIO_QUEUE_NUM);
+ if (transport->version == 1) {
+ u64 q_pfn = virtqueue_get_desc_addr(vq) >> PAGE_SHIFT;
+
+ if (q_pfn >> 32) {
+ dev_err(&vdev->dev,
+ "platform bug: legacy virtio-mmio must not be used with RAM above 0x%llxGB\n",
+ 0x1ULL << (32 + PAGE_SHIFT - 30));
+ err = -E2BIG;
+ goto error_bad_pfn;
+ }
+
+ writel(PAGE_SIZE, transport->base + VIRTIO_MMIO_QUEUE_ALIGN);
+ writel(q_pfn, transport->base + VIRTIO_MMIO_QUEUE_PFN);
+ } else {
+ u64 addr;
+
+ addr = virtqueue_get_desc_addr(vq);
+ writel((u32)addr, transport->base + VIRTIO_MMIO_QUEUE_DESC_LOW);
+ writel((u32)(addr >> 32),
+ transport->base + VIRTIO_MMIO_QUEUE_DESC_HIGH);
+
+ addr = virtqueue_get_avail_addr(vq);
+ writel((u32)addr, transport->base + VIRTIO_MMIO_QUEUE_AVAIL_LOW);
+ writel((u32)(addr >> 32),
+ transport->base + VIRTIO_MMIO_QUEUE_AVAIL_HIGH);
+
+ addr = virtqueue_get_used_addr(vq);
+ writel((u32)addr, transport->base + VIRTIO_MMIO_QUEUE_USED_LOW);
+ writel((u32)(addr >> 32),
+ transport->base + VIRTIO_MMIO_QUEUE_USED_HIGH);
+
+ writel(1, transport->base + VIRTIO_MMIO_QUEUE_READY);
+ }
+
+ return vq;
+
+error_bad_pfn:
+ vring_del_virtqueue(vq);
+error_new_virtqueue:
+ if (transport->version == 1) {
+ writel(0, transport->base + VIRTIO_MMIO_QUEUE_PFN);
+ } else {
+ writel(0, transport->base + VIRTIO_MMIO_QUEUE_READY);
+ WARN_ON(readl(transport->base + VIRTIO_MMIO_QUEUE_READY));
+ }
+error_available:
+ return ERR_PTR(err);
+}
+
+static int dcp_virtio_find_vqs(struct virtio_device *vdev, unsigned int nvqs,
+ struct virtqueue *vqs[],
+ struct virtqueue_info vqs_info[],
+ struct irq_affinity *desc)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ int irq = platform_get_irq(transport->pdev, 0);
+ int i, err, queue_idx = 0;
+
+ if (irq < 0)
+ return irq;
+
+ err = request_irq(irq, dcp_virtio_interrupt, IRQF_SHARED,
+ dev_name(&vdev->dev), transport);
+ if (err)
+ return err;
+
+ if (of_property_read_bool(transport->pdev->dev.of_node, "wakeup-source") &&
+ !enable_irq_wake(irq))
+ transport->wake_irq_enabled = true;
+
+ for (i = 0; i < nvqs; ++i) {
+ struct virtqueue_info *vqi = &vqs_info[i];
+
+ if (!vqi->name) {
+ vqs[i] = NULL;
+ continue;
+ }
+
+ vqs[i] = dcp_virtio_setup_vq(vdev, queue_idx++, vqi->callback,
+ vqi->name, vqi->ctx);
+ if (IS_ERR(vqs[i])) {
+ dcp_virtio_del_vqs(vdev);
+ return PTR_ERR(vqs[i]);
+ }
+ }
+
+ return 0;
+}
+
+static const char *dcp_virtio_bus_name(struct virtio_device *vdev)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+
+ return transport->pdev->name;
+}
+
+static bool dcp_virtio_get_shm_region(struct virtio_device *vdev,
+ struct virtio_shm_region *region, u8 id)
+{
+ struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
+ u64 len, addr;
+
+ writel(id, transport->base + VIRTIO_MMIO_SHM_SEL);
+
+ len = (u64)readl(transport->base + VIRTIO_MMIO_SHM_LEN_LOW);
+ len |= (u64)readl(transport->base + VIRTIO_MMIO_SHM_LEN_HIGH) << 32;
+
+ region->len = len;
+
+ if (len == ~(u64)0)
+ return false;
+
+ addr = (u64)readl(transport->base + VIRTIO_MMIO_SHM_BASE_LOW);
+ addr |= (u64)readl(transport->base + VIRTIO_MMIO_SHM_BASE_HIGH) << 32;
+
+ region->addr = addr;
+
+ return true;
+}
+
+static const struct virtio_config_ops dcp_virtio_config_ops = {
+ .get = dcp_virtio_get,
+ .set = dcp_virtio_set,
+ .generation = dcp_virtio_generation,
+ .get_status = dcp_virtio_get_status,
+ .set_status = dcp_virtio_set_status,
+ .reset = dcp_virtio_reset,
+ .find_vqs = dcp_virtio_find_vqs,
+ .del_vqs = dcp_virtio_del_vqs,
+ .get_features = dcp_virtio_get_features,
+ .finalize_features = dcp_virtio_finalize_features,
+ .bus_name = dcp_virtio_bus_name,
+ .get_shm_region = dcp_virtio_get_shm_region,
+ .synchronize_cbs = dcp_virtio_synchronize_cbs,
+};
+
+static void dcp_virtio_release_dev(struct device *dev)
+{
+}
+
+struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev)
+{
+ return &to_dcp_virtio_transport(vdev)->ipcc;
+}
+
+static int dcp_virtio_transport_probe(struct platform_device *pdev)
+{
+ struct dcp_virtio_transport *transport;
+ unsigned long magic;
+ int ret;
+
+ transport = devm_kzalloc(&pdev->dev, sizeof(*transport), GFP_KERNEL);
+ if (!transport)
+ return -ENOMEM;
+
+ transport->pdev = pdev;
+ transport->vdev.dev.parent = &pdev->dev;
+ transport->vdev.dev.release = dcp_virtio_release_dev;
+ transport->vdev.config = &dcp_virtio_config_ops;
+ platform_set_drvdata(pdev, transport);
+
+ ret = dcp_ipcc_init(&transport->ipcc, &pdev->dev, 1);
+ if (ret)
+ goto err_free;
+
+ ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+ if (ret)
+ goto err_ipcc;
+
+ transport->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(transport->base)) {
+ ret = PTR_ERR(transport->base);
+ goto err_node;
+ }
+
+ magic = readl(transport->base + VIRTIO_MMIO_MAGIC_VALUE);
+ if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
+ dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
+ ret = -ENODEV;
+ goto err_node;
+ }
+
+ transport->version = readl(transport->base + VIRTIO_MMIO_VERSION);
+ if (transport->version < 1 || transport->version > 3) {
+ dev_err(&pdev->dev, "Version %ld not supported!\n",
+ transport->version);
+ ret = -ENXIO;
+ goto err_node;
+ }
+
+ transport->vdev.id.device =
+ readl(transport->base + VIRTIO_MMIO_DEVICE_ID);
+ if (!transport->vdev.id.device) {
+ ret = -ENODEV;
+ goto err_node;
+ }
+ transport->vdev.id.vendor =
+ readl(transport->base + VIRTIO_MMIO_VENDOR_ID);
+
+ if (transport->version == 1) {
+ writel(PAGE_SIZE,
+ transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+ ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
+ if (!ret)
+ ret = dma_set_coherent_mask(&pdev->dev,
+ DMA_BIT_MASK(32 + PAGE_SHIFT));
+ } else {
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ }
+ if (ret)
+ ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+ if (ret)
+ dev_warn(&pdev->dev,
+ "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
+
+ ret = register_virtio_device(&transport->vdev);
+ if (ret) {
+ put_device(&transport->vdev.dev);
+ dcp_ipcc_deinit(&transport->ipcc);
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+ }
+
+ return 0;
+
+err_node:
+err_ipcc:
+ dcp_ipcc_deinit(&transport->ipcc);
+err_free:
+ platform_set_drvdata(pdev, NULL);
+ return ret;
+}
+
+static void dcp_virtio_transport_remove(struct platform_device *pdev)
+{
+ struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
+
+ unregister_virtio_device(&transport->vdev);
+ platform_set_drvdata(pdev, NULL);
+ dcp_ipcc_deinit(&transport->ipcc);
+}
+
+static int dcp_virtio_transport_suspend(struct device *dev)
+{
+ struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+ int ret;
+
+ ret = virtio_device_freeze(&transport->vdev);
+ if (ret)
+ return ret;
+
+ ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
+ if (ret)
+ goto err_restore;
+
+ ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+ if (ret) {
+ dcp_ipcc_disable_irq_wake(&transport->ipcc);
+ goto err_restore;
+ }
+
+ return 0;
+
+err_restore:
+ virtio_device_restore(&transport->vdev);
+ return ret;
+}
+
+static int dcp_virtio_transport_resume(struct device *dev)
+{
+ struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
+ int ret;
+
+ ret = dcp_ipcc_send_power_notify(&transport->ipcc);
+ if (ret)
+ return ret;
+
+ dcp_ipcc_disable_irq_wake(&transport->ipcc);
+ if (transport->version == 1)
+ writel(PAGE_SIZE,
+ transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
+
+ return virtio_device_restore(&transport->vdev);
+}
+
+static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
+ .suspend = dcp_virtio_transport_suspend,
+ .resume = dcp_virtio_transport_resume,
+};
+
+static const struct of_device_id dcp_virtio_transport_of_match[] = {
+ { .compatible = "qcom,dcp-virtio" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
+
+static struct platform_driver dcp_virtio_transport_driver = {
+ .probe = dcp_virtio_transport_probe,
+ .remove = dcp_virtio_transport_remove,
+ .driver = {
+ .name = "qcom-dcp-virtio",
+ .of_match_table = dcp_virtio_transport_of_match,
+ .pm = &dcp_virtio_transport_pm_ops,
+ },
+};
+
+int dcp_virtio_transport_register(void)
+{
+ return platform_driver_register(&dcp_virtio_transport_driver);
+}
+
+void dcp_virtio_transport_unregister(void)
+{
+ platform_driver_unregister(&dcp_virtio_transport_driver);
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (18 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 10:15 ` Dmitry Baryshkov
2026-10-26 14:31 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
` (3 subsequent siblings)
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Add the DCP function driver that binds the VirtIO device to MSM
DRM. Without a function driver, Linux cannot establish HFI queues,
discover firmware capabilities, or register the firmware-owned
display backend.
Set up the DCP DMA and IOMMU identity, initialize VirtIO and HFI
transport, register the MSM DRM component, and synchronize suspend,
resume, remove, and shutdown with in-flight HFI activity.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/Makefile | 2 +
drivers/gpu/drm/msm/dcp/dcp_drv.c | 191 ++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_drv.h | 17 +++
drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_virtio.h | 2 +
drivers/gpu/drm/msm/msm_drv.c | 6 +-
drivers/gpu/drm/msm/msm_drv.h | 2 +
7 files changed, 475 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index e1a995a8c980..ffdb159dd9ad 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -85,6 +85,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
dcp/dcp_encoder.o \
+ dcp/dcp_drv.o \
+ dcp/dcp_virtio.o \
dcp/dcp_virtio_transport.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c
new file mode 100644
index 000000000000..8c38de883bf6
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO driver.
+ */
+
+#include <linux/component.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/panic_notifier.h>
+#include <linux/pm_runtime.h>
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+int dcp_subsystem_dev_init(struct dcp_drv *drv)
+{
+ struct virtio_device *vdev = drv->vdev;
+ struct device_node *dcp_subsystem_node;
+ struct platform_device *pdev;
+ struct device *dev;
+ char busid[32];
+ bool iommu_mapped;
+ int ret;
+
+ dcp_subsystem_node = of_node_get(vdev->dev.of_node);
+ if (!dcp_subsystem_node) {
+ dev_err(&vdev->dev, "no DCP subsystem DT node\n");
+ return -ENODEV;
+ }
+
+ snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
+
+ /*
+ * DCP is discovered through VirtIO, but display/HFI buffers must be
+ * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
+ * platform device for that DT node so DMA API and msm_kms_init_vm() use
+ * the display function's IOMMU domain.
+ */
+ pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
+ if (!pdev) {
+ dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
+ of_node_put(dcp_subsystem_node);
+ return -ENODEV;
+ }
+ dev = &pdev->dev;
+
+ dev->dma_parms = &drv->dcp_subsystem_dma_parms;
+ dma_set_max_seg_size(dev, UINT_MAX);
+
+ ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
+ if (ret) {
+ dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
+ goto err_destroy;
+ }
+
+ iommu_mapped = device_iommu_mapped(dev);
+ if (!iommu_mapped) {
+ dev_err(dev, "no IOMMU group attached\n");
+ ret = -ENODEV;
+ goto err_destroy;
+ }
+
+ dev_dbg(dev, "dcp-subsystem: iommu_mapped=%d iommu_group=%d\n",
+ iommu_mapped,
+ dev->iommu_group ? iommu_group_id(dev->iommu_group) : -1);
+
+ pm_runtime_set_active(dev);
+ pm_runtime_enable(dev);
+ pm_runtime_set_autosuspend_delay(dev, DCP_DPU_AUTOSUSPEND_DELAY_MS);
+ pm_runtime_use_autosuspend(dev);
+
+ drv->dcp_subsystem_dev = pdev;
+ drv->dcp_subsystem_node = dcp_subsystem_node;
+
+ return 0;
+
+err_destroy:
+ of_platform_device_destroy(dev, NULL);
+ of_node_put(dcp_subsystem_node);
+ return ret;
+}
+
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
+{
+ if (!drv->dcp_subsystem_dev)
+ return;
+
+ pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
+ of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
+ of_node_put(drv->dcp_subsystem_node);
+ drv->dcp_subsystem_dev = NULL;
+}
+
+int dcp_pm_prepare(struct device *vdev_dev)
+{
+ struct dcp_drv *drv = vdev_dev ?
+ dev_to_virtio(vdev_dev)->priv : NULL;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return 0;
+
+ return msm_kms_pm_prepare(&drv->dcp_subsystem_dev->dev);
+}
+
+void dcp_pm_complete(struct device *vdev_dev)
+{
+ struct dcp_drv *drv = vdev_dev ?
+ dev_to_virtio(vdev_dev)->priv : NULL;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return;
+
+ msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
+}
+
+int dcp_component_register(struct dcp_drv *drv)
+{
+ struct device *dev = &drv->dcp_subsystem_dev->dev;
+ struct component_match *match = NULL;
+ int ret;
+
+ ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
+ if (ret) {
+ dev_err(dev, "DSI host initialization failed: %d\n", ret);
+ dcp_dpu_kms_cleanup_bridges(drv->kms);
+ return ret;
+ }
+
+ /* A GPU is optional for the DCP display-only DRM device. */
+ msm_add_gpu_components(dev, &match);
+
+ ret = component_master_add_with_match(dev, &msm_drm_ops, match);
+ if (ret) {
+ dev_err(dev, "component_master_add_with_match failed: %d\n",
+ ret);
+ dcp_dpu_kms_cleanup_bridges(drv->kms);
+ return ret;
+ }
+
+ return 0;
+}
+
+void dcp_component_unregister(struct dcp_drv *drv)
+{
+ struct device *dev = &drv->dcp_subsystem_dev->dev;
+
+ component_master_del(dev, &msm_drm_ops);
+ dcp_dpu_kms_cleanup_bridges(drv->kms);
+}
+
+static int dcp_panic_notifier_cb(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct dcp_drv *drv = container_of(nb, struct dcp_drv, panic_notifier);
+
+ if (drv->hfi)
+ atomic_set(&drv->hfi->shutdown_in_progress, 1);
+
+ return NOTIFY_OK;
+}
+
+void dcp_panic_notifier_register(struct dcp_drv *drv)
+{
+ drv->panic_notifier.notifier_call = dcp_panic_notifier_cb;
+ atomic_notifier_chain_register(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void dcp_panic_notifier_unregister(struct dcp_drv *drv)
+{
+ atomic_notifier_chain_unregister(&panic_notifier_list, &drv->panic_notifier);
+}
+
+void __init msm_dcp_register(void)
+{
+ dcp_virtio_register();
+}
+
+void __exit msm_dcp_unregister(void)
+{
+ dcp_virtio_unregister();
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h
index 16f87035cb9a..3adc5855a9a2 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -14,6 +14,7 @@
#include "dcp_ipcc.h"
struct dcp_dpu_kms;
+struct component_match;
struct dcp_drv {
struct virtio_device *vdev;
@@ -24,6 +25,22 @@ struct dcp_drv {
struct dcp_dpu_kms *kms;
struct dcp_caps caps;
struct notifier_block panic_notifier;
+ struct dcp_ipcc *ipcc;
};
+int dcp_subsystem_dev_init(struct dcp_drv *drv);
+void dcp_subsystem_dev_deinit(struct dcp_drv *drv);
+
+int dcp_component_register(struct dcp_drv *drv);
+void dcp_component_unregister(struct dcp_drv *drv);
+
+int dcp_pm_prepare(struct device *vdev_dev);
+void dcp_pm_complete(struct device *vdev_dev);
+
+void dcp_panic_notifier_register(struct dcp_drv *drv);
+void dcp_panic_notifier_unregister(struct dcp_drv *drv);
+
+void __init msm_dcp_register(void);
+void __exit msm_dcp_unregister(void);
+
#endif /* __DCP_DRV_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.c b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
new file mode 100644
index 000000000000..15d5caf02ff5
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO device driver.
+ */
+
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/pm_runtime.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/slab.h>
+
+#include "dcp_drv.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "dcp_ipcc.h"
+#include "dcp_virtio.h"
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv);
+
+static const unsigned int dcp_virtio_features[] = {
+ VIRTIO_F_VERSION_1,
+ VIRTIO_F_NOTIFICATION_DATA,
+ VIRTIO_F_RING_RESET,
+ VIRTIO_F_ACCESS_PLATFORM,
+};
+
+/*
+ * The VirtIO core owns transport discovery. This driver owns HFI setup and
+ * creates the DRM component only after firmware capabilities are available.
+ */
+static int dcp_virtio_probe(struct virtio_device *vdev)
+{
+ struct dcp_drv *drv;
+ struct virtqueue *vqs[DCP_NUM_VQS];
+ struct virtqueue_info vqs_info[DCP_NUM_VQS] = {
+ [DCP_VQ_CMD] = { .name = "cmd", .callback = dcp_hfi_cmd_callback },
+ [DCP_VQ_RESP] = { .name = "resp", .callback = dcp_hfi_resp_callback },
+ };
+ struct dcp_dpu_kms *dcp_kms;
+ struct msm_drm_private *priv;
+ int ret;
+
+ dev_dbg(&vdev->dev, "DCP VirtIO probe started\n");
+
+ drv = devm_kzalloc(&vdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ drv->vdev = vdev;
+ vdev->priv = drv;
+
+ if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
+ dev_err(&vdev->dev, "VIRTIO_F_VERSION_1 not negotiated\n");
+ return -ENODEV;
+ }
+
+ drv->ipcc = dcp_virtio_get_ipcc(vdev);
+ /* Access-platform is required for the firmware-visible IOVA mappings. */
+ if (!virtio_has_feature(vdev, VIRTIO_F_ACCESS_PLATFORM)) {
+ dev_err(&vdev->dev, "VIRTIO_F_ACCESS_PLATFORM not negotiated\n");
+ return -ENODEV;
+ }
+
+ ret = dcp_subsystem_dev_init(drv);
+ if (ret)
+ return ret;
+
+ ret = virtio_find_vqs(vdev, DCP_NUM_VQS, vqs, vqs_info, NULL);
+ if (ret) {
+ dev_err(&vdev->dev, "virtqueue allocation failed: %d\n", ret);
+ goto err_fn_dev;
+ }
+
+ ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
+ vdev->dev.parent);
+ if (ret) {
+ dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
+ goto err_del_vqs;
+ }
+
+ dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
+
+ /*
+ * Keep the device unready until HFI state and virtqueues are initialized.
+ * KMS registration below depends on synchronous firmware initialization.
+ */
+ virtio_device_ready(vdev);
+ virtqueue_kick(drv->hfi->resp_vq);
+
+ ret = dcp_hfi_send_device_init(drv->hfi);
+ if (ret) {
+ dev_err(&vdev->dev, "HFI DEVICE_INIT failed: %d\n", ret);
+ goto err_hfi_deinit;
+ }
+
+ ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
+ if (ret) {
+ dev_err(&vdev->dev, "HFI GET_CAPS failed: %d\n", ret);
+ goto err_hfi_deinit;
+ }
+
+ /* Register the display resources before exposing the DRM component. */
+ ret = dcp_hfi_send_resource_register(drv->hfi);
+ if (ret) {
+ dev_err(&vdev->dev, "HFI RESOURCE_REGISTER failed: %d\n", ret);
+ goto err_hfi_deinit;
+ }
+
+ dev_dbg(&vdev->dev,
+ "DCP caps: %u displays, max %ux%u, %u planes\n",
+ drv->caps.num_displays, drv->caps.max_width, drv->caps.max_height,
+ drv->caps.max_planes);
+
+ dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
+ if (!dcp_kms) {
+ ret = -ENOMEM;
+ goto err_hfi_deinit;
+ }
+
+ dcp_kms->drv = drv;
+ dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
+ dcp_kms->hfi = drv->hfi;
+ dcp_kms->caps = &drv->caps;
+ dcp_kms->dpus_np = drv->dcp_subsystem_node;
+ dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+ drv->kms = dcp_kms;
+
+ ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
+ if (ret) {
+ dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
+ goto err_hfi_deinit;
+ }
+
+ priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto err_hfi_deinit;
+ }
+
+ priv->kms = &dcp_kms->base;
+ priv->kms_init = dcp_kms_init;
+ dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
+ dcp_kms->rpm_enabled = true;
+
+ /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
+ ret = dcp_component_register(drv);
+ if (ret) {
+ dev_err_probe(&vdev->dev, ret, "component register failed\n");
+ goto err_hfi_deinit;
+ }
+
+ dcp_panic_notifier_register(drv);
+
+ dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
+ return 0;
+
+err_hfi_deinit:
+ dcp_virtio_hfi_cleanup(drv);
+ goto err_fn_dev;
+err_del_vqs:
+ virtio_reset_device(vdev);
+ vdev->config->del_vqs(vdev);
+err_fn_dev:
+ dcp_subsystem_dev_deinit(drv);
+ return ret;
+}
+
+static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
+{
+ struct virtio_device *vdev = drv->vdev;
+
+ if (!drv->hfi)
+ return;
+
+ dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
+
+ dcp_hfi_stop(drv->hfi);
+ virtio_reset_device(vdev);
+ virtio_synchronize_cbs(vdev);
+ dcp_hfi_quiesce_queues(drv->hfi);
+ vdev->config->del_vqs(vdev);
+ dcp_hfi_deinit(drv->hfi);
+}
+
+/* Remove DRM users before tearing down HFI, IPCC, or the VirtIO queues. */
+static void dcp_virtio_remove(struct virtio_device *vdev)
+{
+ struct dcp_drv *drv = vdev->priv;
+
+ dev_dbg(&vdev->dev, "DCP VirtIO remove\n");
+
+ dcp_component_unregister(drv);
+ dcp_panic_notifier_unregister(drv);
+
+ dcp_virtio_hfi_cleanup(drv);
+
+ dcp_subsystem_dev_deinit(drv);
+}
+
+/* KMS shutdown must complete while the firmware transport is available. */
+static void dcp_virtio_shutdown(struct virtio_device *vdev)
+{
+ struct dcp_drv *drv = vdev->priv;
+
+ pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
+ msm_kms_shutdown(drv->dcp_subsystem_dev);
+ pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
+}
+
+static const struct virtio_device_id dcp_virtio_id_table[] = {
+ { VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },
+ { 0 },
+};
+MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
+
+static const struct dev_pm_ops dcp_virtio_pm_ops = {
+ .prepare = dcp_pm_prepare,
+ .complete = dcp_pm_complete,
+};
+
+static struct virtio_driver dcp_virtio_driver = {
+ .driver = {
+ .name = "qcom-dcp-hfi",
+ .owner = THIS_MODULE,
+ .pm = &dcp_virtio_pm_ops,
+ },
+ .id_table = dcp_virtio_id_table,
+ .feature_table = dcp_virtio_features,
+ .feature_table_size = ARRAY_SIZE(dcp_virtio_features),
+ .probe = dcp_virtio_probe,
+ .remove = dcp_virtio_remove,
+ .shutdown = dcp_virtio_shutdown,
+};
+
+int dcp_virtio_register(void)
+{
+ int ret;
+
+ ret = dcp_virtio_transport_register();
+ if (ret)
+ return ret;
+
+ ret = register_virtio_driver(&dcp_virtio_driver);
+ if (ret)
+ dcp_virtio_transport_unregister();
+
+ return ret;
+}
+
+void dcp_virtio_unregister(void)
+{
+ unregister_virtio_driver(&dcp_virtio_driver);
+ dcp_virtio_transport_unregister();
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
index a9c3b9aff61d..3ded8293ab18 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_virtio.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
@@ -8,9 +8,11 @@
#include <linux/device.h>
#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
struct dcp_drv;
struct dcp_ipcc;
+#define VIRTIO_VENDOR_ID_DCP_HFI 1
struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 5e8f3ce26b32..5816c64d7ce7 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1093,8 +1093,8 @@ static const struct of_device_id msm_gpu_match[] = {
{ },
};
-static int add_gpu_components(struct device *dev,
- struct component_match **matchptr)
+int msm_add_gpu_components(struct device *dev,
+ struct component_match **matchptr)
{
struct device_node *np;
@@ -1153,7 +1153,7 @@ int msm_drv_probe(struct device *master_dev,
}
if (!msm_separate_gpu_kms_components()) {
- ret = add_gpu_components(master_dev, &match);
+ ret = msm_add_gpu_components(master_dev, &match);
if (ret)
return ret;
}
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index 2840e8a5052d..aa5dfd82ac6f 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -523,6 +523,8 @@ static inline unsigned long timeout_to_jiffies(const ktime_t *timeout)
/* Driver helpers */
extern const struct component_master_ops msm_drm_ops;
+int msm_add_gpu_components(struct device *dev,
+ struct component_match **matchptr);
int msm_kms_pm_prepare(struct device *dev);
void msm_kms_pm_complete(struct device *dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (19 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-27 11:37 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 22/23] drm/msm: Register the DCP backend Jayant Shekhar
` (2 subsequent siblings)
23 siblings, 1 reply; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Some MSM KMS backends receive display events through firmware or
another transport and therefore have no host-visible display interrupt.
Rejecting such a backend during common KMS initialization prevents
it from using the MSM DRM framework.
Allow KMS backends to use IRQ_NOTCONNECTED. DCP receives VSYNC and
frame-scan completion as asynchronous HFI events over VirtIO; its IPCC
interrupt is reserved for power-notify acknowledgments. Existing
IRQ-backed KMS implementations retain their current request and
teardown flow.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/msm_kms.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/msm_kms.c b/drivers/gpu/drm/msm/msm_kms.c
index e5d0ea629448..1870a7008ead 100644
--- a/drivers/gpu/drm/msm/msm_kms.c
+++ b/drivers/gpu/drm/msm/msm_kms.c
@@ -72,7 +72,7 @@ static int msm_irq_install(struct drm_device *dev, unsigned int irq)
int ret;
if (irq == IRQ_NOTCONNECTED)
- return -ENOTCONN;
+ return 0;
msm_irq_preinstall(dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 22/23] drm/msm: Register the DCP backend
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (20 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 14:58 ` sashiko-bot
2026-10-27 11:38 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
2026-10-27 11:58 ` [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Dmitry Baryshkov
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
The DCP platform driver provides an MSM KMS backend. Register it from the
MSM DRM driver init and exit paths so the VirtIO driver can use the backend
when the DCP function is discovered.
Add DCP registration hooks with no-op stubs when CONFIG_DRM_MSM_DCP is
disabled. This keeps backend registration consistent with the other MSM
display backends while allowing DCP support to remain optional.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
drivers/gpu/drm/msm/msm_drv.c | 2 ++
drivers/gpu/drm/msm/msm_drv.h | 8 ++++++++
2 files changed, 10 insertions(+)
diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
index 5816c64d7ce7..d413c361c442 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
DBG("init");
msm_mdp_register();
msm_dpu_register();
+ msm_dcp_register();
msm_dsi_register();
msm_hdmi_register();
msm_dp_register();
@@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
msm_dsi_unregister();
msm_mdp_unregister();
msm_dpu_unregister();
+ msm_dcp_unregister();
}
module_init(msm_drm_register);
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index aa5dfd82ac6f..befaa9f2cb5c 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
static inline void msm_dpu_unregister(void) {}
#endif
+#ifdef CONFIG_DRM_MSM_DCP
+void msm_dcp_register(void);
+void msm_dcp_unregister(void);
+#else
+static inline void msm_dcp_register(void) {}
+static inline void msm_dcp_unregister(void) {}
+#endif
+
#ifdef CONFIG_DRM_MSM_MDSS
void msm_mdss_register(void);
void msm_mdss_unregister(void);
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (21 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 22/23] drm/msm: Register the DCP backend Jayant Shekhar
@ 2026-10-26 6:21 ` Jayant Shekhar
2026-10-26 15:10 ` sashiko-bot
2026-10-27 11:44 ` Dmitry Baryshkov
2026-10-27 11:58 ` [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Dmitry Baryshkov
23 siblings, 2 replies; 55+ messages in thread
From: Jayant Shekhar @ 2026-10-26 6:21 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P
Cc: Jayant Shekhar, virtualization, linux-arm-msm, dri-devel,
freedreno, devicetree, linux-kernel, linux-media, linaro-mm-sig,
iommu
Hawi display hardware is owned by DCP firmware, so the platform device tree
must describe the Qualcomm transport wrapper, firmware display subsystem,
power-notify channel, IOMMU contexts, and display graph used by the MSM DRM
driver.
Describe the Qualcomm DCP transport and its VirtIO-MMIO child,
the IPCC power-notify channel, IOMMU assignments, firmware-visible IOVA
range, and DSI display endpoints on Hawi. Enable the DCP display device on
the Hawi MTP platform.
Assisted-by: LLM
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
---
arch/arm64/boot/dts/qcom/hawi-mtp.dts | 129 ++++++++++++++++++++++++++++++++++
arch/arm64/boot/dts/qcom/hawi.dtsi | 40 +++++++++++
2 files changed, 169 insertions(+)
diff --git a/arch/arm64/boot/dts/qcom/hawi-mtp.dts b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
index c38e5fab7a2c..a27003f69d02 100644
--- a/arch/arm64/boot/dts/qcom/hawi-mtp.dts
+++ b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
@@ -34,6 +34,18 @@ chosen {
stdout-path = "serial0:115200n8";
};
+ display_panel_vdd: regulator-display-panel-vdd {
+ compatible = "regulator-fixed";
+ regulator-name = "display-panel-vdd";
+ regulator-min-microvolt = <1100000>;
+ regulator-max-microvolt = <1100000>;
+ gpio = <&pmd8028_e1_gpios 2 GPIO_ACTIVE_HIGH>;
+ enable-active-high;
+ regulator-boot-on;
+ pinctrl-0 = <&sde_disp0_vdd_active>;
+ pinctrl-names = "default";
+ };
+
clocks {
bi_tcxo_ao_div2: bi-tcxo-ao-div2-clk {
compatible = "fixed-factor-clock";
@@ -142,6 +154,7 @@ sdcard_vdd_3p0: sdcard-vdd-3p0 {
vin-supply = <&vreg_bob1>;
};
+
};
&apps_rsc {
@@ -995,3 +1008,119 @@ &usb_dp_qmpphy {
&usb_dp_qmpphy_out {
remote-endpoint = <&pmic_glink_ss_in>;
};
+
+&dcp_dsi0 {
+ panel@0 {
+ compatible = "novatek,nt37801";
+ reg = <0>;
+
+ pinctrl-0 = <&sde_dsi_active &sde_te_active &sde_esync0_suspend
+ &sde_mdp_vsync_p_1p2_active &sde_mdp_vsync_p_1p8_active
+ &sde_disp0_rst_1p2_active &sde_disp0_rst_1p8_active>;
+ pinctrl-1 = <&sde_dsi_suspend &sde_te_suspend &sde_esync0_suspend
+ &sde_mdp_vsync_p_1p2_active &sde_mdp_vsync_p_1p8_active
+ &sde_disp0_rst_1p2_active &sde_disp0_rst_1p8_active>;
+ pinctrl-names = "default", "sleep";
+
+ vddio-supply = <&vreg_l12b_1p8>;
+ vci-supply = <&vreg_l13b_3p0>;
+ vdd-supply = <&display_panel_vdd>;
+
+ reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
+
+ port {
+ panel_dcp_in: endpoint {
+ remote-endpoint = <&dcp_intf0_out>;
+ };
+ };
+ };
+};
+
+&dcp_intf0_out {
+ remote-endpoint = <&panel_dcp_in>;
+};
+
+&pmd8028_e1_gpios {
+ sde_disp0_vdd_active: sde_disp0_vdd_active {
+ pins = "gpio2";
+ function = "normal";
+ input-disable;
+ output-enable;
+ bias-disable;
+ power-source = <2>;
+ qcom,drive-strength = <2>;
+ };
+};
+
+&pmh0110_d_e0_gpios {
+ sde_mdp_vsync_p_1p2_active: sde-mdp-vsync-p-1p2-active-state {
+ pins = "gpio9";
+ function = "paired";
+ input-disable;
+ output-enable;
+ power-source = <2>; /* 1.2v */
+ };
+
+ sde_mdp_vsync_p_1p8_active: sde-mdp-vsync-p-1p8-active-state {
+ pins = "gpio10";
+ function = "paired";
+ input-enable;
+ output-disable;
+ power-source = <1>; /* 1.8v */
+ };
+};
+
+&pmh0110_f_e0_gpios {
+ sde_disp0_rst_1p2_active: sde-disp0-rst-1p2-active-state {
+ pins = "gpio9";
+ function = "paired";
+ input-enable;
+ output-disable;
+ power-source = <2>; /* 1.2v */
+ };
+
+ sde_disp0_rst_1p8_active: sde-disp0-rst-1p8-active-state {
+ pins = "gpio10";
+ function = "paired";
+ input-disable;
+ output-enable;
+ power-source = <1>; /* 1.8v */
+ };
+};
+
+&tlmm {
+ sde_te_active: sde-te-active-state {
+ pins = "gpio86";
+ function = "mdp_vsync";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+
+ sde_te_suspend: sde-te-suspend-state {
+ pins = "gpio86";
+ function = "mdp_vsync";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+
+ sde_esync0_suspend: sde-esync0-suspend-state {
+ pins = "gpio88";
+ function = "mdp_esync0";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+
+ sde_dsi_active: sde-dsi-active-state {
+ pins = "gpio211";
+ function = "gpio";
+ drive-strength = <8>;
+ bias-disable;
+ };
+
+ sde_dsi_suspend: sde-dsi-suspend-state {
+ pins = "gpio211";
+ function = "gpio";
+ drive-strength = <2>;
+ bias-pull-down;
+ };
+};
diff --git a/arch/arm64/boot/dts/qcom/hawi.dtsi b/arch/arm64/boot/dts/qcom/hawi.dtsi
index 6b0a0ff0faa4..836970cdef71 100644
--- a/arch/arm64/boot/dts/qcom/hawi.dtsi
+++ b/arch/arm64/boot/dts/qcom/hawi.dtsi
@@ -6841,4 +6841,44 @@ timer {
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
};
+
+ dcp: virtio@9be8000 {
+ compatible = "qcom,dcp-virtio";
+ reg = <0x0 0x09be8000 0x0 0x1000>;
+ interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+ <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+ iommus = <&apps_smmu 0x15a3 0x0>;
+ iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
+ dma-coherent;
+ mboxes = <&ipcc 13 5>;
+ mbox-names = "dcp-power-tx";
+
+ dcp-subsystem {
+ compatible = "virtio,device34";
+
+ iommus = <&apps_smmu 0x400 0x2>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_intf0_out: endpoint {
+
+ };
+ };
+
+ };
+
+ dcp_dsi0: dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+ };
+ };
+ };
};
--
2.43.0
^ permalink raw reply related [flat|nested] 55+ messages in thread
* Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
@ 2026-10-26 9:38 ` sashiko-bot
2026-10-26 9:57 ` Dmitry Baryshkov
` (2 subsequent siblings)
3 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 9:38 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] dt-bindings: qcom,dcp-virtio: invalid uint32 $ref in iommu-ranges items
- [Medium] dt-bindings: qcom,dcp-virtio: mismatched reg and iommu-ranges cell counts
--
commit 148fd24e1536920168a0cc19486ca6a7b8039591
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
dt-bindings: display: msm: Add Qualcomm DCP
This commit adds Device Tree binding schemas for the Qualcomm Display
Co-Processor (DCP) VirtIO transport wrapper, the DCP VirtIO display
subsystem, and its child MIPI DSI host controller.
> diff --git a/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
> new file mode 100644
> index 0000000000000..6020e848ce259
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
> @@ -0,0 +1,132 @@
[ ... ]
> + iommu-ranges:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + description: |
> + The firmware-visible IOVA range, specified as a four-cell tuple
> + containing a 64-bit IOVA base followed by a 64-bit range size.
> + items:
> + - description: IOVA base, high cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA base, low cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA range size, high cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA range size, low cell
> + $ref: /schemas/types.yaml#/definitions/uint32
[Severity: Medium]
Does make dt_binding_check pass on properties.iommu-ranges in
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml with these
per-item $ref entries?
When iommu-ranges uses /schemas/types.yaml#/definitions/uint32-array at the
property level, dtschema validates each entry under items against
meta-schemas/cell.yaml#/$defs/single-cell, which sets additionalProperties:
false and does not permit $ref, causing dt-doc-validate to fail.
In addition, /schemas/types.yaml#/definitions/uint32 is a property-level 2D
matrix schema rather than a scalar cell schema, so applying it to individual
cells of a uint32-array creates an array-versus-integer type conflict when
dt-validate checks the example's iommu-ranges property. Should the four $ref:
/schemas/types.yaml#/definitions/uint32 lines under items be dropped?
> +
> + dma-coherent: true
> +
> + mboxes:
> + maxItems: 1
> +
> + mbox-names:
> + const: dcp-power-tx
> +
> + dcp-subsystem:
> + $ref: ../display/msm/qcom,dcp.yaml#
> +
> +required:
> + - compatible
> + - reg
> + - interrupts-extended
> + - iommus
> + - iommu-ranges
> + - dma-coherent
> + - mboxes
> + - mbox-names
> + - dcp-subsystem
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/mailbox/qcom-ipcc.h>
> +
> + virtio@9be8000 {
> + compatible = "qcom,dcp-virtio";
> + reg = <0x09be8000 0x1000>;
> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
> + iommus = <&apps_smmu 0x15a3 0x0>;
> + iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
[Severity: Medium]
In the examples block of
Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml, reg is
specified with 1 address cell and 1 size cell under the default example bus
(#address-cells = <1>; #size-cells = <1>;), whereas iommu-ranges is written
as a 4-cell tuple (2 address cells and 2 size cells).
When the kernel parses iommu-ranges via
of_iommu_get_resv_regions() -> of_iommu_derive_resv_regions() ->
of_translate_dma_region() in drivers/of/address.c:
struct device_node *parent __free(device_node) = __of_get_dma_parent(dev);
...
na = of_bus_n_addr_cells(parent);
ns = of_bus_n_size_cells(parent);
address = of_translate_dma_address(dev, prop);
if (address == OF_BAD_ADDR)
return NULL;
size = of_read_number(prop + na, ns);
both reg and iommu-ranges use the parent bus #address-cells and #size-cells.
With a 1-cell parent bus, wouldn't of_iommu_derive_resv_regions() misparse
<0x0 0x84000000 0x0 0x04000000> as two 32-bit ranges ([0x0, 0x84000000) and
[0x0, 0x04000000)) instead of one 64-bit range ([0x84000000, 0x88000000))?
Should the example wrap virtio@9be8000 in a parent bus node with
#address-cells = <2> and #size-cells = <2> and use a 4-cell reg property?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=1
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers
2026-10-26 6:21 ` [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers Jayant Shekhar
@ 2026-10-26 9:48 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 9:48 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] drm/msm/dcp: DRM_MSM_DCP bool dependency on tristate VIRTIO allows DRM_MSM=y with VIRTIO=m
--
commit 29edd03d5dbd7916e10401025f35f2e578051ed0
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add HFI packet helpers
This commit adds the HFI packet definitions, packing and unpacking helpers,
and key-value parsing utilities for communicating with the Display
Coprocessor (DCP) firmware, along with the DRM_MSM_DCP Kconfig symbol and
Makefile rules.
> diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig
> index ed1c4775d1e92..a9f049271c251 100644
> --- a/drivers/gpu/drm/msm/Kconfig
> +++ b/drivers/gpu/drm/msm/Kconfig
> @@ -114,6 +114,31 @@ config DRM_MSM_DPU
> the MSM DRM driver. It is the display controller found in devices
> using e.g. SDM845 and newer platforms.
>
> +config DRM_MSM_DCP
> + bool "Enable DCP firmware-delegated display support in MSM DRM driver"
> + depends on DRM_MSM
> + depends on VIRTIO
> + select DRM_MSM_KMS
> + select DRM_BRIDGE_CONNECTOR
> + select DRM_MIPI_DSI
> + select DRM_DISPLAY_DSC_HELPER
> + default y
[Severity: Low]
Can this Kconfig dependency allow CONFIG_DRM_MSM_DCP=y when CONFIG_DRM_MSM=y
and CONFIG_VIRTIO=m?
Both DRM_MSM and VIRTIO are tristate symbols, while DRM_MSM_DCP is a bool
symbol whose objects in drivers/gpu/drm/msm/Makefile are linked directly
into msm.o via msm-display-$(CONFIG_DRM_MSM_DCP) and
msm-$(CONFIG_DRM_MSM_KMS) += $(msm-display-y):
drivers/gpu/drm/msm/Makefile:
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.o \
dcp/hfi/hfi_utils.o
In Kconfig, a bool option with depends on DRM_MSM and depends on VIRTIO
evaluates its upper bound as y even when CONFIG_DRM_MSM=y and
CONFIG_VIRTIO=m. When the DCP VirtIO transport and driver objects are added
to msm-display-$(CONFIG_DRM_MSM_DCP) later in this series ("drm/msm/dcp: Add
HFI VirtIO transport", "drm/msm/dcp: Add Qualcomm VirtIO transport wrapper",
and "drm/msm/dcp: Add DCP VirtIO driver"), built-in msm.o calls VirtIO core
and vring symbols such as register_virtio_driver(), register_virtio_device(),
virtio_find_vqs(), and vring_create_virtqueue() that live in loadable
modules when CONFIG_VIRTIO=m, resulting in a vmlinux link failure.
Should DRM_MSM_DCP use depends on VIRTIO=y || VIRTIO=DRM_MSM (or DRM_MSM use
depends on VIRTIO || !DRM_MSM_DCP) to prevent a built-in msm driver from
linking against modular virtio?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=2
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
2026-10-26 9:38 ` sashiko-bot
@ 2026-10-26 9:57 ` Dmitry Baryshkov
2026-10-26 19:44 ` Rob Herring (Arm)
2026-10-27 8:36 ` Krzysztof Kozlowski
3 siblings, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 9:57 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:11AM +0530, Jayant Shekhar wrote:
> DCP platforms place the display pipeline under firmware control, so Linux
> cannot discover display resources through direct register access. The DRM
> driver therefore needs device-tree descriptions of the Qualcomm transport
> wrapper, display topology, IOMMU contexts, and panel connections.
>
> Add bindings for the Qualcomm DCP transport wrapper and VirtIO display
> subsystem. Document the IPCC power-notify resources, transport and
> framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and
> DSI panel connections. The wrapper creates a standard VirtIO-MMIO child
> after the IPCC power-notify sequence has completed.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> .../bindings/display/msm/qcom,dcp-dsi.yaml | 79 ++++++++++++
> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 123 +++++++++++++++++++
> .../bindings/virtio/qcom,dcp-virtio.yaml | 132 +++++++++++++++++++++
> 3 files changed, 334 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
> new file mode 100644
> index 000000000000..9a0acaf00bb3
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
> @@ -0,0 +1,79 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/msm/qcom,dcp-dsi.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm DCP DSI host
> +
> +maintainers:
> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> +
> +description: |
> + A MIPI DSI host exposed by the Qualcomm DCP VirtIO device (see
> + qcom,dcp.yaml). It follows the generic DSI controller binding, with panel
> + devices described as child nodes. The DCP INTF graph endpoint connects
> + directly to the panel; the DSI host node is not a graph endpoint. The host
> + node uses the dsi@N naming convention, and its reg property identifies the
> + DSI host index.
> +
> +allOf:
> + - $ref: /schemas/display/dsi-controller.yaml#
If there are no separate compatibles, can this be folded into the main
DCP schema?
> +
> +properties:
> + reg:
> + maxItems: 1
> + description: DSI host index matching the DCP output port.
> +
> +required:
> + - reg
> + - "#address-cells"
> + - "#size-cells"
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + dcp-subsystem {
> + compatible = "virtio,device34";
> + iommus = <&apps_smmu 0x400 0x2>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_intf0_out: endpoint {
> + remote-endpoint = <&panel_dcp_in>;
> + };
> + };
> + };
> +
> + dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
Yes, fold it there. It's not a separate device on its own.
> +
> + panel@0 {
> + compatible = "novatek,nt37801";
> + reg = <0>;
> +
> + vddio-supply = <&vreg_l12b_1p8>;
> + vci-supply = <&vreg_l13b_3p0>;
> + vdd-supply = <&display_panel_vdd>;
> + reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
> +
> + port {
> + panel_dcp_in: endpoint {
> + remote-endpoint = <&dcp_intf0_out>;
> + };
> + };
> + };
> + };
> + };
> diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml
> new file mode 100644
> index 000000000000..96f6c2a17e9e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml
> @@ -0,0 +1,123 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/msm/qcom,dcp.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm Display Co-Processor (DCP)
> +
> +maintainers:
> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> +
> +description: |
> + The Display Co-Processor (DCP) is a firmware-owned display subsystem.
> + Linux communicates with DCP over HFI through VirtIO and does not access
> + display hardware registers directly.
> +
> + This node describes the externally connected display graph between DCP
> + INTF outputs and attached DSI panels. It is assigned to the VirtIO device
> + by the Qualcomm transport driver and is not independently enumerated.
> + Firmware capabilities and internal resources, including pipes, mixers,
> + CRTCs, and clocks, are discovered at runtime.
> +
> +allOf:
> + - $ref: /schemas/virtio/virtio-device.yaml#
> +
> +properties:
> + compatible:
> + const: virtio,device34
> +
> + iommus:
> + maxItems: 1
> + description:
> + SMMU context used by DCP firmware to access GEM framebuffers. This
> + is separate from the HFI transport context specified by the
> + iommus property on the parent DCP transport node.
> + The child context is used for framebuffer DMA performed by DCP
> + firmware, while the parent context is used for VirtIO transport and HFI
> + queue buffers. These contexts represent distinct firmware-visible DMA
> + address spaces and must not be used interchangeably.
> +
> + "#address-cells":
> + const: 1
> +
> + "#size-cells":
> + const: 0
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> + description: |
> + DPU INTF output-only ports. The port number matches the firmware INTF
> + index. The corresponding DSI host is the child node whose reg value
> + matches the port number; the graph endpoint itself connects directly to
> + the attached panel. Unused INTF ports are omitted.
> + unevaluatedProperties: false
> + patternProperties:
> + "^port@[0-9a-f]+$":
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + unevaluatedProperties: false
> + properties:
> + endpoint:
> + $ref: /schemas/graph.yaml#/$defs/endpoint-base
> + unevaluatedProperties: false
> + required:
> + - remote-endpoint
> +
> +patternProperties:
> + "^dsi@[0-9a-f]+$":
> + $ref: qcom,dcp-dsi.yaml#
When adding DP support, would you add more devices at the same level? If
so, we risk getting a warning about multiple devices using the same
address.
> +required:
> + - compatible
> + - iommus
> + - "#address-cells"
> + - "#size-cells"
> + - ports
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + dcp-subsystem {
> + compatible = "virtio,device34";
> + iommus = <&apps_smmu 0x400 0x2>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_intf0_out: endpoint {
> + remote-endpoint = <&panel_dcp_in>;
> + };
> + };
> + };
> +
> + dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + panel@0 {
> + compatible = "novatek,nt37801";
> + reg = <0>;
> +
> + vddio-supply = <&vreg_l12b_1p8>;
> + vci-supply = <&vreg_l13b_3p0>;
> + vdd-supply = <&display_panel_vdd>;
> + reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
> +
> + port {
> + panel_dcp_in: endpoint {
> + remote-endpoint = <&dcp_intf0_out>;
> + };
> + };
> + };
> + };
> + };
> diff --git a/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
> new file mode 100644
> index 000000000000..6020e848ce25
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
> @@ -0,0 +1,132 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/virtio/qcom,dcp-virtio.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm DCP VirtIO transport
> +
> +maintainers:
> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> +
> +description: |
> + Qualcomm DCP VirtIO transport. The IPCC power-notify channel and
> + associated interrupts are required before the transport can access its
> + MMIO registers. This is a Qualcomm-specific transport and must not use the
> + generic virtio-mmio driver. The DCP VirtIO function is described as a child
> + node and is assigned to the registered VirtIO device by this transport.
> +
> +properties:
> + compatible:
> + const: qcom,dcp-virtio
> +
> + reg:
> + maxItems: 1
> +
> + interrupts-extended:
> + minItems: 2
> + maxItems: 2
> + description: The VirtIO-MMIO and DCP power-notify interrupts, in that order.
> +
> + iommus:
> + maxItems: 1
> +
> + iommu-ranges:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + description: |
> + The firmware-visible IOVA range, specified as a four-cell tuple
> + containing a 64-bit IOVA base followed by a 64-bit range size.
> + items:
> + - description: IOVA base, high cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA base, low cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA range size, high cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> + - description: IOVA range size, low cell
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + dma-coherent: true
> +
> + mboxes:
> + maxItems: 1
> +
> + mbox-names:
> + const: dcp-power-tx
> +
> + dcp-subsystem:
'dcp' is not a generic name. If you want to list it here, it should be
something like 'display-subsystem' or 'display-controller'.
> + $ref: ../display/msm/qcom,dcp.yaml#
> +
> +required:
> + - compatible
> + - reg
> + - interrupts-extended
> + - iommus
> + - iommu-ranges
> + - dma-coherent
> + - mboxes
> + - mbox-names
> + - dcp-subsystem
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> + #include <dt-bindings/mailbox/qcom-ipcc.h>
> +
> + virtio@9be8000 {
> + compatible = "qcom,dcp-virtio";
> + reg = <0x09be8000 0x1000>;
> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
> + iommus = <&apps_smmu 0x15a3 0x0>;
> + iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
This doesn't match the address / size cells.
> + dma-coherent;
> + mboxes = <&ipcc 13 5>;
> + mbox-names = "dcp-power-tx";
> +
> + dcp-subsystem {
> + compatible = "virtio,device34";
> + iommus = <&apps_smmu 0x400 0x2>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_intf0_out: endpoint {
> + remote-endpoint = <&panel_dcp_in>;
> + };
> + };
> + };
> +
> + dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + panel@0 {
> + compatible = "novatek,nt37801";
> + reg = <0>;
> +
> + vddio-supply = <&vreg_l12b_1p8>;
> + vci-supply = <&vreg_l13b_3p0>;
> + vdd-supply = <&display_panel_vdd>;
> + reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
> +
> + port {
> + panel_dcp_in: endpoint {
> + remote-endpoint = <&dcp_intf0_out>;
> + };
> + };
> + };
> + };
> + };
> + };
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID
2026-10-26 6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
@ 2026-10-26 9:59 ` Dmitry Baryshkov
2026-10-26 10:27 ` Michael S. Tsirkin
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 9:59 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:28AM +0530, Jayant Shekhar wrote:
> The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed
Can it be anything other than the DCP? If so, how would you identify it
on the Linux side?
> as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the
> common VirtIO ID header so transport and function drivers use the
> shared identifier.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> include/uapi/linux/virtio_ids.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index f9056af0c622..6c6f283733b1 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -70,6 +70,7 @@
> #define VIRTIO_ID_GPIO 41 /* virtio gpio */
> #define VIRTIO_ID_SPI 45 /* virtio spi */
> #define VIRTIO_ID_MEDIA 48 /* virtio media */
> +#define VIRTIO_ID_QPSFI 52 /* Qualcomm Platform Services Firmware Interface */
>
> /*
> * Virtio Transitional IDs
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper
2026-10-26 6:21 ` [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper Jayant Shekhar
@ 2026-10-26 10:07 ` Dmitry Baryshkov
2026-10-26 14:05 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 10:07 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:29AM +0530, Jayant Shekhar wrote:
> Add the Qualcomm-specific VirtIO transport required by DCP firmware.
>
> The transport sends an IPCC power notification before accessing the
> VirtIO MMIO registers, then registers a VirtIO device directly.
>
> Implement MMIO configuration access, feature negotiation, virtqueue
> setup, interrupt handling, reset, DMA setup, and suspend/resume
> sequencing in the transport. Keep power-management ordering in the
> transport so VirtIO queues are quiesced before DCP power changes.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/dcp/dcp_virtio.h | 23 +
> drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c | 643 +++++++++++++++++++++++++
This is a separate VirtIO host driver. Please move it to drivers/virtio
like the rest of the VirtIO hosts. It has nothing to do with the drm
subsystem now.
> 3 files changed, 667 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 9f0a9bfeb2a7..e1a995a8c980 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -85,6 +85,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_plane.o \
> dcp/dcp_ipcc.o \
> dcp/dcp_encoder.o \
> + dcp/dcp_virtio_transport.o \
> dcp/hfi/hfi_packer.o \
> dcp/hfi/hfi_unpacker.o \
> dcp/hfi/hfi_utils.o
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> new file mode 100644
> index 000000000000..a9c3b9aff61d
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> @@ -0,0 +1,23 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + */
> +
> +#ifndef __DCP_VIRTIO_H__
> +#define __DCP_VIRTIO_H__
> +
> +#include <linux/device.h>
> +#include <linux/virtio.h>
> +
> +struct dcp_drv;
> +struct dcp_ipcc;
> +
> +struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
What is the use for it? The DCP driver should not communicate to the
IPCC, it's a part of the underlying VirtIO mechanism. If we export DCP
to the VM there will be no IPCC anymore.
> +
> +int dcp_virtio_transport_register(void);
> +void dcp_virtio_transport_unregister(void);
> +
> +int dcp_virtio_register(void);
> +void dcp_virtio_unregister(void);
Unused, remove these two.
> +
> +#endif /* __DCP_VIRTIO_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
> new file mode 100644
> index 000000000000..02c0e2a9fafe
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
> @@ -0,0 +1,643 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * Qualcomm DCP VirtIO transport.
> + */
> +
> +#include <linux/acpi.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/highmem.h>
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_device.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/slab.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_config.h>
> +#include <uapi/linux/virtio_mmio.h>
> +#include <linux/virtio_ring.h>
> +
> +#include "dcp_ipcc.h"
> +#include "dcp_virtio.h"
> +
> +#define VIRTIO_MMIO_VRING_ALIGN PAGE_SIZE
> +
> +#define to_dcp_virtio_transport(_plat_dev) \
> + container_of(_plat_dev, struct dcp_virtio_transport, vdev)
> +
> +struct dcp_virtio_transport {
> + struct virtio_device vdev;
> + struct platform_device *pdev;
> +
> + void __iomem *base;
> + unsigned long version;
> +
> + bool wake_irq_enabled;
> + struct dcp_ipcc ipcc;
> +};
> +
> +static u64 dcp_virtio_get_features(struct virtio_device *vdev)
> +{
> + struct dcp_virtio_transport *transport = to_dcp_virtio_transport(vdev);
> + u64 features;
> +
> + writel(1, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
> + features = readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
> + features <<= 32;
> +
> + writel(0, transport->base + VIRTIO_MMIO_DEVICE_FEATURES_SEL);
> + features |= readl(transport->base + VIRTIO_MMIO_DEVICE_FEATURES);
Can we turn virtio-mmio into the library code instead of copy-pasting
everything here?
> +
> + return features;
> +}
> +
[...]
> +
> + magic = readl(transport->base + VIRTIO_MMIO_MAGIC_VALUE);
> + if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
Is it really a correct magic here? should it be a different magic as you
are not just a virtio-mmio?
> + dev_warn(&pdev->dev, "Wrong magic value 0x%08lx!\n", magic);
> + ret = -ENODEV;
> + goto err_node;
> + }
> +
> + transport->version = readl(transport->base + VIRTIO_MMIO_VERSION);
> + if (transport->version < 1 || transport->version > 3) {
Isn't transport version fixed for the DCP purposes? Why do we need to
determine it again at the runtime?
> + dev_err(&pdev->dev, "Version %ld not supported!\n",
> + transport->version);
> + ret = -ENXIO;
> + goto err_node;
> + }
> +
> + transport->vdev.id.device =
> + readl(transport->base + VIRTIO_MMIO_DEVICE_ID);
> + if (!transport->vdev.id.device) {
> + ret = -ENODEV;
> + goto err_node;
> + }
> + transport->vdev.id.vendor =
> + readl(transport->base + VIRTIO_MMIO_VENDOR_ID);
> +
> + if (transport->version == 1) {
> + writel(PAGE_SIZE,
> + transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
> + ret = dma_set_mask(&pdev->dev, DMA_BIT_MASK(64));
> + if (!ret)
> + ret = dma_set_coherent_mask(&pdev->dev,
> + DMA_BIT_MASK(32 + PAGE_SHIFT));
> + } else {
> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
> + }
> + if (ret)
> + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> + if (ret)
> + dev_warn(&pdev->dev,
> + "Failed to enable 64-bit or 32-bit DMA. Trying to continue, but this might not work.\n");
> +
> + ret = register_virtio_device(&transport->vdev);
> + if (ret) {
> + put_device(&transport->vdev.dev);
> + dcp_ipcc_deinit(&transport->ipcc);
> + platform_set_drvdata(pdev, NULL);
> + return ret;
> + }
> +
> + return 0;
> +
> +err_node:
> +err_ipcc:
> + dcp_ipcc_deinit(&transport->ipcc);
> +err_free:
> + platform_set_drvdata(pdev, NULL);
> + return ret;
> +}
> +
> +static void dcp_virtio_transport_remove(struct platform_device *pdev)
> +{
> + struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
> +
> + unregister_virtio_device(&transport->vdev);
> + platform_set_drvdata(pdev, NULL);
> + dcp_ipcc_deinit(&transport->ipcc);
> +}
> +
> +static int dcp_virtio_transport_suspend(struct device *dev)
> +{
> + struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = virtio_device_freeze(&transport->vdev);
> + if (ret)
> + return ret;
> +
> + ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
> + if (ret)
> + goto err_restore;
> +
> + ret = dcp_ipcc_send_power_notify(&transport->ipcc);
> + if (ret) {
> + dcp_ipcc_disable_irq_wake(&transport->ipcc);
> + goto err_restore;
> + }
> +
> + return 0;
> +
> +err_restore:
> + virtio_device_restore(&transport->vdev);
> + return ret;
> +}
> +
> +static int dcp_virtio_transport_resume(struct device *dev)
> +{
> + struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = dcp_ipcc_send_power_notify(&transport->ipcc);
> + if (ret)
> + return ret;
> +
> + dcp_ipcc_disable_irq_wake(&transport->ipcc);
> + if (transport->version == 1)
> + writel(PAGE_SIZE,
> + transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
> +
> + return virtio_device_restore(&transport->vdev);
> +}
> +
> +static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
> + .suspend = dcp_virtio_transport_suspend,
> + .resume = dcp_virtio_transport_resume,
> +};
> +
> +static const struct of_device_id dcp_virtio_transport_of_match[] = {
> + { .compatible = "qcom,dcp-virtio" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
> +
> +static struct platform_driver dcp_virtio_transport_driver = {
> + .probe = dcp_virtio_transport_probe,
> + .remove = dcp_virtio_transport_remove,
> + .driver = {
> + .name = "qcom-dcp-virtio",
> + .of_match_table = dcp_virtio_transport_of_match,
> + .pm = &dcp_virtio_transport_pm_ops,
> + },
> +};
> +
> +int dcp_virtio_transport_register(void)
> +{
> + return platform_driver_register(&dcp_virtio_transport_driver);
> +}
> +
> +void dcp_virtio_transport_unregister(void)
> +{
> + platform_driver_unregister(&dcp_virtio_transport_driver);
> +}
Turn it into a separate driver, use module_platform_driver().
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport
2026-10-26 6:21 ` [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport Jayant Shekhar
@ 2026-10-26 10:07 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 10:07 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_send_sync_on_vq()
- [High] drm/msm/dcp: unsynchronized virtqueue callbacks and work re-arming on teardown
- [Medium] drm/msm/dcp: double-fetch from shared DMA buffer in dcp_hfi_resp_callback()
--
commit 250b478306210e9e3849c522be712bc686a63c4b
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add HFI VirtIO transport
This commit implements the HFI transport over VirtIO between Linux and the DCP
firmware, managing DMA command and response buffers, VirtIO queue callbacks,
synchronous and asynchronous command submission, and teardown synchronization.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> new file mode 100644
> index 0000000000000..9f7ac798e13c4
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
[ ... ]
> +void dcp_hfi_resp_callback(struct virtqueue *vq)
> +{
> + struct dcp_drv *drv = vq->vdev->priv;
> + struct dcp_hfi *hfi = drv ? drv->hfi : NULL;
> + struct dcp_hfi_buf *buf;
> + unsigned int len;
> + unsigned long drain_flags;
> +
> + if (!hfi || atomic_read(&hfi->shutdown_in_progress))
> + return;
> +
> + spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
> + virtqueue_disable_cb(vq);
> + for (;;) {
> + while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
> + struct hfi_cmd_buff_hdl hdl;
> + struct hfi_header_info hdr_info;
> + struct hfi_packet_info pkt_info;
> + u32 pkt_num;
> + int rc;
> +
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
> +
> + if (unlikely(len < sizeof(struct hfi_header) ||
> + len > DCP_HFI_BUF_SIZE)) {
> + dev_warn_ratelimited(hfi->dev,
> + "HFI resp: short buffer len=%u (min=%zu) — skipping\n",
> + len, sizeof(struct hfi_header));
> + goto repost;
> + }
> +
> + hdl.cmd_buffer = buf->data;
> + hdl.size = (u32)len;
> + rc = hfi_unpacker_get_header_info(&hdl, &hdr_info);
> + if (rc) {
> + dev_warn_ratelimited(hfi->dev,
> + "HFI resp: invalid header (rc=%d) len=%u\n",
> + rc, len);
> + print_hex_dump(KERN_WARNING, "HFI resp raw: ",
> + DUMP_PREFIX_OFFSET, 16, 4,
> + buf->data, min_t(u32, len, 64), false);
> + goto repost;
> + }
> +
> + for (pkt_num = 1; pkt_num <= hdr_info.num_packets; pkt_num++) {
> + memset(&pkt_info, 0, sizeof(pkt_info));
> + rc = hfi_unpacker_get_packet_info(&hdl, pkt_num,
> + &pkt_info);
[Severity: Medium]
Does unpacking directly from the coherent DMA buffer in
dcp_hfi_resp_callback() expose the parser to a double-fetch if the firmware or
VirtIO backend modifies the shared buffer concurrently?
In dcp_hfi_init(), the buffers in hfi->resp_pool are allocated with
dma_alloc_coherent() and premapped into hfi->resp_vq, so buf->data remains in
shared DMA memory writable by the device.
When hfi_unpacker_get_packet_info() runs on buf->data, it first validates the
buffer through hfi_sanitize_cmd_buff(), and then re-reads packet->payload_info
in hfi_packet_at() and GET_PACKET_SIZE(packet->payload_info):
drivers/gpu/drm/msm/dcp/hfi/hfi_unpacker.c:hfi_unpacker_get_packet_info() {
hdr = cmd_buf_hdl->cmd_buffer;
if (GET_HEADER_SIZE(hdr->cmd_buff_info) > cmd_buf_hdl->size ||
packet_num > hdr->num_packets || hfi_sanitize_cmd_buff(hdr))
return -EINVAL;
packet = hfi_packet_at(hdr, packet_num);
packet_size = GET_PACKET_SIZE(packet->payload_info);
...
packet_info->payload_size = packet_size - sizeof(*packet);
packet_info->payload_ptr = packet_info->payload_size ?
(void *)(packet + 1) : NULL;
}
If hdr->cmd_buff_info or packet->payload_info changes in shared memory after
hfi_sanitize_cmd_buff() returns, hfi_packet_at() can walk past the buffer or
packet_size can become smaller than sizeof(*packet) (underflowing
packet_info->payload_size) before dcp_hfi_complete_pending() calls memcpy() on
pkt_info->payload_ptr.
Should dcp_hfi_resp_callback() copy the received len bytes out of buf->data
into a non-shared kernel buffer before validating and unpacking the packets?
[ ... ]
> +static int dcp_hfi_send_sync_on_vq(struct dcp_hfi *hfi,
> + struct virtqueue *vq __maybe_unused,
> + u32 cmd,
> + enum hfi_cmd_buff_type cmd_type,
> + u32 object_id,
> + enum hfi_packet_payload_type payload_type,
> + void *payload,
> + u32 payload_size,
> + u32 tx_flags,
> + void *resp_data,
> + size_t resp_len,
> + unsigned int timeout_ms)
> +{
> + struct dcp_hfi_buf *buf;
> + struct dcp_hfi_completion comp;
[ ... ]
> + ret = dcp_hfi_send_cmd_async(hfi, buf,
> + hfi_get_header_size(&hdl), &comp);
> + if (ret)
> + return ret;
> +
> + if (!wait_for_completion_timeout(&comp.done, msecs_to_jiffies(timeout_ms))) {
> + dev_err(hfi->dev,
> + "HFI cmd=0x%x buf_type=%s object_id=%u timed out after %ums (pkt_id=%u)\n",
> + cmd, dcp_hfi_cmd_buff_type_name(cmd_type), object_id,
> + timeout_ms, packet_id);
> + spin_lock_irqsave(&hfi->resp_drain_lock, flags);
> + dcp_hfi_pending_find(hfi, packet_id);
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
> + return -ETIMEDOUT;
> + }
> +
> + return comp.status;
> +}
[Severity: Critical]
Can a timeout racing with response completion cause a stack use-after-free of
comp and the caller's resp_data buffer in dcp_hfi_send_sync_on_vq()?
In dcp_hfi_resp_callback(), hfi->resp_drain_lock is dropped before unpacking
the response and calling dcp_hfi_complete_pending():
dcp_hfi_resp_callback()
spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
...
dcp_hfi_complete_pending()
comp = dcp_hfi_pending_find(hfi, pkt_info->packet_id);
spin_lock_irqsave(&hfi->pending_lock, flags);
comp = xa_erase(&hfi->pending_xa, packet_id);
spin_unlock_irqrestore(&hfi->pending_lock, flags);
if (!status && comp->resp_data && comp->resp_len &&
pkt_info->payload_size) {
n = min_t(size_t, comp->resp_len, pkt_info->payload_size);
memcpy(comp->resp_data, pkt_info->payload_ptr, n);
}
comp->status = status;
complete(&comp->done);
If wait_for_completion_timeout() expires right after dcp_hfi_pending_find() in
dcp_hfi_complete_pending() erases comp from hfi->pending_xa, acquiring
hfi->resp_drain_lock on the timeout path does not wait for
dcp_hfi_complete_pending() because neither resp_drain_lock nor pending_lock is
held during the memcpy(), status assignment, and complete() calls.
The timeout path's dcp_hfi_pending_find(hfi, packet_id) then returns NULL, and
dcp_hfi_send_sync_on_vq() immediately returns -ETIMEDOUT while
dcp_hfi_complete_pending() on the other CPU is still writing to the stack-
allocated comp and comp->resp_data.
Should the completion update be serialized under a lock held across the lookup
and memcpy()/complete(), or should the timeout path wait on
wait_for_completion(&comp.done) when dcp_hfi_pending_find() returns NULL?
[ ... ]
> +void dcp_hfi_stop(struct dcp_hfi *hfi)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&hfi->cmd_lock, flags);
> + atomic_set(&hfi->shutdown_in_progress, 1);
> + spin_unlock_irqrestore(&hfi->cmd_lock, flags);
> +
> + cancel_delayed_work_sync(&hfi->resp_repost_work);
> + dcp_hfi_cancel_pending(hfi, -ENODEV);
> +}
> +
> +void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&hfi->resp_drain_lock, flags);
> + if (hfi->cmd_vq)
> + virtqueue_disable_cb(hfi->cmd_vq);
> + if (hfi->resp_vq)
> + virtqueue_disable_cb(hfi->resp_vq);
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
> +
> + if (hfi->cmd_vq)
> + while (virtqueue_detach_unused_buf(hfi->cmd_vq))
> + ;
> + if (hfi->resp_vq)
> + while (virtqueue_detach_unused_buf(hfi->resp_vq))
> + ;
> +}
> +
> +void dcp_hfi_deinit(struct dcp_hfi *hfi)
> +{
> + xa_destroy(&hfi->pending_xa);
> +
> + dcp_hfi_free_pool(hfi, hfi->cmd_pool, DCP_HFI_CMD_POOL_SIZE);
> + dcp_hfi_free_pool(hfi, hfi->resp_pool, DCP_HFI_RESP_POOL_SIZE);
> +}
[Severity: High]
How are in-flight dcp_hfi_resp_callback() and dcp_hfi_cmd_callback() calls
synchronized with dcp_hfi_stop(), dcp_hfi_quiesce_queues(), and
dcp_hfi_deinit() during teardown?
In dcp_hfi_resp_callback(), hfi->shutdown_in_progress is only checked once at
entry before acquiring hfi->resp_drain_lock, and hfi->resp_drain_lock is
released while unpacking each buffer before being re-acquired at repost:
dcp_hfi_resp_callback() {
if (!hfi || atomic_read(&hfi->shutdown_in_progress))
return;
spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
...
spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
...
repost:
spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
if (virtqueue_add_inbuf_premapped(vq, &buf->sg, 1, buf,
NULL, GFP_ATOMIC)) {
list_add_tail(&buf->node, &hfi->resp_repost_list);
schedule_delayed_work(&hfi->resp_repost_work,
msecs_to_jiffies(20));
}
}
if (virtqueue_kick_prepare(vq))
virtqueue_notify(vq);
if (virtqueue_enable_cb(vq))
break;
}
If dcp_hfi_stop() runs while dcp_hfi_resp_callback() has dropped
hfi->resp_drain_lock, dcp_hfi_stop() sets shutdown_in_progress = 1 and calls
cancel_delayed_work_sync(&hfi->resp_repost_work), after which
dcp_hfi_resp_callback() can reach repost:, re-add the buffer to vq, re-arm
hfi->resp_repost_work, and call virtqueue_enable_cb(vq).
Similarly, in dcp_hfi_cmd_callback(), shutdown_in_progress is checked before
acquiring hfi->cmd_lock, and in dcp_hfi_quiesce_queues(),
virtqueue_disable_cb(hfi->cmd_vq) is called under hfi->resp_drain_lock instead
of hfi->cmd_lock while virtqueue_detach_unused_buf() is called on both queues
without holding cmd_lock or resp_drain_lock and without waiting for an
in-flight dcp_hfi_resp_callback() to finish using buf->data before
dcp_hfi_deinit() frees the pools.
Should the callbacks re-check shutdown_in_progress under their respective
locks, and should teardown synchronize with in-flight callbacks before
canceling the delayed work, detaching virtqueue buffers, and freeing the DMA
pools?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=6
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver
2026-10-26 6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
@ 2026-10-26 10:15 ` Dmitry Baryshkov
2026-10-26 14:31 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 10:15 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:30AM +0530, Jayant Shekhar wrote:
> Add the DCP function driver that binds the VirtIO device to MSM
> DRM. Without a function driver, Linux cannot establish HFI queues,
> discover firmware capabilities, or register the firmware-owned
> display backend.
>
> Set up the DCP DMA and IOMMU identity, initialize VirtIO and HFI
> transport, register the MSM DRM component, and synchronize suspend,
> resume, remove, and shutdown with in-flight HFI activity.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/Makefile | 2 +
> drivers/gpu/drm/msm/dcp/dcp_drv.c | 191 ++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_drv.h | 17 +++
> drivers/gpu/drm/msm/dcp/dcp_virtio.c | 258 +++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_virtio.h | 2 +
> drivers/gpu/drm/msm/msm_drv.c | 6 +-
> drivers/gpu/drm/msm/msm_drv.h | 2 +
> 7 files changed, 475 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index e1a995a8c980..ffdb159dd9ad 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -85,6 +85,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_plane.o \
> dcp/dcp_ipcc.o \
> dcp/dcp_encoder.o \
> + dcp/dcp_drv.o \
> + dcp/dcp_virtio.o \
> dcp/dcp_virtio_transport.o \
> dcp/hfi/hfi_packer.o \
> dcp/hfi/hfi_unpacker.o \
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c
> new file mode 100644
> index 000000000000..8c38de883bf6
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c
> @@ -0,0 +1,191 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP VirtIO driver.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/of.h>
> +#include <linux/of_platform.h>
> +#include <linux/panic_notifier.h>
> +#include <linux/pm_runtime.h>
> +
> +#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000
> +
> +#include "dcp_drv.h"
> +#include "dcp_dpu_kms.h"
> +#include "dcp_hfi.h"
> +#include "dcp_ipcc.h"
> +#include "dcp_virtio.h"
> +#include "msm_drv.h"
> +#include "msm_kms.h"
> +
> +int dcp_subsystem_dev_init(struct dcp_drv *drv)
> +{
> + struct virtio_device *vdev = drv->vdev;
> + struct device_node *dcp_subsystem_node;
> + struct platform_device *pdev;
> + struct device *dev;
> + char busid[32];
> + bool iommu_mapped;
> + int ret;
> +
> + dcp_subsystem_node = of_node_get(vdev->dev.of_node);
> + if (!dcp_subsystem_node) {
> + dev_err(&vdev->dev, "no DCP subsystem DT node\n");
> + return -ENODEV;
> + }
> +
> + snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
> +
> + /*
> + * DCP is discovered through VirtIO, but display/HFI buffers must be
> + * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
> + * platform device for that DT node so DMA API and msm_kms_init_vm() use
Does DMA API require platform_device or would it work with the generic
struct device? In the latter case, should the MSM layer be changed to
pass struct device instead of struct platform_device?
> + * the display function's IOMMU domain.
> + */
> + pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
> + if (!pdev) {
> + dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
> + of_node_put(dcp_subsystem_node);
> + return -ENODEV;
> + }
> + dev = &pdev->dev;
> +
> + dev->dma_parms = &drv->dcp_subsystem_dma_parms;
> + dma_set_max_seg_size(dev, UINT_MAX);
> +
> + ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
> + if (ret) {
> + dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
> + goto err_destroy;
> + }
> +
> + iommu_mapped = device_iommu_mapped(dev);
> + if (!iommu_mapped) {
> + dev_err(dev, "no IOMMU group attached\n");
> + ret = -ENODEV;
> + goto err_destroy;
> + }
> +
> + dev_dbg(dev, "dcp-subsystem: iommu_mapped=%d iommu_group=%d\n",
> + iommu_mapped,
> + dev->iommu_group ? iommu_group_id(dev->iommu_group) : -1);
> +
> + pm_runtime_set_active(dev);
> + pm_runtime_enable(dev);
> + pm_runtime_set_autosuspend_delay(dev, DCP_DPU_AUTOSUSPEND_DELAY_MS);
> + pm_runtime_use_autosuspend(dev);
> +
> + drv->dcp_subsystem_dev = pdev;
> + drv->dcp_subsystem_node = dcp_subsystem_node;
> +
> + return 0;
> +
> +err_destroy:
> + of_platform_device_destroy(dev, NULL);
> + of_node_put(dcp_subsystem_node);
> + return ret;
> +}
> +
> +void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
> +{
> + if (!drv->dcp_subsystem_dev)
> + return;
> +
> + pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
> + of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
> + of_node_put(drv->dcp_subsystem_node);
> + drv->dcp_subsystem_dev = NULL;
> +}
> +
> +int dcp_pm_prepare(struct device *vdev_dev)
> +{
> + struct dcp_drv *drv = vdev_dev ?
> + dev_to_virtio(vdev_dev)->priv : NULL;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return 0;
> +
> + return msm_kms_pm_prepare(&drv->dcp_subsystem_dev->dev);
> +}
> +
> +void dcp_pm_complete(struct device *vdev_dev)
> +{
> + struct dcp_drv *drv = vdev_dev ?
> + dev_to_virtio(vdev_dev)->priv : NULL;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return;
> +
> + msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
> +}
> +
> +int dcp_component_register(struct dcp_drv *drv)
> +{
> + struct device *dev = &drv->dcp_subsystem_dev->dev;
> + struct component_match *match = NULL;
> + int ret;
> +
> + ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
> + if (ret) {
> + dev_err(dev, "DSI host initialization failed: %d\n", ret);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> + return ret;
> + }
> +
> + /* A GPU is optional for the DCP display-only DRM device. */
> + msm_add_gpu_components(dev, &match);
Missign error handling.
> +
> + ret = component_master_add_with_match(dev, &msm_drm_ops, match);
> + if (ret) {
> + dev_err(dev, "component_master_add_with_match failed: %d\n",
> + ret);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +void dcp_component_unregister(struct dcp_drv *drv)
> +{
> + struct device *dev = &drv->dcp_subsystem_dev->dev;
> +
> + component_master_del(dev, &msm_drm_ops);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> +}
> +
> +static int dcp_panic_notifier_cb(struct notifier_block *nb,
> + unsigned long action, void *data)
> +{
> + struct dcp_drv *drv = container_of(nb, struct dcp_drv, panic_notifier);
> +
> + if (drv->hfi)
> + atomic_set(&drv->hfi->shutdown_in_progress, 1);
> +
> + return NOTIFY_OK;
> +}
> +
> +void dcp_panic_notifier_register(struct dcp_drv *drv)
> +{
> + drv->panic_notifier.notifier_call = dcp_panic_notifier_cb;
> + atomic_notifier_chain_register(&panic_notifier_list, &drv->panic_notifier);
> +}
> +
> +void dcp_panic_notifier_unregister(struct dcp_drv *drv)
> +{
> + atomic_notifier_chain_unregister(&panic_notifier_list, &drv->panic_notifier);
> +}
> +
> +void __init msm_dcp_register(void)
> +{
> + dcp_virtio_register();
> +}
> +
> +void __exit msm_dcp_unregister(void)
> +{
> + dcp_virtio_unregister();
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.h b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> index 16f87035cb9a..3adc5855a9a2 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> @@ -14,6 +14,7 @@
> #include "dcp_ipcc.h"
>
> struct dcp_dpu_kms;
> +struct component_match;
>
> struct dcp_drv {
> struct virtio_device *vdev;
> @@ -24,6 +25,22 @@ struct dcp_drv {
> struct dcp_dpu_kms *kms;
> struct dcp_caps caps;
> struct notifier_block panic_notifier;
> + struct dcp_ipcc *ipcc;
> };
>
> +int dcp_subsystem_dev_init(struct dcp_drv *drv);
> +void dcp_subsystem_dev_deinit(struct dcp_drv *drv);
> +
> +int dcp_component_register(struct dcp_drv *drv);
> +void dcp_component_unregister(struct dcp_drv *drv);
> +
> +int dcp_pm_prepare(struct device *vdev_dev);
> +void dcp_pm_complete(struct device *vdev_dev);
> +
> +void dcp_panic_notifier_register(struct dcp_drv *drv);
> +void dcp_panic_notifier_unregister(struct dcp_drv *drv);
> +
> +void __init msm_dcp_register(void);
> +void __exit msm_dcp_unregister(void);
> +
> #endif /* __DCP_DRV_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.c b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
> new file mode 100644
> index 000000000000..15d5caf02ff5
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
> @@ -0,0 +1,258 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP VirtIO device driver.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/err.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_config.h>
> +#include <linux/slab.h>
> +
> +#include "dcp_drv.h"
> +#include "dcp_dpu_kms.h"
> +#include "dcp_hfi.h"
> +#include "dcp_ipcc.h"
> +#include "dcp_virtio.h"
> +#include "msm_drv.h"
> +#include "msm_kms.h"
> +
> +static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv);
> +
> +static const unsigned int dcp_virtio_features[] = {
> + VIRTIO_F_VERSION_1,
> + VIRTIO_F_NOTIFICATION_DATA,
> + VIRTIO_F_RING_RESET,
> + VIRTIO_F_ACCESS_PLATFORM,
> +};
> +
> +/*
> + * The VirtIO core owns transport discovery. This driver owns HFI setup and
> + * creates the DRM component only after firmware capabilities are available.
> + */
> +static int dcp_virtio_probe(struct virtio_device *vdev)
> +{
> + struct dcp_drv *drv;
> + struct virtqueue *vqs[DCP_NUM_VQS];
> + struct virtqueue_info vqs_info[DCP_NUM_VQS] = {
> + [DCP_VQ_CMD] = { .name = "cmd", .callback = dcp_hfi_cmd_callback },
> + [DCP_VQ_RESP] = { .name = "resp", .callback = dcp_hfi_resp_callback },
> + };
> + struct dcp_dpu_kms *dcp_kms;
> + struct msm_drm_private *priv;
> + int ret;
> +
> + dev_dbg(&vdev->dev, "DCP VirtIO probe started\n");
> +
> + drv = devm_kzalloc(&vdev->dev, sizeof(*drv), GFP_KERNEL);
> + if (!drv)
> + return -ENOMEM;
> +
> + drv->vdev = vdev;
> + vdev->priv = drv;
> +
> + if (!virtio_has_feature(vdev, VIRTIO_F_VERSION_1)) {
> + dev_err(&vdev->dev, "VIRTIO_F_VERSION_1 not negotiated\n");
> + return -ENODEV;
> + }
> +
> + drv->ipcc = dcp_virtio_get_ipcc(vdev);
Here you are breaking the abstraction boundary. The DCP is a VirtIO
device. You should not use IPCC to communicate with it.
> + /* Access-platform is required for the firmware-visible IOVA mappings. */
> + if (!virtio_has_feature(vdev, VIRTIO_F_ACCESS_PLATFORM)) {
> + dev_err(&vdev->dev, "VIRTIO_F_ACCESS_PLATFORM not negotiated\n");
> + return -ENODEV;
> + }
> +
> + ret = dcp_subsystem_dev_init(drv);
> + if (ret)
> + return ret;
> +
> + ret = virtio_find_vqs(vdev, DCP_NUM_VQS, vqs, vqs_info, NULL);
> + if (ret) {
> + dev_err(&vdev->dev, "virtqueue allocation failed: %d\n", ret);
> + goto err_fn_dev;
> + }
> +
> + ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
> + vdev->dev.parent);
> + if (ret) {
> + dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
> + goto err_del_vqs;
> + }
> +
> + dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
> +
> + /*
> + * Keep the device unready until HFI state and virtqueues are initialized.
> + * KMS registration below depends on synchronous firmware initialization.
> + */
> + virtio_device_ready(vdev);
> + virtqueue_kick(drv->hfi->resp_vq);
> +
> + ret = dcp_hfi_send_device_init(drv->hfi);
> + if (ret) {
> + dev_err(&vdev->dev, "HFI DEVICE_INIT failed: %d\n", ret);
> + goto err_hfi_deinit;
> + }
> +
> + ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
> + if (ret) {
> + dev_err(&vdev->dev, "HFI GET_CAPS failed: %d\n", ret);
> + goto err_hfi_deinit;
> + }
> +
> + /* Register the display resources before exposing the DRM component. */
> + ret = dcp_hfi_send_resource_register(drv->hfi);
> + if (ret) {
> + dev_err(&vdev->dev, "HFI RESOURCE_REGISTER failed: %d\n", ret);
> + goto err_hfi_deinit;
> + }
> +
> + dev_dbg(&vdev->dev,
> + "DCP caps: %u displays, max %ux%u, %u planes\n",
> + drv->caps.num_displays, drv->caps.max_width, drv->caps.max_height,
> + drv->caps.max_planes);
> +
> + dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
> + if (!dcp_kms) {
> + ret = -ENOMEM;
> + goto err_hfi_deinit;
> + }
> +
> + dcp_kms->drv = drv;
> + dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
> + dcp_kms->hfi = drv->hfi;
> + dcp_kms->caps = &drv->caps;
> + dcp_kms->dpus_np = drv->dcp_subsystem_node;
> + dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
> + drv->kms = dcp_kms;
> +
> + ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
> + if (ret) {
> + dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
> + goto err_hfi_deinit;
> + }
> +
> + priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv) {
> + ret = -ENOMEM;
> + goto err_hfi_deinit;
> + }
> +
> + priv->kms = &dcp_kms->base;
> + priv->kms_init = dcp_kms_init;
> + dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
> + dcp_kms->rpm_enabled = true;
> +
> + /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
> + ret = dcp_component_register(drv);
> + if (ret) {
> + dev_err_probe(&vdev->dev, ret, "component register failed\n");
> + goto err_hfi_deinit;
> + }
> +
> + dcp_panic_notifier_register(drv);
> +
> + dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
> + return 0;
> +
> +err_hfi_deinit:
> + dcp_virtio_hfi_cleanup(drv);
> + goto err_fn_dev;
> +err_del_vqs:
> + virtio_reset_device(vdev);
> + vdev->config->del_vqs(vdev);
> +err_fn_dev:
> + dcp_subsystem_dev_deinit(drv);
> + return ret;
> +}
> +
> +static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
> +{
> + struct virtio_device *vdev = drv->vdev;
> +
> + if (!drv->hfi)
> + return;
> +
> + dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
> +
> + dcp_hfi_stop(drv->hfi);
> + virtio_reset_device(vdev);
> + virtio_synchronize_cbs(vdev);
> + dcp_hfi_quiesce_queues(drv->hfi);
> + vdev->config->del_vqs(vdev);
> + dcp_hfi_deinit(drv->hfi);
> +}
> +
> +/* Remove DRM users before tearing down HFI, IPCC, or the VirtIO queues. */
> +static void dcp_virtio_remove(struct virtio_device *vdev)
> +{
> + struct dcp_drv *drv = vdev->priv;
> +
> + dev_dbg(&vdev->dev, "DCP VirtIO remove\n");
> +
> + dcp_component_unregister(drv);
> + dcp_panic_notifier_unregister(drv);
> +
> + dcp_virtio_hfi_cleanup(drv);
> +
> + dcp_subsystem_dev_deinit(drv);
> +}
> +
> +/* KMS shutdown must complete while the firmware transport is available. */
> +static void dcp_virtio_shutdown(struct virtio_device *vdev)
> +{
> + struct dcp_drv *drv = vdev->priv;
> +
> + pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
> + msm_kms_shutdown(drv->dcp_subsystem_dev);
> + pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
> +}
> +
> +static const struct virtio_device_id dcp_virtio_id_table[] = {
> + { VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },
What exactly does it mean? Are you using Vendor ID (which should be
Qualcomm) to distinguish between possible QPSFI devices?
> + { 0 },
> +};
> +MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
> +
> +static const struct dev_pm_ops dcp_virtio_pm_ops = {
> + .prepare = dcp_pm_prepare,
> + .complete = dcp_pm_complete,
> +};
> +
> +static struct virtio_driver dcp_virtio_driver = {
> + .driver = {
> + .name = "qcom-dcp-hfi",
> + .owner = THIS_MODULE,
> + .pm = &dcp_virtio_pm_ops,
> + },
> + .id_table = dcp_virtio_id_table,
> + .feature_table = dcp_virtio_features,
> + .feature_table_size = ARRAY_SIZE(dcp_virtio_features),
> + .probe = dcp_virtio_probe,
> + .remove = dcp_virtio_remove,
> + .shutdown = dcp_virtio_shutdown,
> +};
> +
> +int dcp_virtio_register(void)
> +{
> + int ret;
> +
> + ret = dcp_virtio_transport_register();
> + if (ret)
> + return ret;
> +
> + ret = register_virtio_driver(&dcp_virtio_driver);
> + if (ret)
> + dcp_virtio_transport_unregister();
> +
> + return ret;
> +}
> +
> +void dcp_virtio_unregister(void)
> +{
> + unregister_virtio_driver(&dcp_virtio_driver);
> + dcp_virtio_transport_unregister();
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.h b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> index a9c3b9aff61d..3ded8293ab18 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> @@ -8,9 +8,11 @@
>
> #include <linux/device.h>
> #include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
>
> struct dcp_drv;
> struct dcp_ipcc;
> +#define VIRTIO_VENDOR_ID_DCP_HFI 1
Squatting the ID?
>
> struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev);
>
> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
> index 5e8f3ce26b32..5816c64d7ce7 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -1093,8 +1093,8 @@ static const struct of_device_id msm_gpu_match[] = {
> { },
> };
>
> -static int add_gpu_components(struct device *dev,
> - struct component_match **matchptr)
> +int msm_add_gpu_components(struct device *dev,
> + struct component_match **matchptr)
Separate commit.
> {
> struct device_node *np;
>
> @@ -1153,7 +1153,7 @@ int msm_drv_probe(struct device *master_dev,
> }
>
> if (!msm_separate_gpu_kms_components()) {
> - ret = add_gpu_components(master_dev, &match);
> + ret = msm_add_gpu_components(master_dev, &match);
> if (ret)
> return ret;
> }
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index 2840e8a5052d..aa5dfd82ac6f 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -523,6 +523,8 @@ static inline unsigned long timeout_to_jiffies(const ktime_t *timeout)
> /* Driver helpers */
>
> extern const struct component_master_ops msm_drm_ops;
> +int msm_add_gpu_components(struct device *dev,
> + struct component_match **matchptr);
>
> int msm_kms_pm_prepare(struct device *dev);
> void msm_kms_pm_complete(struct device *dev);
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses
2026-10-26 6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
@ 2026-10-26 10:22 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 10:22 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] drm/msm/dcp: uninitialized completions in dcp_hfi_init() and dcp_hfi_deinit()
- [High] drm/msm/dcp: race re-scheduling resource_vote_work after dcp_hfi_stop()
- [High] drm/msm/dcp: race between dcp_hfi_clear_power_control_cb() and power_cb()
- [Medium] drm/msm/dcp: malformed RESOURCE_VOTE clobbers vote in dcp_hfi_dispatch_event()
- [Low] drm/msm/dcp: declarations placed after #endif include guard in dcp_hfi.h
--
commit 181941c645b83584ab1315f1b30bec1f0ef47673
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Handle asynchronous HFI responses
This commit adds handling for asynchronous HFI messages in the DCP driver,
dispatching display events, caching capability payloads, invoking power
control callbacks, and deferring resource vote updates to a workqueue.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> index 23e1cd39b89d4..055e9c584892b 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
[ ... ]
> @@ -167,3 +211,8 @@ int dcp_hfi_send_no_wait_atomic_flags(struct dcp_hfi *hfi, u32 cmd,
> enum hfi_packet_payload_type payload_type,
> void *payload, u32 payload_size, u32 tx_flags);
> #endif /* __DCP_HFI_H__ */
> +void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
> + void (*cb)(void *priv, u32 display_id,
> + u32 power_mask, bool enable,
> + u32 packet_id));
> +void dcp_hfi_clear_power_control_cb(struct dcp_hfi *hfi, void *priv);
[Severity: Low]
Should the declarations for dcp_hfi_set_power_control_cb() and
dcp_hfi_clear_power_control_cb() be placed before #endif /* __DCP_HFI_H__ */
so they remain inside the header include guard?
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> index 9f7ac798e13c4..774a5afac0f0a 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
[ ... ]
> @@ -227,6 +233,218 @@ void dcp_hfi_cmd_callback(struct virtqueue *vq)
> spin_unlock_irqrestore(&hfi->cmd_lock, flags);
> }
>
> +static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
> + struct completion *done,
> + u8 *dst, u32 *dst_len,
> + const struct hfi_packet_info *pkt_info,
> + const char *name)
> +{
> + u32 n;
> +
> + if (!pkt_info->payload_ptr || !pkt_info->payload_size) {
> + dev_dbg(hfi->dev, "%s: empty payload\n", name);
> + return;
> + }
> +
> + if (pkt_info->payload_size > DCP_HFI_CAPS_BUF_SIZE) {
> + dev_warn(hfi->dev, "%s: payload too large: %u\n",
> + name, pkt_info->payload_size);
> + return;
> + }
> +
> + n = pkt_info->payload_size;
> + memcpy(dst, pkt_info->payload_ptr, n);
> + *dst_len = n;
> + if (done)
> + complete(done);
> + dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
> +}
> +
> +/* Route asynchronous events and capability packets before flag handling. */
> +static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
> + const struct hfi_packet_info *pkt_info)
> +{
> + switch (pkt_info->cmd) {
[ ... ]
> + case HFI_COMMAND_DISPLAY_POWER_CONTROL: {
> + u32 display_id = pkt_info->id & 0xFFFF;
> + u32 power_mask = 0;
> + bool enable = true;
> + unsigned long flags;
> + void *power_priv;
> + void (*power_cb)(void *priv, u32 display_id,
> + u32 power_mask, bool enable, u32 packet_id);
> +
> + if (pkt_info->payload_ptr &&
> + pkt_info->payload_size >= 2 * sizeof(u32)) {
> + const u32 *p = pkt_info->payload_ptr;
> +
> + power_mask = p[0];
> + enable = (p[1] == HFI_TRUE);
> + }
> +
> + spin_lock_irqsave(&hfi->resp_drain_lock, flags);
> + power_cb = hfi->power_control_cb;
> + power_priv = hfi->power_control_priv;
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
> +
> + if (power_cb)
> + power_cb(power_priv, display_id, power_mask, enable,
> + pkt_info->packet_id);
> + return true;
> + }
[Severity: High]
Can power_cb() race with dcp_hfi_clear_power_control_cb() during teardown?
In dcp_hfi_dispatch_event(), HFI_COMMAND_DISPLAY_POWER_CONTROL copies
hfi->power_control_cb and hfi->power_control_priv under hfi->resp_drain_lock
and then drops the lock before calling power_cb(power_priv, ...).
Meanwhile, dcp_hfi_clear_power_control_cb() only clears those pointers under
hfi->resp_drain_lock and returns immediately:
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_clear_power_control_cb() {
spin_lock_irqsave(&hfi->resp_drain_lock, flags);
if (hfi->power_control_priv == priv) {
hfi->power_control_priv = NULL;
hfi->power_control_cb = NULL;
}
spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
}
If a caller unregisters its callback and cancels its work or frees priv while
dcp_hfi_dispatch_event() has already dropped hfi->resp_drain_lock, couldn't
power_cb() still run after dcp_hfi_clear_power_control_cb() returns,
re-scheduling work or accessing power_priv after it is torn down?
> +
> + case HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS: {
> + unsigned long flags;
> +
> + spin_lock_irqsave(&hfi->caps_lock, flags);
> + dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.device_done,
> + hfi->caps_cache.device_buf,
> + &hfi->caps_cache.device_len,
> + pkt_info, "DEVICE_INIT_DEVICE_CAPS");
> + spin_unlock_irqrestore(&hfi->caps_lock, flags);
> + return true;
> + }
> +
> + case HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS: {
> + unsigned long flags;
> +
> + spin_lock_irqsave(&hfi->caps_lock, flags);
> + dcp_hfi_store_caps_payload(hfi, &hfi->caps_cache.display_done,
> + hfi->caps_cache.display_buf,
> + &hfi->caps_cache.display_len,
> + pkt_info, "DEVICE_INIT_DISPLAY_CAPS");
> + spin_unlock_irqrestore(&hfi->caps_lock, flags);
> + return true;
> + }
[ ... ]
> + case HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE: {
> + unsigned long vote_flags;
> + u32 payload_size = pkt_info->payload_size;
> + bool valid = pkt_info->payload_ptr && payload_size &&
> + payload_size <= DCP_HFI_RESOURCE_VOTE_BUF_SIZE &&
> + payload_size % sizeof(struct dcp_resource_vote_wire) == 0;
> +
> + if (!valid) {
> + dev_warn_ratelimited(hfi->dev,
> + "dropping malformed RESOURCE_VOTE payload size=%u max=%u\n",
> + payload_size, DCP_HFI_RESOURCE_VOTE_BUF_SIZE);
> + payload_size = 0;
> + }
> +
> + spin_lock_irqsave(&hfi->resource_vote_lock, vote_flags);
> + if (payload_size)
> + memcpy(hfi->resource_vote_buf, pkt_info->payload_ptr,
> + payload_size);
> + hfi->resource_vote_len = payload_size;
> + hfi->resource_vote_generation++;
> + spin_unlock_irqrestore(&hfi->resource_vote_lock, vote_flags);
[Severity: Medium]
When a malformed HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE packet arrives,
why does this set payload_size = 0 and continue instead of returning early?
By setting hfi->resource_vote_len = 0 and incrementing
hfi->resource_vote_generation under hfi->resource_vote_lock, a malformed
packet will clobber any valid resource vote already queued in
hfi->resource_vote_buf that dcp_hfi_resource_vote_work() has not yet
processed, causing dcp_hfi_resource_vote_work() to see len == 0 and drop the
earlier valid vote.
[ ... ]
> @@ -726,7 +987,10 @@ int dcp_hfi_init(struct dcp_drv *drv,
>
> spin_lock_init(&hfi->cmd_lock);
> spin_lock_init(&hfi->pending_lock);
> + spin_lock_init(&hfi->resource_vote_lock);
> spin_lock_init(&hfi->resp_drain_lock);
> + spin_lock_init(&hfi->caps_lock);
> + mutex_init(&hfi->caps_cache_lock);
[Severity: High]
Where are hfi->caps_cache.device_done and hfi->caps_cache.display_done
initialized?
In dcp_hfi_init(), hfi is allocated with devm_kzalloc() and response buffers
are posted immediately via dcp_hfi_prepost_resp_bufs(), but init_completion()
is never called on hfi->caps_cache.device_done or
hfi->caps_cache.display_done. In addition, dcp_hfi_deinit() zeroes the entire
struct with memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache)), which wipes
the embedded completion waitqueues and spinlocks.
If HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS or
HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS arrives in
dcp_hfi_resp_callback()->dcp_hfi_dispatch_event()->dcp_hfi_store_caps_payload()
while those completions are zeroed, won't complete(done) dereference a NULL
wait.task_list pointer in swake_up_locked()?
[ ... ]
> @@ -804,6 +1097,7 @@ void dcp_hfi_stop(struct dcp_hfi *hfi)
> atomic_set(&hfi->shutdown_in_progress, 1);
> spin_unlock_irqrestore(&hfi->cmd_lock, flags);
>
> + cancel_work_sync(&hfi->resource_vote_work);
> cancel_delayed_work_sync(&hfi->resp_repost_work);
> dcp_hfi_cancel_pending(hfi, -ENODEV);
> }
[Severity: High]
Can dcp_hfi_resp_callback() re-schedule hfi->resource_vote_work after
dcp_hfi_stop() has already called cancel_work_sync()?
In dcp_hfi_resp_callback(), hfi->shutdown_in_progress is only checked at the
start of the function, and hfi->resp_drain_lock is dropped while unpacking
packets and calling dcp_hfi_dispatch_event():
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
if (!hfi || atomic_read(&hfi->shutdown_in_progress))
return;
spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
virtqueue_disable_cb(vq);
for (;;) {
while ((buf = virtqueue_get_buf(vq, &len)) != NULL) {
...
spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
...
} else if (dcp_hfi_dispatch_event(hfi, &pkt_info)) {
}
If dcp_hfi_stop() runs on another CPU while dcp_hfi_resp_callback() is
processing a buffer, dcp_hfi_stop() can set hfi->shutdown_in_progress = 1 and
finish cancel_work_sync(&hfi->resource_vote_work) before
dcp_hfi_dispatch_event() handles HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE
and calls schedule_work(&hfi->resource_vote_work).
Since neither HFI_COMMAND_DEVICE_CALLBACK_RESOURCE_VOTE nor
dcp_hfi_resource_vote_work() checks hfi->shutdown_in_progress, and neither
dcp_hfi_quiesce_queues() nor dcp_hfi_deinit() cancels resource_vote_work
after virtqueue callbacks are synchronized, could dcp_hfi_resource_vote_work()
execute after teardown completes?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=7
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID
2026-10-26 6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
2026-10-26 9:59 ` Dmitry Baryshkov
@ 2026-10-26 10:27 ` Michael S. Tsirkin
1 sibling, 0 replies; 55+ messages in thread
From: Michael S. Tsirkin @ 2026-10-26 10:27 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jason Wang, Eugenio Pérez,
Xuan Zhuo, Sumit Semwal, Christian König, Philipp Stanner,
Bjorn Andersson, Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, Flemin Jose, Mahadevan P,
virtualization, linux-arm-msm, dri-devel, freedreno, devicetree,
linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:28AM +0530, Jayant Shekhar wrote:
> The Qualcomm Platform Services Firmware Interface (QPSFI) is exposed
> as a VirtIO device with device ID 52. Define VIRTIO_ID_QPSFI in the
> common VirtIO ID header so transport and function drivers use the
> shared identifier.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> include/uapi/linux/virtio_ids.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
> index f9056af0c622..6c6f283733b1 100644
> --- a/include/uapi/linux/virtio_ids.h
> +++ b/include/uapi/linux/virtio_ids.h
> @@ -70,6 +70,7 @@
> #define VIRTIO_ID_GPIO 41 /* virtio gpio */
> #define VIRTIO_ID_SPI 45 /* virtio spi */
> #define VIRTIO_ID_MEDIA 48 /* virtio media */
> +#define VIRTIO_ID_QPSFI 52 /* Qualcomm Platform Services Firmware Interface */
>
> /*
> * Virtio Transitional IDs
virtio IDs are allocated through the virtio TC.
Reserving this spcific one as a generic VIRTIO_ID_VENDOR is now under
discussion on the virtio-comments mailing list:
https://lore.kernel.org/all/20261022091645-mutt-send-email-mst@kernel.org/
Please
- switch your driver to use that + qualcomm vendor id +
validating the device through the DT
- do not merge this yet, before the virtio TC has reserved the
device ID.
> --
> 2.43.0
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications
2026-10-26 6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
@ 2026-10-26 10:36 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 10:36 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/msm/dcp: unsynchronized resp_vq kick in dcp_ipcc_handle_rx()
- [Medium] drm/msm/dcp: dcp_ipcc_send_power_notify() misuses mbox_client_txdone()
- [Low] drm/msm/dcp: missing MAILBOX dependency in Kconfig causes link failure
--
commit 847d1e1e504619e829df644d8cb1af09e3cef49c
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add IPCC power notifications
This commit adds an inter-processor communication controller mailbox client
to wake and acknowledge display coprocessor firmware during power transitions.
It implements serialized notifications, bounded acknowledgment waits, wake
interrupt handling, and callback lifetime management.
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 56c48fe2e2c5f..9c698c4f49495 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -73,6 +73,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
>
> msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_core.o \
> + dcp/dcp_ipcc.o \
[Severity: Low]
Will this cause a link failure when the mailbox framework is disabled?
In drivers/gpu/drm/msm/Makefile, dcp/dcp_ipcc.o is built unconditionally
whenever the display coprocessor option is enabled, and dcp_ipcc_init(),
dcp_ipcc_free_channel(), and dcp_ipcc_send_power_notify() call
mbox_request_channel(), mbox_free_channel(), mbox_send_message(), and
mbox_client_txdone().
However, include/linux/mailbox_client.h does not provide inline stubs when
mailbox support is disabled, and drivers/gpu/drm/msm/Kconfig only lists:
config DRM_MSM_DCP
bool "Enable DCP firmware-delegated display support in MSM DRM driver"
depends on DRM_MSM
depends on VIRTIO
Should the Kconfig entry also depend on or select the mailbox subsystem?
[ ... ]
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_ipcc.c b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
> new file mode 100644
> index 0000000000000..e161939dddc1e
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
> @@ -0,0 +1,173 @@
[ ... ]
> +static void dcp_ipcc_handle_rx(struct dcp_ipcc *ipcc)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ipcc->state_lock, flags);
> + if (ipcc->shutting_down) {
> + spin_unlock_irqrestore(&ipcc->state_lock, flags);
> + return;
> + }
> +
> + complete(&ipcc->power_notify_done);
> + if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
> + virtqueue_notify(ipcc->resp_vq);
> +
> + spin_unlock_irqrestore(&ipcc->state_lock, flags);
> +}
[Severity: High]
Can calling virtqueue_kick_prepare(ipcc->resp_vq) here under only
ipcc->state_lock race with concurrent response virtqueue operations?
In drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, operations on the same response
virtqueue in dcp_hfi_resp_callback(), dcp_hfi_resp_repost_work(), and
dcp_hfi_quiesce_queues() are serialized under hfi->resp_drain_lock:
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
...
repost:
spin_lock_irqsave(&hfi->resp_drain_lock, drain_flags);
dcp_hfi_buf_init_sg(buf, DCP_HFI_BUF_SIZE);
if (virtqueue_add_inbuf_premapped(vq, &buf->sg, 1, buf,
NULL, GFP_ATOMIC)) {
list_add_tail(&buf->node, &hfi->resp_repost_list);
schedule_delayed_work(&hfi->resp_repost_work,
msecs_to_jiffies(20));
}
}
if (virtqueue_kick_prepare(vq))
virtqueue_notify(vq);
}
Because virtqueue_kick_prepare() requires external serialization with other
operations on the same virtqueue and updates vq->num_added and vq->in_use,
running dcp_ipcc_handle_rx() from dcp_ipcc_rx_callback() or
dcp_ipcc_power_notify_irq() without holding hfi->resp_drain_lock can corrupt
vq->num_added kick tracking or trigger a virtio ring in_use check failure.
In addition, complete(&ipcc->power_notify_done) runs before
virtqueue_notify(ipcc->resp_vq), waking waiters in
dcp_ipcc_send_power_notify() before the response virtqueue notification has
been sent.
[ ... ]
> +int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
> +{
> + unsigned long timeout;
> + int ret;
> +
> + mutex_lock(&ipcc->power_notify_lock);
> +
> + reinit_completion(&ipcc->power_notify_done);
> +
> + ret = mbox_send_message(ipcc->power_chan, NULL);
> + mbox_client_txdone(ipcc->power_chan, ret);
> + if (ret < 0) {
> + dev_err(ipcc->client.dev,
> + "IPCC TX: power notify failed: %d\n", ret);
> + goto out_unlock;
> + }
[Severity: Medium]
Should mbox_client_txdone() only be called with 0 after verifying that
mbox_send_message() succeeded?
When mbox_send_message() fails (ret < 0), no message was queued for this
request, yet calling mbox_client_txdone(ipcc->power_chan, ret) before the
error check still invokes tx_tick(chan, ret) on the mailbox channel (and would
dereference a null pointer if ipcc->power_chan were null).
Furthermore, when mbox_send_message() succeeds in non-blocking mode
(ipcc->client.tx_block = false), it returns the non-negative ring buffer slot
index from add_to_rbuf(), whereas mbox_client_txdone() expects 0 on success or
a negative errno on failure.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=8
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state
2026-10-26 6:21 ` [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state Jayant Shekhar
@ 2026-10-26 10:49 ` Dmitry Baryshkov
2026-10-26 11:33 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 10:49 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:21AM +0530, Jayant Shekhar wrote:
> Create the DRM CRTC objects and shared state needed to expose the
> firmware-owned DCP pipeline through MSM DRM. Without CRTC state,
> DRM cannot track firmware sessions, aggregate bandwidth, or validate
> the display configuration.
>
> Implement CRTC objects, display lookup and session state, CRTC
> bandwidth aggregation, and the shared plane state used by validation
> and plane programming.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/Makefile | 2 +
> drivers/gpu/drm/msm/dcp/dcp_crtc.c | 725 ++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_crtc.h | 104 +++++
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 21 +
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 54 +++
> drivers/gpu/drm/msm/dcp/dcp_plane_state.h | 37 ++
If you need plane state for the CRTCs, then put planes support before
adding CRTCs.
> 6 files changed, 943 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 77c268aabca6..80341ce07d53 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -72,6 +72,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
> disp/mdp5/mdp5_smp.o \
>
> msm-display-$(CONFIG_DRM_MSM_DCP) += \
> + dcp/dcp_crtc.o \
> + dcp/dcp_dpu_kms.o \
> dcp/dcp_hfi_core.o \
> dcp/dcp_hfi_crtc.o \
> dcp/dcp_hfi_dsi.o \
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> new file mode 100644
> index 000000000000..ec4f74c2920f
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> @@ -0,0 +1,725 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP CRTC implementation.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/wait.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_blend.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_framebuffer.h>
> +#include <drm/drm_rect.h>
> +#include <drm/drm_vblank.h>
> +
> +#include <drm/drm_managed.h>
> +
> +#include "msm_drv.h"
> +
> +#include "dcp_crtc.h"
> +#include "dcp_drv.h"
> +#include "dcp_hfi.h"
> +#include "dcp_dpu_kms.h"
> +#include "dcp_plane_state.h"
> +
> +#include "hfi/hfi_defs_display.h"
> +#include "hfi/hfi_properties_display.h"
> +
> +#define DCP_FRAME_DONE_TIMEOUT_MS 500
> +
> +static struct dcp_dpu_kms *dcp_crtc_get_kms(struct drm_crtc *crtc)
> +{
> + struct msm_drm_private *priv = crtc->dev->dev_private;
> +
> + return to_dcp_dpu_kms(priv->kms);
> +}
> +
> +static struct drm_crtc_state *
> +dcp_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc_state *cstate;
> +
> + if (WARN_ON(!crtc->state))
> + return NULL;
> +
> + cstate = kzalloc_obj(*cstate, GFP_KERNEL);
> + if (!cstate)
> + return NULL;
> +
> + __drm_atomic_helper_crtc_duplicate_state(crtc, &cstate->base);
> +
> + cstate->topology_mixer_count =
> + to_dcp_crtc_state(crtc->state)->topology_mixer_count;
> + cstate->topology_bits_per_component =
> + to_dcp_crtc_state(crtc->state)->topology_bits_per_component;
> + memcpy(cstate->mixer_roi, to_dcp_crtc_state(crtc->state)->mixer_roi,
> + sizeof(cstate->mixer_roi));
> + cstate->update_roi = to_dcp_crtc_state(crtc->state)->update_roi;
> + return &cstate->base;
> +}
> +
> +static void dcp_crtc_atomic_destroy_state(struct drm_crtc *crtc,
> + struct drm_crtc_state *state)
> +{
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
> +
> + __drm_atomic_helper_crtc_destroy_state(state);
> + kfree(cstate);
> +}
> +
> +static struct drm_crtc_state *
> +dcp_crtc_atomic_create_state(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc_state *cstate;
> +
> + cstate = kzalloc_obj(*cstate, GFP_KERNEL);
> + if (!cstate)
> + return ERR_PTR(-ENOMEM);
> +
> + __drm_atomic_helper_crtc_state_init(&cstate->base, crtc);
> + cstate->topology_mixer_count = 1;
> + cstate->topology_bits_per_component = 8;
> + return &cstate->base;
> +}
> +
> +static void dcp_crtc_aggregate_bw(struct drm_crtc_state *crtc_state,
> + bool enable)
> +{
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
> + struct drm_plane *plane;
> + const struct drm_plane_state *pstate;
> +
> + cstate->dram_ib = 0;
> + cstate->dram_ab = 0;
> + cstate->llcc_ib = 0;
> + cstate->llcc_ab = 0;
> + cstate->core_clk = 0;
> +
> + if (!enable)
> + return;
> +
> + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
> + if (!pstate->visible || !pstate->fb)
Can it be visible if there is no fb?
> + continue;
> +
> + const struct dcp_plane_state *dcp_pstate = to_dcp_plane_state(pstate);
> +
> + cstate->dram_ib = max(cstate->dram_ib, dcp_pstate->fetch_bw);
> + cstate->dram_ab += dcp_pstate->fetch_bw;
> + cstate->llcc_ib = max(cstate->llcc_ib, dcp_pstate->fetch_bw);
> + cstate->llcc_ab += dcp_pstate->fetch_bw;
> + cstate->core_clk = max(cstate->core_clk, dcp_pstate->clk);
> + }
> +}
> +
> +static enum drm_mode_status
> +dcp_crtc_mode_valid(struct drm_crtc *crtc,
> + const struct drm_display_mode *mode)
> +{
> + struct dcp_dpu_kms *kms = dcp_crtc_get_kms(crtc);
> + u32 max_width, max_height;
> +
> + dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
> + &max_width, &max_height);
> +
> + return drm_mode_validate_size(mode, max_width, max_height);
> +}
> +
> +static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + u32 mixer_count;
> + u32 session_id;
> + u32 i;
> + int ret;
> +
> + if (!crtc->state->active)
> + return;
Can't you just use DRM_PLANE_COMMIT_ACTIVE_ONLY?
> + if (!dcp_kms->commit_pm_active) {
> + cstate->hfi_error = -EIO;
> + return;
> + }
> +
> + if (!dcp_crtc_get_session(crtc, &session_id)) {
> + cstate->hfi_error = -ENODEV;
> + return;
> + }
> +
> + if (cstate->core_clk > 0 || cstate->dram_ib > 0) {
> + ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id,
> + cstate->dram_ib, cstate->dram_ab,
> + cstate->llcc_ib, cstate->llcc_ab,
> + cstate->core_clk);
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
> + }
> +
> + mixer_count = cstate->topology_mixer_count;
> + if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
> + cstate->hfi_error = -EINVAL;
> + return;
> + }
> + for (i = 0; i < mixer_count; i++) {
> + struct dcp_mixer_roi *roi = &cstate->mixer_roi[i];
> +
> + if (!roi->w || !roi->h) {
> + drm_err(crtc->dev, "CRTC%u: invalid zero-sized LM%u ROI\n",
> + dcp_crtc->display_id, i);
> + cstate->hfi_error = -EINVAL;
> + return;
> + }
> +
> + ret = dcp_hfi_crtc_set_resource(dcp_kms->hfi, session_id,
> + i, roi->w, roi->h, roi->x, roi->y);
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
> + }
> +
> + ret = dcp_hfi_crtc_set_dest_roi(dcp_kms->hfi, session_id,
> + cstate->update_roi.x, cstate->update_roi.y,
> + cstate->update_roi.w, cstate->update_roi.h);
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
> +}
> +
> +static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + unsigned long flags;
> + u32 session_id;
> + int ret;
> +
> + if (!crtc->state->enable)
> + return;
So, atomic_being happens only for active CRTCs, but atomic_flush also
happens for enabled, but not active CRTCs. Why?
> + drm_dbg_driver(crtc->dev, "DCP_FLUSH CRTC%u mask=0x%x err=%d\n",
> + dcp_crtc->display_id, crtc->state->plane_mask,
> + cstate->hfi_error);
> +
> + if (!dcp_crtc_get_session(crtc, &session_id)) {
> + drm_dbg_driver(crtc->dev,
> + "CRTC%u: atomic flush without HFI session\n",
> + dcp_crtc->display_id);
> + goto flush_done;
> + }
> +
> + if (cstate->hfi_error)
> + goto flush_done;
> +
> + ret = dcp_hfi_crtc_set_scan_sequence_id(dcp_kms->hfi, session_id,
> + ++dcp_crtc->commit_seq_id);
> + if (ret)
> + goto flush_error;
> +
> + ret = dcp_hfi_crtc_set_autorefresh(dcp_kms->hfi,
> + session_id, true, 1);
> + if (ret)
> + goto flush_error;
> +
> + goto flush_done;
> +
> +flush_error:
> + cstate->hfi_error = ret;
> +
> +flush_done:
> + spin_lock_irqsave(&crtc->dev->event_lock, flags);
> + dcp_crtc->event = crtc->state->event;
> + crtc->state->event = NULL;
> + spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> +}
> +
> +void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id)
> +{
> + struct dcp_crtc *dcp_crtc;
> + unsigned long flags;
> +
> + if (!crtc)
> + return;
Why?
> +
> + dcp_crtc = to_dcp_crtc(crtc);
> + spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
> + dcp_crtc->session_id = session_id;
> + dcp_crtc->session_valid = true;
> + spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
> +}
> +
> +void dcp_crtc_clear_session(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc;
> + unsigned long flags;
> +
> + if (!crtc)
> + return;
Why?
> +
> + dcp_crtc = to_dcp_crtc(crtc);
> + spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
> + dcp_crtc->session_valid = false;
> + dcp_crtc->session_id = 0;
> + spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
> +}
> +
> +bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id)
> +{
> + struct dcp_crtc *dcp_crtc;
> + unsigned long flags;
> + bool valid;
> +
> + if (!crtc)
> + return false;
Why?
> +
> + dcp_crtc = to_dcp_crtc(crtc);
> + spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
> + valid = dcp_crtc->session_valid;
> + if (valid && session_id)
> + *session_id = dcp_crtc->session_id;
> + spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
> +
> + return valid;
> +}
> +
> +void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
> + u32 mixer_count,
> + u32 bits_per_component)
> +{
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
> +
> + cstate->topology_mixer_count = mixer_count;
> + cstate->topology_bits_per_component = bits_per_component;
> +}
> +
> +void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + u32 session_id;
> + int ret;
> +
> + if (cstate->hfi_error) {
> + drm_err(crtc->dev,
> + "DCP_TRIGGER CRTC%u suppressed after HFI error %d\n",
> + dcp_crtc->display_id, cstate->hfi_error);
> + return;
> + }
> +
> + if (!dcp_crtc_get_session(crtc, &session_id)) {
> + drm_warn(crtc->dev,
> + "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
> + dcp_crtc->display_id);
> + return;
> + }
> +
> + /* MSM serializes commits, so only one frame completion may be pending. */
> + if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
> + drm_err(crtc->dev,
> + "CRTC%u: frame completion already pending\n",
> + dcp_crtc->display_id);
> + cstate->hfi_error = -EBUSY;
> + return;
> + }
> + dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
> +
> + ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
> + if (ret) {
> + drm_err(crtc->dev,
> + "HFI commit failed for CRTC%u (session=%u): %d\n",
> + dcp_crtc->display_id, session_id, ret);
> + atomic_dec(&dcp_crtc->pending_kickoff_cnt);
> + wake_up(&dcp_crtc->frame_done_wq);
> + }
> +}
> +
> +void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + unsigned long timeout = msecs_to_jiffies(DCP_FRAME_DONE_TIMEOUT_MS);
> +
> + if (!atomic_read(&dcp_crtc->pending_kickoff_cnt))
> + return;
> +
> + if (!wait_event_timeout(dcp_crtc->frame_done_wq,
> + atomic_read(&dcp_crtc->pending_kickoff_cnt) == 0,
> + timeout)) {
> + drm_err(crtc->dev,
> + "frame done timeout for CRTC%u\n",
> + dcp_crtc->display_id);
> + atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
> + }
> +}
> +
> +void dcp_crtc_complete_commit(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + unsigned long flags;
> +
> + if (!crtc->state->active)
> + dcp_crtc_aggregate_bw(crtc->state, false);
> +
> + spin_lock_irqsave(&crtc->dev->event_lock, flags);
> + if (dcp_crtc->event) {
> + drm_crtc_send_vblank_event(crtc, dcp_crtc->event);
> + dcp_crtc->event = NULL;
> + }
> + spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> +}
> +
> +int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + u32 session_id;
> + int ret;
> +
> + if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))
Can HFI be NULL here?
> + return -ENODEV;
> +
> + if (enable)
> + ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
> + session_id, HFI_EVENT_VSYNC);
> + else
> + ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
> + session_id, HFI_EVENT_VSYNC);
> + if (ret)
> + return ret;
> +
> + WRITE_ONCE(dcp_crtc->vblank_enabled, enable);
Why do you need vblank_enabled in addition to the
drm_vblank_crtc::enabled?
> + return 0;
> +}
> +
> +void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> +
> + if (!READ_ONCE(dcp_crtc->vblank_enabled))
> + return;
> +
> + if (ktime_compare(dcp_crtc->vblank_cb_time, ktime_set(0, 0)) == 0)
> + dcp_crtc->vblank_cb_time = ktime_get();
> + else
> + dcp_crtc->vblank_cb_count++;
> + drm_crtc_handle_vblank(crtc);
> +}
> +
> +void dcp_crtc_frame_done_callback(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> +
> + atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0);
> + wake_up(&dcp_crtc->frame_done_wq);
> +}
> +
> +static struct drm_crtc *dcp_crtc_find_by_session_id(struct dcp_drv *drv,
> + u32 session_id)
> +{
> + struct dcp_dpu_kms *dcp_kms;
> + struct drm_crtc *crtc;
> + u32 active_session_id;
> +
> + if (!drv || !drv->kms)
> + return NULL;
Why?
> +
> + dcp_kms = drv->kms;
> +
> + crtc = dcp_dpu_kms_get_crtc(dcp_kms, session_id);
Hmm, why are you passing session_id here even though the function
accepts display_id?
> + if (!crtc || !dcp_crtc_get_session(crtc, &active_session_id) ||
> + active_session_id != session_id)
> + return NULL;
> +
> + return crtc;
> +}
> +
> +void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
> +{
> + struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
> + struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
> +
> + if (crtc)
> + dcp_crtc_vblank_callback(crtc);
> + else if (dev)
Can dev be NULL here?
> + drm_err_ratelimited(dev,
> + "unknown CRTC for HFI VSYNC session %u\n",
> + session_id);
> +}
> +
> +void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id)
> +{
> + struct drm_device *dev = drv && drv->kms ? drv->kms->dev : NULL;
> + struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
> +
> + if (crtc) {
Why? Also, if the !crtc can actually happen, handle it as something
non-standard:
if (!crtc) {
drm_err_ratelimited(...);
return;
}
struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
...
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> +
> + /* A single pending commit makes equality valid across sequence wrap. */
> + if (sequence_id != dcp_crtc->pending_seq_id) {
> + drm_warn(crtc->dev,
> + "CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u expected=%u\n",
> + dcp_crtc->display_id, sequence_id,
> + dcp_crtc->pending_seq_id);
> + return;
> + }
> + dcp_crtc_frame_done_callback(crtc);
Can't you inline it here?
> + } else if (dev)
> + drm_err_ratelimited(dev,
> + "unknown CRTC for HFI frame-done session %u seq=%u\n",
> + session_id, sequence_id);
> +}
> +
> +static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
> + struct drm_crtc_state *crtc_state,
> + struct dcp_crtc_state *cstate)
> +{
> + const struct drm_plane_state *pstate;
> + struct drm_plane *plane;
> + const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
> + u32 mixer_count = cstate->topology_mixer_count;
> + u32 mixer_width = mode->hdisplay / mixer_count;
> + u32 i;
> +
> + if (!mixer_width)
> + return -EINVAL;
> +
> + cstate->update_roi.x = 0;
> + cstate->update_roi.y = 0;
> + cstate->update_roi.w = mode->hdisplay;
> + cstate->update_roi.h = mode->vdisplay;
> + for (i = 0; i < mixer_count; i++) {
> + cstate->mixer_roi[i].x = (mode->hdisplay * i) / mixer_count;
> + cstate->mixer_roi[i].y = 0;
> + cstate->mixer_roi[i].w = (mode->hdisplay * (i + 1)) / mixer_count -
> + cstate->mixer_roi[i].x;
> + cstate->mixer_roi[i].h = mode->vdisplay;
> + drm_dbg_driver(crtc->dev,
> + "DCP_CHECK CRTC%u LM%u roi=(%u,%u %ux%u)\n",
> + to_dcp_crtc(crtc)->display_id, i,
> + cstate->mixer_roi[i].x, cstate->mixer_roi[i].y,
> + cstate->mixer_roi[i].w, cstate->mixer_roi[i].h);
> + }
> +
> + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
> + bool fullscreen;
> + bool assigned = false;
> + u32 mixer_idx;
> +
> + if (!pstate || !pstate->visible || !pstate->fb)
> + continue;
> +
> + for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
> + struct dcp_mixer_roi *mixer = &cstate->mixer_roi[mixer_idx];
> +
> + if (pstate->crtc_x >= mixer->x &&
> + pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w) {
> + assigned = true;
> + break;
> + }
> + }
> +
> + /* Any plane may cover the complete split display. Plane type is
> + * not a resource-selection mechanism in the universal-plane API.
> + */
> + fullscreen = pstate->crtc_x == 0 && pstate->crtc_y == 0 &&
> + pstate->crtc_w == mode->hdisplay &&
> + pstate->crtc_h == mode->vdisplay;
Why do we need to special-handle the fullscreen planes?
> +
> + if (!assigned && !fullscreen && mixer_count > 1) {
> + /*
> + * Split-pipe/source-split and multirect allocation are
> + * deferred until the atomic pipe resource manager is added.
> + * Reject planes needing multiple mixer rectangles for now.
> + */
> + return -EOPNOTSUPP;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct drm_crtc_state *crtc_state =
> + drm_atomic_get_new_crtc_state(state, crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
> + struct drm_plane *plane;
> + const struct drm_plane_state *pstate;
> + u32 max_width, max_height;
> + u32 mixer_count;
> + int num_planes = 0;
> + int ret;
> +
> + drm_dbg_atomic(crtc->dev, "CRTC%u: atomic_check enable=%d active=%d mode=%s\n",
> + crtc->base.id, crtc_state->enable, crtc_state->active,
> + crtc_state->mode.name[0] ? crtc_state->mode.name : "none");
> +
> + if (!crtc_state->enable || !crtc_state->active)
> + return 0;
> +
> + mixer_count = cstate->topology_mixer_count;
> + if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
> + drm_err(crtc->dev,
> + "CRTC%u: invalid mixer count %u (limit 1..%u)\n",
> + crtc->base.id, mixer_count, DCP_MAX_MIXERS);
No, don't let userspace spam the kernel log.
> + return -EINVAL;
> + }
> +
> + ret = dcp_crtc_setup_mixer_rois(crtc, crtc_state, cstate);
> + if (ret)
> + return ret;
> +
> + dcp_mode_limits(&crtc_state->adjusted_mode,
> + dcp_kms->caps->max_width, dcp_kms->caps->max_height,
> + &max_width, &max_height);
> +
> + if (crtc_state->adjusted_mode.hdisplay > max_width ||
> + crtc_state->adjusted_mode.vdisplay > max_height) {
> + drm_err(crtc->dev, "CRTC%u: mode %dx%d exceeds DCP caps %dx%d\n",
> + crtc->base.id,
> + crtc_state->adjusted_mode.hdisplay,
> + crtc_state->adjusted_mode.vdisplay,
> + dcp_kms->caps->max_width,
> + dcp_kms->caps->max_height);
> + return -E2BIG;
> + }
> +
> + /* Limit visible planes to the firmware blend-stage count. */
> + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
> + if (!pstate || !pstate->visible)
> + continue;
> + num_planes++;
I think I commented here. Can't you use normalized zpos in plane's
atomic_check instead?
> + }
> +
> + if (num_planes > DCP_MAX_BLEND_STAGES) {
> + drm_err(crtc->dev, "CRTC%u: %d planes exceeds max blend stages %d\n",
> + crtc->base.id, num_planes,
> + DCP_MAX_BLEND_STAGES);
> + return -EINVAL;
> + }
> +
> + dcp_crtc_aggregate_bw(crtc_state, true);
> +
> + return 0;
> +}
> +
> +static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
> +{
> + return drm_crtc_vblank_count(crtc);
> +}
> +
> +static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + u32 session_id;
> + int ret;
> +
> + if (!dcp_crtc_get_session(crtc, &session_id))
> + return;
> +
> + ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
> + HFI_MODE_DPMS_ON);
> + if (ret) {
> + drm_err(crtc->dev, "CRTC%u: DPMS ON failed: %d\n",
> + to_dcp_crtc(crtc)->display_id, ret);
> + cstate->hfi_error = ret;
> + }
> +
> + drm_crtc_vblank_on(crtc);
> +}
> +
> +static void dcp_crtc_atomic_disable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + u32 session_id;
> + int ret;
> +
> + if (dcp_crtc_get_session(crtc, &session_id)) {
> + ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id, 0, 0, 0, 0, 0);
> + if (ret) {
> + drm_err(crtc->dev,
> + "CRTC%u: clearing bandwidth vote failed: %d\n",
> + to_dcp_crtc(crtc)->display_id, ret);
> + cstate->hfi_error = ret;
> + }
> +
> + ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
> + HFI_MODE_DPMS_OFF);
> + if (ret) {
> + drm_err(crtc->dev, "CRTC%u: DPMS OFF failed: %d\n",
> + to_dcp_crtc(crtc)->display_id, ret);
> + cstate->hfi_error = ret;
> + }
> + }
> +
> + drm_crtc_vblank_off(crtc);
> +}
> +
> +static const struct drm_crtc_funcs dcp_crtc_funcs = {
> + .set_config = drm_atomic_helper_set_config,
> + .page_flip = drm_atomic_helper_page_flip,
> + .atomic_create_state = dcp_crtc_atomic_create_state,
> + .atomic_duplicate_state = dcp_crtc_atomic_duplicate_state,
> + .atomic_destroy_state = dcp_crtc_atomic_destroy_state,
> + .enable_vblank = msm_crtc_enable_vblank,
> + .disable_vblank = msm_crtc_disable_vblank,
> + .get_vblank_counter = dcp_crtc_get_vblank_counter,
> +};
> +
> +static const struct drm_crtc_helper_funcs dcp_crtc_helper_funcs = {
> + .atomic_check = dcp_crtc_atomic_check,
> + .atomic_enable = dcp_crtc_atomic_enable,
> + .atomic_disable = dcp_crtc_atomic_disable,
> + .atomic_begin = dcp_crtc_atomic_begin,
> + .atomic_flush = dcp_crtc_atomic_flush,
> + .mode_valid = dcp_crtc_mode_valid,
> +};
> +
> +struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
> + struct drm_plane *primary_plane,
> + u32 display_id)
> +{
> + struct drm_device *dev = kms->dev;
> + struct dcp_crtc *dcp_crtc;
> + int ret;
> +
> + dcp_crtc = drmm_kzalloc(dev, sizeof(*dcp_crtc), GFP_KERNEL);
> + if (!dcp_crtc)
> + return ERR_PTR(-ENOMEM);
> +
> + dcp_crtc->display_id = display_id;
> +
> + spin_lock_init(&dcp_crtc->spin_lock);
> + init_waitqueue_head(&dcp_crtc->frame_done_wq);
> + atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
> +
> + ret = drmm_crtc_init_with_planes(dev, &dcp_crtc->base,
> + primary_plane, NULL,
> + &dcp_crtc_funcs,
> + NULL);
> + if (ret) {
> + drm_err(dev, "CRTC%u init failed: %d\n", display_id, ret);
> + return ERR_PTR(ret);
> + }
> +
> + drm_crtc_helper_add(&dcp_crtc->base, &dcp_crtc_helper_funcs);
> +
> + drm_dbg_driver(dev, "CRTC%u initialized\n", display_id);
> + return &dcp_crtc->base;
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.h b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
> new file mode 100644
> index 000000000000..ae7046e8d97a
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
> @@ -0,0 +1,104 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP CRTC implementation.
> + */
> +
> +#ifndef __DCP_CRTC_H__
> +#define __DCP_CRTC_H__
> +
> +#include <linux/completion.h>
> +#include <linux/spinlock.h>
> +
> +#include <drm/drm_crtc.h>
> +
> +struct dcp_dpu_kms;
> +struct drm_plane;
> +
> +#define DCP_MAX_MIXERS 6
> +#define DCP_MAX_BLEND_STAGES 12
> +
> +static inline void dcp_mode_limits(const struct drm_display_mode *mode,
> + u32 cap_width, u32 cap_height,
> + u32 *max_width, u32 *max_height)
> +{
> + *max_width = cap_width;
> + *max_height = cap_height;
> +
> + if (mode->hdisplay < mode->vdisplay && cap_width > cap_height) {
> + *max_width = cap_height;
> + *max_height = cap_width;
Why? I guess, the caps interface is just incorrect. We have two
limitations: the maximum width and the maximum clock / bandwdith.
maximum height doesn't seem to make any sense here.
> + }
> +}
> +
> +struct dcp_mixer_roi {
> + u32 x;
> + u32 y;
> + u32 w;
> + u32 h;
> +};
Is this an HFI or an internal value? If it's the latter, please use
drm_rect.
> +
> +struct dcp_crtc {
> + struct drm_crtc base;
> + u32 display_id;
> + u32 session_id;
> + bool session_valid;
Why do we need a separate flag here? When is the session being set and
when is it removed?
> + struct drm_pending_vblank_event *event;
> + wait_queue_head_t frame_done_wq;
> + atomic_t pending_kickoff_cnt;
> + /* Protects CRTC-local vblank and commit state. */
> + spinlock_t spin_lock;
> + u32 vblank_cb_count;
> + bool vblank_enabled;
> + ktime_t vblank_cb_time;
> + bool enabled;
> + u32 commit_seq_id;
> + u32 pending_seq_id;
> +};
> +
> +struct dcp_crtc_state {
> + struct drm_crtc_state base;
> +
> + u32 topology_mixer_count;
> + u32 topology_bits_per_component;
> + struct dcp_mixer_roi mixer_roi[DCP_MAX_MIXERS];
> +
> + struct dcp_mixer_roi update_roi;
It's set exactly to the display mode. Can we drop it?
> + int hfi_error;
Why is it a part of the state? What does it mean if we got an HFI error?
What is a recovery model? Is it fine to restart the session? Or is the
whole device wedged?
> + u64 bw_ctl;
> +
> + u64 dram_ib;
> + u64 dram_ab;
> + u64 llcc_ib;
> + u64 llcc_ab;
> + u64 core_clk;
> +};
> +
> +#define to_dcp_crtc(x) container_of(x, struct dcp_crtc, base)
> +#define to_dcp_crtc_state(x) container_of(x, struct dcp_crtc_state, base)
> +
> +struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
> + struct drm_plane *primary_plane,
> + u32 display_id);
> +
> +void dcp_crtc_commit_kickoff(struct drm_crtc *crtc);
> +void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc);
> +void dcp_crtc_complete_commit(struct drm_crtc *crtc);
> +int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable);
> +void dcp_crtc_vblank_callback(struct drm_crtc *crtc);
> +void dcp_crtc_frame_done_callback(struct drm_crtc *crtc);
Are those used outside of dcp_crtc.c? If not, drop all the prototypes
and definitions which are local to the module. Reorder the functions as
required to remove the need for the forward declarations, where
possible.
> +struct dcp_drv;
> +void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id);
> +void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32 sequence_id);
> +
> +void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id);
> +void dcp_crtc_clear_session(struct drm_crtc *crtc);
> +bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id);
> +void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
> + u32 mixer_count,
> + u32 bits_per_component);
> +
> +#endif /* __DCP_CRTC_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> new file mode 100644
> index 000000000000..44b1c2200aee
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> @@ -0,0 +1,21 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP KMS driver.
> + */
> +
> +#include <drm/drm_crtc.h>
> +
> +#include "dcp_dpu_kms.h"
> +
> +struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
> + u32 display_id)
> +{
> + unsigned int i;
> +
> + for (i = 0; i < dcp_kms->num_crtcs; i++)
> + if (dcp_kms->crtcs[i].display_id == display_id)
> + return dcp_kms->crtcs[i].crtc;
> +
> + return NULL;
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
> new file mode 100644
> index 000000000000..f8010fc5d32f
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
> @@ -0,0 +1,54 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP KMS state.
> + */
> +
> +#ifndef __DCP_DPU_KMS_H__
> +#define __DCP_DPU_KMS_H__
> +
> +#include <linux/spinlock.h>
> +
> +#include "msm_drv.h"
> +#include "msm_kms.h"
> +
> +struct dcp_hfi;
> +struct dcp_caps;
> +struct dcp_dsi_bridge;
> +struct dcp_drv;
> +struct drm_crtc;
> +
> +#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000
> +#define DCP_DPU_MAX_CRTCS 4
> +#define DCP_DPU_MAX_PLANES 12
> +
> +struct dcp_dpu_kms {
> + struct msm_kms base;
> + struct drm_device *dev;
> + struct device *pm_dev;
> + struct dcp_drv *drv;
> + struct dcp_hfi *hfi;
> + struct dcp_caps *caps;
> + struct device_node *dpus_np;
> + struct device *iommu_dev;
> + bool rpm_enabled;
> + bool commit_pm_active;
> + struct dcp_dsi_bridge *dsi_bridge[2];
> + struct {
> + struct drm_crtc *crtc;
> + u32 display_id;
> + } crtcs[DCP_DPU_MAX_CRTCS];
> + unsigned int num_crtcs;
> +};
> +
> +#define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
> +struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dcp_kms,
> + u32 display_id);
> +int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dcp_kms,
> + struct drm_crtc *crtc);
> +
> +int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms);
> +extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
> +int dcp_kms_init(struct drm_device *ddev);
> +
> +#endif /* __DCP_DPU_KMS_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane_state.h b/drivers/gpu/drm/msm/dcp/dcp_plane_state.h
> new file mode 100644
> index 000000000000..aafa93cb69b9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane_state.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP plane state definitions.
> + */
> +
> +#ifndef __DCP_PLANE_STATE_H__
> +#define __DCP_PLANE_STATE_H__
> +
> +#include <drm/drm_plane.h>
> +
> +#define DCP_PLANE_PROP_PKT_MAX_SIZE 512
> +
> +struct dcp_plane_state {
> + struct drm_plane_state base;
> +
> + u64 fetch_bw;
> + u64 clk;
> +
> + u8 prop_pkt[DCP_PLANE_PROP_PKT_MAX_SIZE];
> + size_t prop_pkt_size;
> + u32 prop_pkt_id;
> +
> + bool needs_dirtyfb;
> +
> + u32 src_img_w;
> + u32 src_img_h;
> +
> + u32 hfi_pipe_id;
> + u32 hfi_format;
> +};
> +
> +#define to_dcp_plane_state(x) \
> + container_of(x, struct dcp_plane_state, base)
> +
> +#endif /* __DCP_PLANE_STATE_H__ */
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands
2026-10-26 6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
@ 2026-10-26 10:52 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 10:52 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/msm/dcp: completion corruption and unlocked caps_cache in dcp_hfi_kms.c
- [High] drm/msm/dcp: pipe count clobbering in dcp_hfi_parse_init_caps_kv()
--
commit 90bc8a62bf35e8d6f13a5451d4e21b3d608a5097
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add generic HFI display commands
This commit adds generic HFI display command helpers and capability parsing
for the firmware-owned DCP display pipeline, translating DRM state for
resources, CRTCs, planes, bandwidth, and frame sequencing into HFI commands
and properties.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c
> new file mode 100644
> index 0000000000000..ac8000a8ee900
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c
> @@ -0,0 +1,366 @@
[ ... ]
> +static void dcp_hfi_caps_cache_init(struct dcp_hfi_caps_cache *cache)
> +{
> + init_completion(&cache->device_done);
> + init_completion(&cache->display_done);
> +}
> +
> +static void dcp_hfi_caps_cache_start(struct dcp_hfi *hfi)
> +{
> + memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
> + dcp_hfi_caps_cache_init(&hfi->caps_cache);
> +}
> +
> +static void dcp_hfi_caps_cache_stop(struct dcp_hfi *hfi)
> +{
> + memset(&hfi->caps_cache, 0, sizeof(hfi->caps_cache));
> +}
> +
> +int dcp_hfi_send_device_init(struct dcp_hfi *hfi)
> +{
> + int ret;
> +
> + mutex_lock(&hfi->caps_cache_lock);
> + dcp_hfi_caps_cache_start(hfi);
> +
> + /* Payload and flags follow the HFI device-command definition. */
> + dev_dbg(hfi->dev,
> + "HFI DEVICE_INIT cmd=0x%x flags=0x%x timeout=%ums\n",
> + HFI_COMMAND_DEVICE_INIT,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED | HFI_TX_FLAGS_NON_DISCARDABLE,
> + DCP_HFI_TIMEOUT_INIT_MS);
> +
> + ret = dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DEVICE_INIT,
> + HFI_CMD_BUFF_DEVICE, 0,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_INIT_MS);
> + if (ret) {
> + dev_err(hfi->dev, "HFI DEVICE_INIT failed: %d\n", ret);
> + dcp_hfi_caps_cache_stop(hfi);
> + } else {
> + dev_dbg(hfi->dev, "HFI DEVICE_INIT succeeded\n");
> + }
> +
> + mutex_unlock(&hfi->caps_cache_lock);
> + return ret;
> +}
> +
> +static void dcp_hfi_parse_init_caps_kv(struct dcp_caps *caps,
> + u32 prop_key,
> + const u32 *payload,
> + u32 max_words)
> +{
> + u32 prop_id = HFI_PROP_ID(prop_key);
> + u32 count, i;
> +
> + if (!payload || !max_words)
> + return;
> +
> + switch (prop_id) {
[ ... ]
> + case HFI_PROPERTY_DEVICE_INIT_VIG_INDICES:
> + if (max_words < 1)
> + break;
> + count = payload[0];
> + if (count > max_words - 1)
> + count = max_words - 1;
> + caps->num_vig_pipes = 0;
> + caps->virt_vig_count = 0;
> + for (i = 0; i < count; i++) {
> + u32 idx = payload[1 + i];
> +
> + if (DCP_REC_ID(idx) == 0) {
> + if (caps->num_vig_pipes < DCP_MAX_SSPP_COUNT)
> + caps->vig_pipe_ids[caps->num_vig_pipes++] = idx;
> + } else {
> + if (caps->virt_vig_count < DCP_MAX_SSPP_COUNT)
> + caps->vig_r1_indices[caps->virt_vig_count++] = idx;
> + }
> + }
> + break;
> + case HFI_PROPERTY_DEVICE_INIT_DMA_INDICES:
> + if (max_words < 1)
> + break;
> + count = payload[0];
> + if (count > max_words - 1)
> + count = max_words - 1;
> + caps->num_dma_pipes = 0;
> + caps->virt_dma_count = 0;
> + for (i = 0; i < count; i++) {
> + u32 idx = payload[1 + i];
> +
> + if (DCP_REC_ID(idx) == 0) {
> + if (caps->num_dma_pipes < DCP_MAX_SSPP_COUNT)
> + caps->dma_pipe_ids[caps->num_dma_pipes++] = idx;
> + } else {
> + if (caps->virt_dma_count < DCP_MAX_SSPP_COUNT)
> + caps->dma_r1_indices[caps->virt_dma_count++] = idx;
> + }
> + }
> + break;
[Severity: High]
Won't resetting both the primary and rect-1 pipe counters here clobber the
previously parsed pipe counts when dcp_hfi_get_capabilities() parses multiple
capability buffers into the same struct dcp_caps?
In drivers/gpu/drm/msm/dcp/hfi/hfi_properties_device.h, both rect-0 pipes
(DCP_REC_ID(idx) == 0) and rect-1 multirect pipes (DCP_REC_ID(idx) != 0) use
HFI_PROPERTY_DEVICE_INIT_VIG_INDICES and HFI_PROPERTY_DEVICE_INIT_DMA_INDICES.
dcp_hfi_get_capabilities() calls dcp_hfi_parse_kv_buf() sequentially on
cache->device_buf, cache->display_buf, cache->vig_buf, cache->dma_buf,
cache->vig_r1_buf, and cache->dma_r1_buf.
When cache->vig_r1_buf and cache->dma_r1_buf are parsed after the primary
pipe buffers, their HFI_PROPERTY_DEVICE_INIT_VIG_INDICES and
HFI_PROPERTY_DEVICE_INIT_DMA_INDICES properties contain only rect-1 indices,
which unconditionally resets caps->num_vig_pipes and caps->num_dma_pipes back
to 0. As a result, caps->max_planes evaluates to 0 and
dcp_hfi_get_capabilities() fails with -EINVAL ("invalid DCP caps: no scanout
pipes"). Conversely, if device_buf contains both rect-0 and rect-1 indices
and vig_buf/dma_buf contain only rect-0 indices, parsing vig_buf/dma_buf
resets caps->virt_vig_count and caps->virt_dma_count to 0.
[ ... ]
> +int dcp_hfi_get_capabilities(struct dcp_hfi *hfi, struct dcp_caps *caps)
> +{
> + struct dcp_hfi_caps_cache *cache;
> + int ret = 0, opt_ret;
> +
> + memset(caps, 0, sizeof(*caps));
> +
> + mutex_lock(&hfi->caps_cache_lock);
> + cache = &hfi->caps_cache;
> +
> + if (!wait_for_completion_timeout(&cache->device_done,
> + msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
> + dev_err(hfi->dev,
> + "DEVICE_INIT_DEVICE_CAPS timeout after %ums\n",
> + DCP_HFI_TIMEOUT_CAPS_MS);
> + ret = -ETIMEDOUT;
> + goto out_free_cache;
> + }
> +
> + if (!cache->device_len) {
> + dev_err(hfi->dev, "DEVICE_INIT_DEVICE_CAPS: empty payload\n");
> + ret = -ENODATA;
> + goto out_free_cache;
> + }
> +
> + ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->device_buf,
> + cache->device_len);
> + if (ret)
> + goto out_free_cache;
> +
> + if (!wait_for_completion_timeout(&cache->display_done,
> + msecs_to_jiffies(DCP_HFI_TIMEOUT_CAPS_MS))) {
> + dev_warn(hfi->dev,
> + "DEVICE_INIT_DISPLAY_CAPS not received\n");
> + } else if (cache->display_len > 0) {
> + opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->display_buf,
> + cache->display_len);
> + if (opt_ret) {
> + dev_warn(hfi->dev,
> + "DISPLAY_CAPS parse failed: %d (non-fatal)\n",
> + opt_ret);
> + }
> + }
> + if (cache->vig_len > 0) {
> + opt_ret = dcp_hfi_parse_kv_buf(hfi, caps, cache->vig_buf,
> + cache->vig_len);
> + if (opt_ret)
> + dev_warn(hfi->dev,
> + "VIG_CAPS parse failed: %d (non-fatal)\n",
> + opt_ret);
> + else
> + dcp_hfi_log_pipe_ids(hfi->dev, "VIG_CAPS",
> + caps->vig_pipe_ids,
> + caps->num_vig_pipes);
> + }
[ ... ]
> +out_free_cache:
> + dcp_hfi_caps_cache_stop(hfi);
> + mutex_unlock(&hfi->caps_cache_lock);
> + return ret;
> +}
[Severity: High]
Can zeroing hfi->caps_cache in dcp_hfi_caps_cache_stop() corrupt
device_done and display_done while capability events are still in flight, and
does caps_cache access here race with dcp_hfi_dispatch_event()?
struct dcp_hfi_caps_cache embeds the device_done and display_done struct
completion objects. Calling memset(&hfi->caps_cache, 0,
sizeof(hfi->caps_cache)) in dcp_hfi_caps_cache_stop() (and in
dcp_hfi_caps_cache_start() before dcp_hfi_caps_cache_init(), as well as prior
to the first dcp_hfi_send_device_init() call after dcp_hfi_init() posts
response buffers) zeroes wait.lock and sets wait.task_list.next and
wait.task_list.prev to NULL.
In dcp_hfi_get_capabilities(), a timeout on display_done is treated as a
non-fatal warning and execution proceeds to out_free_cache, where
dcp_hfi_caps_cache_stop() zeroes hfi->caps_cache. The same happens if
device_done times out, if dcp_hfi_parse_kv_buf() fails on device_buf, or if
dcp_hfi_send_sync() times out in dcp_hfi_send_device_init().
If HFI_COMMAND_DEVICE_INIT_DEVICE_CAPS or
HFI_COMMAND_DEVICE_INIT_DISPLAY_CAPS then arrives in
dcp_hfi_resp_callback()->dcp_hfi_dispatch_event()->dcp_hfi_store_caps_payload(),
complete() calls swake_up_locked() on the zeroed completion. Because
list_empty(&q->task_list) checks READ_ONCE(head->next) == head, a NULL next
pointer evaluates as non-empty and causes a NULL pointer dereference in
interrupt context (along with operating on a zeroed raw_spinlock_t).
In addition, dcp_hfi_dispatch_event() in dcp_hfi_core.c protects writes to
hfi->caps_cache with spin_lock_irqsave(&hfi->caps_lock, flags), whereas
dcp_hfi_caps_cache_start(), dcp_hfi_caps_cache_stop(), and
dcp_hfi_get_capabilities() only hold the mutex hfi->caps_cache_lock and never
acquire hfi->caps_lock. Because VIG_CAPS, DMA_CAPS, VIG_R1_CAPS, and
DMA_R1_CAPS have no completion wait in dcp_hfi_get_capabilities(), reading
cache->vig_buf, cache->dma_buf, cache->vig_r1_buf, and cache->dma_r1_buf (and
zeroing hfi->caps_cache at out_free_cache) can also race concurrently with
dcp_hfi_store_caps_payload() on another CPU.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=9
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support
2026-10-26 6:21 ` [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support Jayant Shekhar
@ 2026-10-26 11:08 ` Dmitry Baryshkov
2026-10-26 12:37 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-26 11:08 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:25AM +0530, Jayant Shekhar wrote:
> Expose the DCP DSI host and panel as DRM-facing objects. The DCP
> display path needs a host, panel configuration, bridge state, DSC
> setup, DCS transfer buffers, and panel lifetime management before it
> can drive a physical display.
>
> Implement the MIPI DSI host, panel-device support, bridge state,
> DSC configuration, DCS transfers, and DSI buffer management.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h | 76 ++
> drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 13 +
> 4 files changed, 1200 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 4024f6b1e3cf..9f0a9bfeb2a7 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_crtc.o \
> dcp/dcp_dpu_kms.o \
> dcp/dcp_plane.o \
> + dcp/dcp_dsi_bridge.o \
> dcp/dcp_hfi_core.o \
> dcp/dcp_hfi_crtc.o \
> dcp/dcp_hfi_dsi.o \
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
> new file mode 100644
> index 000000000000..b3e782b8f047
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
> @@ -0,0 +1,1110 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP DSI bridge driver.
> + *
> + * Implements a DRM bridge for DSI displays in the DCP firmware-delegated
> + * architecture. DCP firmware owns the DSI PHY and controller; this driver
> + * uses HFI commands to configure DSI parameters rather than direct register
> + * writes.
> + */
> +
> +#include <linux/component.h>
> +#include <linux/cleanup.h>
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/iommu.h>
> +#include <linux/jiffies.h>
> +#include <linux/math64.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/slab.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/display/drm_dsc.h>
> +#include <drm/display/drm_dsc_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_connector.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_of.h>
> +
> +#include <video/mipi_display.h>
> +
> +#include "dcp_crtc.h"
> +#include "dcp_dsi_bridge.h"
> +#include "dcp_dpu_kms.h"
> +#include "dcp_hfi.h"
> +#include "hfi/hfi_defs_panel.h"
> +
> +struct dcp_dsi_bridge_state {
> + struct drm_bridge_state base;
> + struct dcp_dsi_panel_state panel_state;
> + int prepare_error;
> +};
> +
> +#define to_dcp_dsi_bridge_state(s) \
> + container_of(s, struct dcp_dsi_bridge_state, base)
> +
> +static struct dcp_dsi_bridge_state *
> +dcp_dsi_bridge_get_state(struct dcp_dsi_bridge *bridge)
> +{
> + return to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->bridge.base.state));
> +}
> +
> +static struct drm_bridge_state *
> +dcp_dsi_bridge_atomic_create_state(struct drm_bridge *bridge)
> +{
> + struct dcp_dsi_bridge_state *state;
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> +
> + state = kzalloc_obj(*state);
> + if (!state)
> + return ERR_PTR(-ENOMEM);
> +
> + __drm_atomic_helper_bridge_state_init(&state->base, bridge);
> + state->panel_state.topology_mixer_count =
> + dsi_bridge->hfi_panel_config.topology_mixer_count;
> + state->panel_state.topology_enc_count =
> + dsi_bridge->hfi_panel_config.topology_enc_count;
> + state->panel_state.topology_intf_count =
> + dsi_bridge->hfi_panel_config.topology_intf_count;
> + return &state->base;
> +}
> +
> +static struct drm_bridge_state *
> +dcp_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
> +{
> + struct dcp_dsi_bridge_state *state, *old_state;
> +
> + if (WARN_ON(!bridge->base.state))
> + return NULL;
> +
> + state = kzalloc_obj(*state);
> + if (!state)
> + return NULL;
> +
> + old_state = to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->base.state));
> + __drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
> + state->panel_state = old_state->panel_state;
> + return &state->base;
> +}
> +
> +static void dcp_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
> + struct drm_bridge_state *bridge_state)
> +{
> + kfree(to_dcp_dsi_bridge_state(bridge_state));
> +}
> +
> +static void
> +dcp_dsi_bridge_build_hfi_config(struct dcp_dsi_bridge *dsi_bridge,
> + const struct dcp_dsi_bridge_state *state,
> + struct dcp_dsi_hfi_panel_config *cfg)
> +{
> + *cfg = dsi_bridge->hfi_panel_config;
> + cfg->topology_mixer_count = state->panel_state.topology_mixer_count;
> + cfg->topology_enc_count = state->panel_state.topology_enc_count;
> + cfg->topology_intf_count = state->panel_state.topology_intf_count;
> +}
> +
> +static void dcp_dsi_host_dev_release(struct device *dev)
> +{
> + of_node_put(dev->of_node);
> +}
> +
> +static bool dcp_dsi_bridge_cmd_mode(const struct dcp_dsi_bridge *dsi_bridge)
> +{
> + return dsi_bridge->dsi_device &&
> + !(dsi_bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
> +}
> +
> +static u64 dcp_dsi_link_clock(const struct dcp_dsi_bridge *dsi_bridge,
> + const struct dcp_dsi_hfi_panel_config *cfg,
> + const struct drm_display_mode *mode)
> +{
> + const struct mipi_dsi_device *dsi = dsi_bridge->dsi_device;
> + int bpp;
> + u32 bpp_x16;
> +
> + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> + if (bpp < 0)
> + return 0;
> +
> + bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
> + cfg->dsc_bits_per_pixel : bpp << 4;
> +
> + if (dsi->hs_rate)
> + return dsi->hs_rate;
> +
> + return div_u64((u64)mode->clock * 1000 * bpp_x16, dsi->lanes * 16);
> +}
> +
> +static void dcp_dsi_power_control_work(struct work_struct *work)
> +{
> + struct dcp_dsi_bridge *dsi_bridge =
> + container_of(work, struct dcp_dsi_bridge, power_control_work);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> +
> + dcp_hfi_display_power_control_ack(kms->hfi,
> + dsi_bridge->session_id,
> + dsi_bridge->power_packet_id);
> +
> + complete(&dsi_bridge->power_done);
> +}
> +
> +static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
> + u32 power_mask, bool enable,
> + u32 packet_id)
> +{
> + struct dcp_dpu_kms *kms = priv;
> + struct dcp_dsi_bridge *dsi_bridge = NULL;
> + int idx;
> +
> + for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
> + struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
> +
> + if (b && b->session_id == display_id) {
> + dsi_bridge = b;
> + break;
> + }
> + }
> +
> + if (!dsi_bridge) {
> + dev_warn(kms->pm_dev,
> + "DSI: power_control for unknown display_id=%u\n", display_id);
> + return;
> + }
> +
> + dsi_bridge->power_packet_id = packet_id;
> + schedule_work(&dsi_bridge->power_control_work);
> +}
> +
> +static const struct component_ops dcp_dsi_component_ops;
> +
> +static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
> + struct mipi_dsi_device *dsi)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
> + struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
> + int ret;
> +
> + if (!dsi->lanes || dsi->lanes > 4)
> + return -EINVAL;
> +
> + cfg->dsc_enabled = false;
> + cfg->dsc_version_major = 0;
> + cfg->dsc_version_minor = 0;
> + cfg->dsc_scr_version = 0;
> + cfg->dsc_slice_height = 0;
> + cfg->dsc_slice_width = 0;
> + cfg->dsc_slices_per_pkt = 0;
> + cfg->dsc_bits_per_component = 0;
> + cfg->dsc_pps_delay_ms = 0;
> + cfg->dsc_bits_per_pixel = 0;
> + cfg->dsc_chroma_format = 0;
> + cfg->dsc_color_space = 0;
> + cfg->dsc_block_prediction_enable = 0;
> + cfg->dsc_rc_override = false;
> + memset(cfg->dsc_rc_min_qp, 0, sizeof(cfg->dsc_rc_min_qp));
> + memset(cfg->dsc_rc_max_qp, 0, sizeof(cfg->dsc_rc_max_qp));
> + memset(cfg->dsc_rc_bpg_offsets, 0, sizeof(cfg->dsc_rc_bpg_offsets));
> +
> + switch (dsi->format) {
> + case MIPI_DSI_FMT_RGB888:
> + case MIPI_DSI_FMT_RGB666:
> + case MIPI_DSI_FMT_RGB666_PACKED:
> + case MIPI_DSI_FMT_RGB565:
> + case MIPI_DSI_FMT_RGB101010:
> + break;
> + default:
> + dev_err(&dsi->dev, "unsupported pixel format %u\n", dsi->format);
> + return -EINVAL;
> + }
> +
> + dsi_bridge->dsi_device = dsi;
> +
> + if (dsi->dsc) {
> + struct drm_dsc_config *dsc = dsi->dsc;
> + u32 chroma_format = 0;
> + u32 color_space = 0;
> +
> + if (dsc->native_422)
> + chroma_format = 1;
> + else if (dsc->native_420)
> + chroma_format = 2;
> + if (dsc->native_422 || dsc->native_420)
> + color_space = 1;
> +
> + cfg->dsc_enabled = true;
> + cfg->dsc_version_major = dsc->dsc_version_major;
> + cfg->dsc_version_minor = dsc->dsc_version_minor;
> + cfg->dsc_scr_version = 0;
> + cfg->dsc_slice_height = dsc->slice_height;
> + cfg->dsc_slice_width = dsc->slice_width;
> + cfg->dsc_slices_per_pkt = 1;
> + cfg->dsc_bits_per_component = dsc->bits_per_component;
> + cfg->dsc_pps_delay_ms = 0;
> + cfg->dsc_bits_per_pixel = dsc->bits_per_pixel;
> + cfg->dsc_chroma_format = chroma_format;
> + cfg->dsc_color_space = color_space;
> + cfg->dsc_block_prediction_enable = dsc->block_pred_enable;
> +
> + dev_dbg(&dsi->dev,
> + "DSI%u: DSC enabled: version=%u.%u slice=%ux%u bpc=%u bpp=%u.%u\n",
> + dsi_bridge->intf_idx, dsc->dsc_version_major,
> + dsc->dsc_version_minor, dsc->slice_width,
> + dsc->slice_height, dsc->bits_per_component,
> + dsc->bits_per_pixel >> 4, (dsc->bits_per_pixel & 0xf) * 100 / 16);
I'd rather see this happen at the atomic_check state. We are trying to
shift towards that model.
> + }
> +
> + ret = component_add(&dsi_bridge->dsi_host_dev,
> + &dcp_dsi_component_ops);
> + if (ret)
> + return ret;
> + dsi_bridge->component_added = true;
What for? Can the bridge really be attached twice?
> +
> + return 0;
> +}
> +
> +static int dcp_dsi_host_detach(struct mipi_dsi_host *host,
> + struct mipi_dsi_device *dsi)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
> +
> + if (dsi_bridge->component_added) {
> + component_del(&dsi_bridge->dsi_host_dev,
> + &dcp_dsi_component_ops);
> + dsi_bridge->component_added = false;
Can it be detached twice? How? Why?
> + }
> + dsi_bridge->panel_bridge = NULL;
> + dsi_bridge->dsi_device = NULL;
> + dsi_bridge->hfi_panel_config.dsc_enabled = false;
Why do you need to NULL all of that?
> + return 0;
> +}
> +
> +static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
> + const struct mipi_dsi_msg *msg)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + u32 session_id;
> + bool temporary_session = false;
> + ssize_t ret;
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (!dsi_bridge->dcs_tx_buf)
> + return -ENODEV;
> +
> + /* Serialize DCS transfers */
> + guard(mutex)(&dsi_bridge->dcs_lock);
> +
> + if (dsi_bridge->session_open) {
> + session_id = dsi_bridge->session_id;
> + } else {
> + /*
> + * mipi_dsi_host_ops::transfer() may be called while the host is
> + * disabled. Open a transaction-only DCP session in that case;
> + * atomic_pre_enable() will create the full display session later.
> + */
> + ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
> + dsi_bridge->fw_display_id,
> + &session_id);
> + if (ret)
> + return ret;
> +
> + temporary_session = true;
> + }
Nice
> +
> + ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
> + dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
> + dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
> + dsi_bridge->hfi_panel_config.ctrl_indices[0]);
> + if (temporary_session) {
> + int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
> +
> + if (ret >= 0 && close_ret)
> + ret = close_ret;
> + }
> + if (ret < 0)
> + dev_warn(kms->pm_dev,
> + "DSI%u: live DCS command transfer failed: %zd\n",
> + dsi_bridge->intf_idx, ret);
> +
> + return ret;
> +}
> +
> +static int dcp_dsi_component_bind(struct device *dev,
> + struct device *master, void *data)
> +{
> + struct dcp_dsi_bridge *dsi_bridge =
> + container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
> +
> + dsi_bridge->panel_bridge =
> + devm_drm_of_get_bridge(dev, dsi_bridge->dcp_np,
> + dsi_bridge->dcp_port, 0);
> + if (IS_ERR(dsi_bridge->panel_bridge)) {
> + int ret = PTR_ERR(dsi_bridge->panel_bridge);
> +
> + dsi_bridge->panel_bridge = NULL;
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static void dcp_dsi_component_unbind(struct device *dev,
> + struct device *master, void *data)
> +{
> + struct dcp_dsi_bridge *dsi_bridge =
> + container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
> +
> + dsi_bridge->panel_bridge = NULL;
> +}
> +
> +static const struct component_ops dcp_dsi_component_ops = {
> + .bind = dcp_dsi_component_bind,
> + .unbind = dcp_dsi_component_unbind,
> +};
> +
> +static const struct mipi_dsi_host_ops dcp_dsi_host_ops = {
> + .attach = dcp_dsi_host_attach,
> + .detach = dcp_dsi_host_detach,
> + .transfer = dcp_dsi_host_transfer,
> +};
> +
> +static int dcp_dsi_bridge_attach(struct drm_bridge *bridge,
> + struct drm_encoder *encoder,
> + enum drm_bridge_attach_flags flags)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct drm_bridge *panel_bridge;
> + int ret;
> +
> + panel_bridge = dsi_bridge->panel_bridge;
> + if (!panel_bridge)
> + return -EPROBE_DEFER;
> +
> + ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
> + if (ret) {
> + drm_err(bridge->dev,
> + "DSI%u: panel bridge attach failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + return ret;
> + }
> +
> + drm_dbg_driver(dsi_bridge->kms->dev,
> + "DSI%u: panel bridge attached\n",
> + dsi_bridge->intf_idx);
> +
> + dcp_hfi_set_power_control_cb(dsi_bridge->kms->hfi,
> + dsi_bridge->kms, dcp_dsi_power_control_cb);
> +
> + return 0;
> +}
> +
> +static u32 dcp_dsi_bridge_get_mixer_count(struct dcp_dsi_bridge *dsi_bridge,
> + const struct dcp_dsi_hfi_panel_config *cfg,
> + const struct drm_display_mode *mode)
> +{
> + u32 mixer_count = cfg->topology_mixer_count;
> +
> + if (!mixer_count)
> + mixer_count = 1;
> +
> + if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
> + const struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
> +
> + if (dsc->slice_count)
> + mixer_count = dsc->slice_count;
> + else if (dsc->slice_width && mode->hdisplay)
> + mixer_count = DIV_ROUND_UP(mode->hdisplay,
> + dsc->slice_width);
> + }
> +
> + return mixer_count;
> +}
> +
> +static void dcp_dsi_bridge_publish_session(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + struct drm_crtc *crtc;
> +
> + crtc = dcp_dpu_kms_get_crtc(kms, dsi_bridge->fw_display_id);
> + if (crtc)
> + dcp_crtc_set_session(crtc, dsi_bridge->session_id);
> + else
> + drm_err(kms->dev, "DSI%u: no CRTC for session %u\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> +}
> +
> +static void dcp_dsi_bridge_clear_session(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + dcp_crtc_clear_session(dcp_dpu_kms_get_crtc(dsi_bridge->kms,
> + dsi_bridge->fw_display_id));
> +}
> +
> +static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
> + struct dcp_dsi_hfi_panel_config *cfg,
> + const struct drm_display_mode *mode)
> +{
> + struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
> + enum drm_dsc_params_type params_type;
> + unsigned int i;
> + int ret;
> +
> + dsc->pic_width = mode->hdisplay;
> + dsc->pic_height = mode->vdisplay;
> + dsc->convert_rgb = !dsc->native_422 && !dsc->native_420;
> + dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width *
> + dsc->bits_per_pixel, 8 * 16);
> + dsc->simple_422 = 0;
> + dsc->vbr_enable = 0;
> +
> + if (dsc->dsc_version_major != 1)
> + return -EOPNOTSUPP;
> +
> + switch (dsc->dsc_version_minor) {
> + case 1:
> + if (dsc->native_422 || dsc->native_420)
> + return -EOPNOTSUPP;
> + params_type = DRM_DSC_1_1_PRE_SCR;
> + break;
> + case 2:
> + if (dsc->native_422)
> + params_type = DRM_DSC_1_2_422;
> + else if (dsc->native_420)
> + params_type = DRM_DSC_1_2_420;
> + else
> + params_type = DRM_DSC_1_2_444;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> +
> + drm_dsc_set_const_params(dsc);
> + drm_dsc_set_rc_buf_thresh(dsc);
> + ret = drm_dsc_setup_rc_params(dsc, params_type);
> + if (ret)
> + return ret;
> +
> + dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
> + dsc->line_buf_depth = dsc->bits_per_component + 1;
> + ret = drm_dsc_compute_rc_parameters(dsc);
> + if (ret)
> + return ret;
> +
> + for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
> + cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
> + cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
> + cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
> + }
> +
> + return 0;
> +}
> +
> +int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
> + const struct drm_display_mode *mode)
> +{
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
> + struct dcp_dsi_hfi_panel_config cfg;
> + int ret;
> + u32 opened_session_id;
> +
> + if (!mode)
> + return -ENODEV;
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (dsi_bridge->session_open)
> + return 0;
> +
> + dsi_bridge->session_id = dsi_bridge->fw_display_id;
> +
> + if (!dsi_bridge->dsi_device)
> + return -ENODEV;
> +
> + dcp_dsi_bridge_build_hfi_config(dsi_bridge, state, &cfg);
> +
> + if (dsi_bridge->dsi_device->dsc) {
When is this going to be called? There are panels which require
different DSC setup for different modes.
> + ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, &cfg, mode);
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: DSC setup failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + }
> +
> + ret = dcp_hfi_panel_init(kms->hfi, dsi_bridge->intf_idx,
> + mode,
> + dsi_bridge->dsi_device,
> + &cfg);
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI PANEL_INIT failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> +
> + if (mode->clock) {
> + u64 link_clock;
> +
> + link_clock = dcp_dsi_link_clock(dsi_bridge, &cfg,
> + mode);
> + ret = dcp_hfi_display_mode_validate(kms->hfi,
> + dsi_bridge->session_id,
> + mode, link_clock);
mode validation should have happened at the atomic_check().
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI MODE_VALIDATE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> +
> + ret = dcp_hfi_display_set_mode(kms->hfi,
> + dsi_bridge->session_id,
> + mode,
> + dcp_dsi_link_clock(dsi_bridge, &cfg,
> + mode));
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI SET_MODE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + } else {
> + drm_warn(kms->dev, "DSI%u: no valid mode, skipping SET_MODE\n",
> + dsi_bridge->intf_idx);
> + }
> +
> + dcp_hfi_set_power_control_cb(kms->hfi, kms,
> + dcp_dsi_power_control_cb);
And what happens when you add DP?
> +
> + reinit_completion(&dsi_bridge->power_done);
> +
> + ret = dcp_hfi_display_power_register(kms->hfi, dsi_bridge->session_id);
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI POWER_REGISTER failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> +
> + ret = dcp_hfi_display_enable(kms->hfi,
> + dsi_bridge->intf_idx,
> + dsi_bridge->fw_display_id,
> + &opened_session_id);
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + dsi_bridge->session_id = opened_session_id;
> + dsi_bridge->session_open = true;
> + dcp_dsi_bridge_publish_session(dsi_bridge);
> +
> + drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> + return 0;
> +
> +err_close:
> + dcp_hfi_clear_power_control_cb(kms->hfi, kms);
> + dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
> + dsi_bridge->session_id = 0;
> + dsi_bridge->session_open = false;
> + dcp_dsi_bridge_clear_session(dsi_bridge);
> +
> + return ret;
> +}
> +
> +static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + int ret;
> +
> + ret = dsi_bridge->session_open ? 0 : -ENODEV;
> + dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
> + if (ret)
> + drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> +}
> +
> +static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + bool scan_start_registered = false;
> + bool scan_complete_registered = false;
> + bool autorefresh_enabled = false;
> + int ret;
> +
> + drm_dbg_driver(kms->dev,
> + "DSI%u: bridge atomic_enable (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> +
> + if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
> + return;
> + if (!dsi_bridge->session_open)
> + return;
> +
> + ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
> + if (ret) {
> + drm_err(bridge->dev,
> + "DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> +
> + dcp_dsi_bridge_publish_session(dsi_bridge);
> +
> + ret = dcp_hfi_display_event_register(kms->hfi,
> + dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_START);
> + if (ret)
> + goto err_close;
> + scan_start_registered = true;
> +
> + ret = dcp_hfi_display_event_register(kms->hfi,
> + dsi_bridge->session_id, HFI_EVENT_FRAME_SCAN_COMPLETE);
> + if (ret)
> + goto err_close;
> + scan_complete_registered = true;
> +
> + if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
> + ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
> + dsi_bridge->session_id, true, 1);
> + if (ret) {
> + drm_err(bridge->dev,
> + "DSI%u: initial autorefresh setup failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + autorefresh_enabled = true;
> + }
> +
> + drm_dbg_driver(bridge->dev,
> + "DSI%u: enable complete (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> + return;
> +
> +err_close:
> + if (autorefresh_enabled)
> + dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
> + false, 0);
> + if (scan_complete_registered)
Turn each of the booleans into a separate label.
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_COMPLETE);
> + if (scan_start_registered)
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_START);
> + drm_err(bridge->dev,
> + "DSI%u: bridge enable failed; session closed\n",
> + dsi_bridge->intf_idx);
> + dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
> + {
> + guard(mutex)(&dsi_bridge->session_lock);
scoped_guard()
> + dsi_bridge->session_id = 0;
> + dsi_bridge->session_open = false;
> + }
> + dcp_dsi_bridge_clear_session(dsi_bridge);
> +}
> +
> +static void dcp_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + int ret;
> +
> + drm_dbg_driver(kms->dev,
> + "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_open,
> + dsi_bridge->session_id);
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (!dsi_bridge->session_open)
> + return;
> +
> + if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
> + ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
> + dsi_bridge->session_id, false, 0);
> + if (ret)
> + drm_warn(bridge->dev,
> + "DSI%u: autorefresh disable failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + }
> +
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_COMPLETE);
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_START);
> +
> + ret = dcp_hfi_display_disable(kms->hfi, dsi_bridge->session_id);
> + if (ret)
> + drm_warn(bridge->dev,
> + "DSI%u: HFI DISPLAY_DISABLE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> +}
> +
> +static void dcp_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + int ret;
> +
> + drm_dbg_driver(kms->dev,
> + "DSI%u: atomic_post_disable\n",
> + dsi_bridge->intf_idx);
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (!dsi_bridge->session_open)
> + return;
> +
> + ret = dcp_hfi_display_post_disable(kms->hfi, dsi_bridge->session_id);
> + if (ret)
> + drm_warn(bridge->dev,
> + "DSI%u: HFI DISPLAY_POST_DISABLE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> +
> + ret = dcp_hfi_send_sync(kms->hfi,
> + HFI_COMMAND_DISPLAY_CLOSE,
> + HFI_CMD_BUFF_DISPLAY, dsi_bridge->session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + 2000 /* ms */);
> + if (ret)
> + drm_warn(bridge->dev,
> + "DSI%u: HFI DISPLAY_CLOSE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> +
> + dsi_bridge->session_id = 0;
> + dsi_bridge->session_open = false;
> +
> + dcp_dsi_bridge_clear_session(dsi_bridge);
> +}
> +
> +static int dcp_dsi_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 dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct dcp_dsi_bridge_state *state =
> + to_dcp_dsi_bridge_state(bridge_state);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
> + u32 max_width, max_height;
> + u32 mixer_count, bpc;
> +
> + state->prepare_error = 0;
> +
> + if (!mode->clock || !drm_mode_vrefresh(mode))
> + return -EINVAL;
> +
> + if (dsi_bridge->dsi_device &&
> + (!dsi_bridge->dsi_device->lanes ||
> + !dcp_dsi_link_clock(dsi_bridge, &dsi_bridge->hfi_panel_config, mode)))
> + return -EINVAL;
> +
> + if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
> + state->panel_state.topology_mixer_count =
> + dcp_dsi_bridge_get_mixer_count(dsi_bridge,
> + &dsi_bridge->hfi_panel_config, mode);
> + state->panel_state.topology_enc_count =
> + state->panel_state.topology_mixer_count;
> + state->panel_state.topology_intf_count = 1;
> + }
> +
> + mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
> + &dsi_bridge->hfi_panel_config, mode);
> + bpc = dsi_bridge->hfi_panel_config.dsc_bits_per_component ?
> + dsi_bridge->hfi_panel_config.dsc_bits_per_component : 8;
> + dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
> +
> + dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
> + &max_width, &max_height);
> +
> + if (mode->hdisplay > max_width || mode->vdisplay > max_height) {
> + drm_err(dsi_bridge->kms->dev,
> + "DSI%u: mode %dx%d exceeds DCP caps %dx%d\n",
> + dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
> + kms->caps->max_width, kms->caps->max_height);
Don't let userspace spam the dmesg.
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static const struct drm_bridge_funcs dcp_dsi_bridge_funcs = {
> + .attach = dcp_dsi_bridge_attach,
> + .atomic_pre_enable = dcp_dsi_bridge_atomic_pre_enable,
> + .atomic_enable = dcp_dsi_bridge_atomic_enable,
> + .atomic_disable = dcp_dsi_bridge_atomic_disable,
> + .atomic_post_disable = dcp_dsi_bridge_atomic_post_disable,
> + .atomic_check = dcp_dsi_bridge_atomic_check,
> + .atomic_create_state = dcp_dsi_bridge_atomic_create_state,
> + .atomic_duplicate_state = dcp_dsi_bridge_atomic_duplicate_state,
> + .atomic_destroy_state = dcp_dsi_bridge_atomic_destroy_state,
> +};
> +
> +#define DCP_DSI_DUAL_MAP_ALIGN SZ_1K
> +
> +static int dcp_dsi_dual_map_buf(struct dcp_dpu_kms *kms,
> + struct device *dma_dev, void *cpu_addr,
> + dma_addr_t iova, size_t size, int prot)
> +{
> + struct sg_table sgt = { 0 };
> + struct iommu_domain *domain;
> + ssize_t mapped;
> + int ret;
> +
> + if (!kms->iommu_dev || !cpu_addr || !iova ||
> + !IS_ALIGNED(iova, DCP_DSI_DUAL_MAP_ALIGN))
> + return -EINVAL;
> +
> + domain = iommu_get_domain_for_dev(kms->iommu_dev);
> + if (!domain)
> + return -ENODEV;
> +
> + ret = dma_get_sgtable(dma_dev, &sgt, cpu_addr, iova, size);
> + if (ret)
> + return ret;
> +
> + mapped = iommu_map_sgtable(domain, iova, &sgt, prot);
> + if (mapped != PAGE_ALIGN(size)) {
> + if (mapped > 0)
> + iommu_unmap(domain, iova, mapped);
> + ret = mapped < 0 ? mapped : -EIO;
> + drm_err(kms->dev,
> + "failed to dual-map iova=%pad into iommu_dev domain: %zd\n",
> + &iova, mapped);
> + sg_free_table(&sgt);
> + return ret;
> + }
> +
> + sg_free_table(&sgt);
> + return 0;
> +}
> +
> +static void dcp_dsi_dual_unmap_buf(struct dcp_dpu_kms *kms, dma_addr_t iova,
> + size_t size)
> +{
> + struct iommu_domain *domain;
> +
> + if (!kms->iommu_dev || !iova)
> + return;
> +
> + domain = iommu_get_domain_for_dev(kms->iommu_dev);
> + if (!domain)
> + return;
> +
> + iommu_unmap(domain, iova, PAGE_ALIGN(size));
> +}
> +
> +static void dcp_dsi_init_hfi_panel_defaults(struct dcp_dsi_hfi_panel_config *cfg,
> + u32 intf_idx)
> +{
> + memset(cfg, 0, sizeof(*cfg));
> +
> + cfg->display_type = HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0;
> + cfg->num_ctrls = 1;
> + cfg->ctrl_indices[0] = intf_idx;
> + cfg->num_phys = 1;
> + cfg->phy_indices[0] = intf_idx;
> +
> + cfg->dma_trigger = HFI_PANEL_TRIGGER_SW;
> + cfg->stream_trigger = HFI_PANEL_TRIGGER_NONE;
> + cfg->te_dcs_command = 1;
> + cfg->wr_mem_start = 0x2C;
> + cfg->wr_mem_continue = 0x3C;
> +
> + cfg->phy_type = HFI_PANEL_PHY_OLED;
> + cfg->bllp_eof_power_mode = true;
> + cfg->bllp_power_mode = true;
> + cfg->lane_map = 1;
> + cfg->color_order = 1;
> +
> + cfg->jitter_numer = 4;
> + cfg->jitter_denom = 1;
> + cfg->dcs_tx_buf_size = DCP_DSI_DCS_TX_BUF_SIZE;
> +
> + cfg->topology_mixer_count = 1;
> + cfg->topology_intf_count = 1;
> +}
> +
> +struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
> + struct device_node *np,
> + struct device_node *dcp_np,
> + u32 dcp_port, u32 intf_idx,
> + u32 fw_display_id)
> +{
> + struct dcp_dsi_bridge *dsi_bridge;
> + struct dcp_dsi_hfi_panel_config *cfg;
> + struct device *dev = kms->pm_dev;
> + struct device *dma_dev = kms->hfi->dma_dev;
> + int ret;
> +
> + dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
> + bridge, &dcp_dsi_bridge_funcs);
> + if (IS_ERR(dsi_bridge))
> + return ERR_CAST(dsi_bridge);
> +
> + dsi_bridge->dcp_np = of_node_get(dcp_np);
> + dsi_bridge->dcp_port = dcp_port;
> + dsi_bridge->kms = kms;
> + dsi_bridge->intf_idx = intf_idx;
> + dsi_bridge->fw_display_id = fw_display_id;
> +
> + INIT_WORK(&dsi_bridge->power_control_work, dcp_dsi_power_control_work);
> + init_completion(&dsi_bridge->power_done);
> + mutex_init(&dsi_bridge->session_lock);
> + mutex_init(&dsi_bridge->dcs_lock);
> +
> + cfg = &dsi_bridge->hfi_panel_config;
> + dcp_dsi_init_hfi_panel_defaults(cfg, intf_idx);
> +
> + /*
> + * Allocate the buffers for the firmware DMA domain here. The second
> + * mapping into the display domain is deferred until msm_kms_init_vm()
> + * has installed the final display IOMMU domain.
> + */
> + dsi_bridge->dcs_tx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + &dsi_bridge->dcs_tx_iova, GFP_KERNEL);
> + if (!dsi_bridge->dcs_tx_buf) {
> + ret = -ENOMEM;
> + goto err_unwind_dma;
> + }
> +
> + dsi_bridge->hfi_panel_config.dcs_tx_dva = dsi_bridge->dcs_tx_iova;
> + dsi_bridge->hfi_panel_config.dcs_tx_iova = dsi_bridge->dcs_tx_iova;
> +
> + dsi_bridge->dcs_rx_buf = dma_alloc_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + &dsi_bridge->dcs_rx_iova, GFP_KERNEL);
> + if (!dsi_bridge->dcs_rx_buf) {
> + ret = -ENOMEM;
> + goto err_unwind_dma;
> + }
> +
> + device_initialize(&dsi_bridge->dsi_host_dev);
> + dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
> + dsi_bridge->dsi_host_dev.parent = dev;
> + dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
> + dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
> + dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
> + dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
> +
> + ret = device_add(&dsi_bridge->dsi_host_dev);
> + if (ret) {
> + dev_err(dev,
> + "DSI%u: failed to add dsi_host_dev: %d\n", intf_idx, ret);
> + put_device(&dsi_bridge->dsi_host_dev);
> + goto err_unwind_dma;
> + }
> +
> + ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
> + if (ret) {
> + dev_err(dev,
> + "DSI%u: failed to register MIPI DSI host: %d\n",
> + intf_idx, ret);
> + device_del(&dsi_bridge->dsi_host_dev);
> + put_device(&dsi_bridge->dsi_host_dev);
> + goto err_unwind_dma;
> + }
> +
> + dsi_bridge->bridge.of_node = of_node_get(np);
> + dsi_bridge->bridge.type = DRM_MODE_CONNECTOR_DSI;
> +
> + drm_bridge_add(&dsi_bridge->bridge);
> +
> + return &dsi_bridge->bridge;
> +
> +err_unwind_dma:
> + if (dsi_bridge->dcs_rx_buf) {
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + dsi_bridge->dcs_rx_buf,
> + dsi_bridge->dcs_rx_iova);
> + }
> + if (dsi_bridge->dcs_tx_buf) {
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf,
> + dsi_bridge->dcs_tx_iova);
> + }
> + cancel_work_sync(&dsi_bridge->power_control_work);
> + mutex_destroy(&dsi_bridge->session_lock);
> + mutex_destroy(&dsi_bridge->dcs_lock);
> + return ERR_PTR(ret);
> +}
> +
> +int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + struct device *dma_dev = kms->hfi->dma_dev;
> + int ret;
> +
> + if (dsi_bridge->dcs_mapped)
> + return 0;
> +
> + ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_tx_buf,
> + dsi_bridge->dcs_tx_iova,
> + DCP_DSI_DCS_TX_BUF_SIZE, IOMMU_READ);
> + if (ret)
> + return ret;
> +
> + ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_rx_buf,
> + dsi_bridge->dcs_rx_iova,
> + DCP_DSI_DCS_RX_BUF_SIZE, IOMMU_WRITE);
> + if (ret) {
> + dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
> + DCP_DSI_DCS_TX_BUF_SIZE);
> + return ret;
> + }
> +
> + dsi_bridge->dcs_mapped = true;
> + drm_dbg_driver(kms->dev,
> + "DSI%u: mapped DCS buffers tx=%pad rx=%pad in display domain\n",
> + dsi_bridge->intf_idx, &dsi_bridge->dcs_tx_iova,
> + &dsi_bridge->dcs_rx_iova);
> +
> + return 0;
> +}
> +
> +void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> +
> + if (!dsi_bridge->dcs_mapped)
> + return;
> +
> + dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
> + DCP_DSI_DCS_TX_BUF_SIZE);
> + dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
> + DCP_DSI_DCS_RX_BUF_SIZE);
> + dsi_bridge->dcs_mapped = false;
> +}
> +
> +void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
> +
> + dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
> + cancel_work_sync(&dsi_bridge->power_control_work);
> +
> + drm_bridge_remove(&dsi_bridge->bridge);
> + of_node_put(dsi_bridge->bridge.of_node);
> + of_node_put(dsi_bridge->dcp_np);
> +
> + mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
> + device_del(&dsi_bridge->dsi_host_dev);
> + put_device(&dsi_bridge->dsi_host_dev);
> +
> + dcp_dsi_bridge_unmap_buffers(dsi_bridge);
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
> +
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
> +
> + mutex_destroy(&dsi_bridge->session_lock);
> + mutex_destroy(&dsi_bridge->dcs_lock);
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
> new file mode 100644
> index 000000000000..f2c7abf23ac9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
> @@ -0,0 +1,76 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP DSI bridge state and panel configuration.
> + * DCP firmware owns the DSI PHY and controller; the bridge submits HFI commands.
> + */
> +
> +#ifndef __DCP_DSI_BRIDGE_H__
> +#define __DCP_DSI_BRIDGE_H__
> +
> +#include <linux/completion.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/ktime.h>
> +#include <linux/workqueue.h>
> +
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +
> +#include "dcp_dsi_panel.h"
> +
> +struct dcp_dpu_kms;
> +struct device_node;
> +
> +struct dcp_dsi_bridge {
> + struct drm_bridge bridge;
> + struct device dsi_host_dev;
> + struct mipi_dsi_host dsi_host;
> + struct mipi_dsi_device *dsi_device;
> + struct drm_bridge *panel_bridge;
> + bool component_added;
> +
> + struct dcp_dpu_kms *kms;
> + struct device_node *dcp_np;
> + u32 dcp_port;
> + u32 intf_idx;
> + u32 fw_display_id;
> + u32 session_id;
> + bool session_open;
> + /* Protects session_id and session_open. */
> + struct mutex session_lock;
> +
> + struct dcp_dsi_hfi_panel_config hfi_panel_config;
> + /* Serializes DCS transfers. */
> + struct mutex dcs_lock;
> +
> + void *dcs_tx_buf;
> + dma_addr_t dcs_tx_iova;
> + void *dcs_rx_buf;
> + dma_addr_t dcs_rx_iova;
> + bool dcs_mapped;
> + struct work_struct power_control_work;
> + struct completion power_done;
> + u32 power_packet_id;
> +};
> +
> +#define to_dcp_dsi_host(h) \
> + container_of(h, struct dcp_dsi_bridge, dsi_host)
> +
> +#define to_dcp_dsi_bridge(b) \
> + container_of(b, struct dcp_dsi_bridge, bridge)
> +
> +struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
> + struct device_node *np,
> + struct device_node *dcp_np,
> + u32 dcp_port, u32 intf_idx,
> + u32 fw_display_id);
> +int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge);
> +void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge);
> +int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
> + const struct drm_display_mode *mode);
> +void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge);
> +
> +#endif /* __DCP_DSI_BRIDGE_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
> index a8898e469b44..cc4e9f4c6fc4 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
> @@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
> u32 topology_intf_count;
> };
>
> +/**
> + * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
> + *
> + * This state is owned by a DRM atomic bridge state. It is combined with
> + * the static panel configuration when constructing the HFI PANEL_INIT
> + * payload.
> + */
> +struct dcp_dsi_panel_state {
> + u32 topology_mixer_count;
> + u32 topology_enc_count;
> + u32 topology_intf_count;
Why is it public? Who uses it?
> +};
> +
> /* DCS buffers use IOVAs directly in the firmware command descriptor. */
> #define DCP_DSI_DCS_TX_BUF_SIZE SZ_4K
> #define DCP_DSI_DCS_RX_BUF_SIZE SZ_4K
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands
2026-10-26 6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
@ 2026-10-26 11:13 ` sashiko-bot
2026-10-27 11:06 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 11:13 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] drm/msm/dcp: stack use-after-free on timeout in dcp_hfi_panel_wait()
- [High] drm/msm/dcp: dcp_hfi_dcs_transfer() returns tx_len instead of rx_len on read
- [Medium] drm/msm/dcp: unsigned bpp in dcp_hfi_panel_init() makes error check dead
--
commit 7a46a036c27a241a8f787ac3f2ae566d027d7a46
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add HFI DSI and panel commands
This commit translates DSI host and panel operations into HFI commands,
implementing DSI mode validation, display session and power management,
panel initialization, and DCS transfer helpers.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
> new file mode 100644
> index 0000000000000..b27fa8fa31609
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
[ ... ]
> +static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
> + struct dcp_hfi_completion *comp)
> +{
> + unsigned long flags;
> +
> + if (wait_for_completion_timeout(&comp->done,
> + msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
> + return false;
> +
> + /* Serialize removal with response dispatch before the stack object dies. */
> + spin_lock_irqsave(&hfi->resp_drain_lock, flags);
> + dcp_hfi_pending_find(hfi, comp->packet_id);
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
> + return true;
> +}
[Severity: Critical]
Can dcp_hfi_panel_wait() in drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
cause a stack use-after-free when a timeout races with response completion?
In dcp_hfi_panel_init(), comp1, comp2, and comp3 are allocated on the stack
and waited on via dcp_hfi_panel_wait(), which returns -ETIMEDOUT and unwinds
the stack frame as soon as dcp_hfi_panel_wait() returns true.
However, in drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, dcp_hfi_resp_callback()
drops hfi->resp_drain_lock before unpacking packets and calling
dcp_hfi_complete_pending():
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_resp_callback() {
...
spin_unlock_irqrestore(&hfi->resp_drain_lock, drain_flags);
...
dcp_hfi_complete_pending(hfi, &pkt_info, status);
}
And dcp_hfi_complete_pending() erases comp from hfi->pending_xa under
hfi->pending_lock, drops the lock, and then writes comp->status and calls
complete(&comp->done) without holding either resp_drain_lock or
pending_lock:
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c:dcp_hfi_complete_pending() {
comp = dcp_hfi_pending_find(hfi, pkt_info->packet_id);
if (!comp) {
...
return;
}
...
comp->status = status;
complete(&comp->done);
}
If dcp_hfi_complete_pending() erases comp from pending_xa right as
wait_for_completion_timeout() expires, dcp_hfi_panel_wait() will acquire the
unheld resp_drain_lock, ignore the NULL return from dcp_hfi_pending_find(),
and allow dcp_hfi_panel_init() to return while dcp_hfi_complete_pending() is
still writing to comp->status and calling complete(&comp->done) on the
unwound stack.
If dcp_hfi_pending_find() returns NULL on timeout, should
dcp_hfi_panel_wait() call wait_for_completion(&comp->done) to wait for the
in-flight completion to finish before returning?
> +
> +int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
> + const struct drm_display_mode *mode,
> + const struct mipi_dsi_device *dsi,
> + const struct dcp_dsi_hfi_panel_config *cfg)
> +{
> + u32 num_lanes;
> + u32 bpp;
> + bool is_cmd_mode;
[ ... ]
> + num_lanes = dsi->lanes;
> + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> + if (bpp < 0)
> + return -EINVAL;
> + is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
> + traffic_mode = dcp_dsi_traffic_mode(dsi);
> + link_bpp_x16 = bpp << 4;
[Severity: Medium]
In dcp_hfi_panel_init() in drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, bpp
is declared as u32, whereas mipi_dsi_pixel_format_to_bpp() returns a signed
int that can be -EINVAL on an unrecognized pixel format.
Because bpp is unsigned, is the check (bpp < 0) always false? If
mipi_dsi_pixel_format_to_bpp() returns -EINVAL, bpp becomes 0xffffffeaU,
bypassing the error return, defaulting to HFI_PANEL_BPP_24 in
dcp_bpp_to_hfi(), and corrupting link_bpp_x16 and clk_hz. Should bpp be
declared as int instead of u32?
[ ... ]
> +ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
> + const struct mipi_dsi_msg *msg,
> + void *tx_buf, dma_addr_t tx_iova,
> + void *rx_buf, dma_addr_t rx_iova,
> + u32 post_wait_ms, u32 ctrl_idx)
> +{
> + struct hfi_dsi_cmd_desc cmd_desc = {};
> + static atomic_t seq_no = ATOMIC_INIT(0);
> + int ret;
> +
> + if (!msg || !msg->tx_buf || !msg->tx_len)
> + return -EINVAL;
> +
> + if (msg->rx_len && (!msg->rx_buf || !rx_buf))
> + return -EINVAL;
> +
> + if (msg->tx_len > DCP_DSI_DCS_TX_BUF_SIZE) {
> + dev_err(hfi->dev, "DCS TX too large: %zu > %u\n",
> + msg->tx_len, DCP_DSI_DCS_TX_BUF_SIZE);
> + return -EINVAL;
> + }
> +
> + memcpy(tx_buf, msg->tx_buf, msg->tx_len);
[ ... ]
> + if (msg->rx_len > 0)
> + memcpy(msg->rx_buf, rx_buf, msg->rx_len);
> +
> + return msg->tx_len;
> +}
[Severity: High]
Should dcp_hfi_dcs_transfer() in
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c return msg->rx_len instead of
msg->tx_len when msg->rx_len > 0?
Under the struct mipi_dsi_host_ops.transfer contract in
include/drm/drm_mipi_dsi.h, the return value must be the number of bytes
transmitted for write packets or the number of bytes received for read
packets. Returning msg->tx_len on read transfers causes multi-byte reads
through mipi_dsi_dcs_read() or mipi_dsi_generic_read() (where msg->tx_len !=
msg->rx_len) to appear as short reads to callers checking the received byte
count.
Also, at the entry of dcp_hfi_dcs_transfer(), the check (!msg ||
!msg->tx_buf || !msg->tx_len) rejects 0-parameter generic transfers where
msg->tx_len == 0, while hfi and tx_buf are dereferenced without a NULL
check. Should this validate hfi and tx_buf and allow 0-length tx when
appropriate?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=10
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state
2026-10-26 6:21 ` [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state Jayant Shekhar
2026-10-26 10:49 ` Dmitry Baryshkov
@ 2026-10-26 11:33 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 11:33 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] drm/msm/dcp: unconsumed crtc event on disable in dcp_crtc_atomic_flush()
- [High] drm/msm/dcp: software vblank count returned in dcp_crtc_get_vblank_counter()
- [Medium] drm/msm/dcp: signed crtc_x promotion in dcp_crtc_setup_mixer_rois()
- [Medium] drm/msm/dcp: pending_kickoff_cnt underflow in dcp_crtc_commit_kickoff()
- [Medium] drm/msm/dcp: skipped drm_crtc_vblank_on() in dcp_crtc_atomic_enable()
- [Medium] drm/msm/dcp: stale bandwidth vote when planes hide in dcp_crtc_atomic_begin()
- [Medium] drm/msm/dcp: vblank_enabled race and stale state in dcp_crtc_vblank()
--
commit 5e8f3961384fe1603b59d4d7ed8e2050307e4116
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add DCP CRTCs and shared plane state
This commit adds DRM CRTC objects, session tracking, bandwidth aggregation,
and shared plane state definitions for the firmware-owned DCP display
pipeline in MSM DRM.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> new file mode 100644
> index 0000000000000..ec4f74c2920f8
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
[ ... ]
> +static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + u32 mixer_count;
> + u32 session_id;
> + u32 i;
> + int ret;
> +
> + if (!crtc->state->active)
> + return;
> + if (!dcp_kms->commit_pm_active) {
> + cstate->hfi_error = -EIO;
> + return;
> + }
> +
> + if (!dcp_crtc_get_session(crtc, &session_id)) {
> + cstate->hfi_error = -ENODEV;
> + return;
> + }
> +
> + if (cstate->core_clk > 0 || cstate->dram_ib > 0) {
> + ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id,
> + cstate->dram_ib, cstate->dram_ab,
> + cstate->llcc_ib, cstate->llcc_ab,
> + cstate->core_clk);
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
> + }
[Severity: Medium]
What happens in dcp_crtc_atomic_begin() when an active CRTC that previously
had visible planes is updated so that all planes are disabled or clipped
off-screen while the CRTC remains active?
In dcp_crtc_atomic_check(), dcp_crtc_aggregate_bw() resets dram_ib, dram_ab,
llcc_ib, llcc_ab, and core_clk to 0. Because dcp_crtc_atomic_begin() guards
the call to dcp_hfi_crtc_set_bw() with (cstate->core_clk > 0 ||
cstate->dram_ib > 0), the zeroed bandwidth values are skipped and the
previous non-zero DRAM/LLCC bandwidth and core clock votes remain pinned in
firmware.
Should dcp_hfi_crtc_set_bw() also be called when the aggregated bandwidth
drops to zero on an active CRTC?
[ ... ]
> +static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + unsigned long flags;
> + u32 session_id;
> + int ret;
> +
> + if (!crtc->state->enable)
> + return;
[ ... ]
> +flush_done:
> + spin_lock_irqsave(&crtc->dev->event_lock, flags);
> + dcp_crtc->event = crtc->state->event;
> + crtc->state->event = NULL;
> + spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
> +}
[Severity: High]
Does disabling an active CRTC leave crtc->state->event unconsumed?
In drm_atomic_helper_setup_commit(), whenever old_crtc_state->active is true
(including when disabling an active CRTC with new_crtc_state->enable == false
and new_crtc_state->active == false), DRM core populates new_crtc_state->event
to signal commit->flip_done and takes a commit reference via
drm_crtc_commit_get().
During msm_atomic_commit_tail():
drm_atomic_helper_commit_modeset_disables()
-> dcp_crtc_atomic_disable()
drm_atomic_helper_commit_planes()
-> dcp_crtc_atomic_flush()
dcp_dpu_kms_complete_commit()
-> dcp_crtc_complete_commit()
drm_atomic_helper_commit_hw_done()
Unlike dpu_crtc_disable(), mdp5_crtc_atomic_disable(), and
mdp4_crtc_atomic_disable(), dcp_crtc_atomic_disable() does not send and clear
crtc->state->event. Then dcp_crtc_atomic_flush() returns early when
!crtc->state->enable before transferring crtc->state->event to
dcp_crtc->event, so dcp_crtc_complete_commit() sees dcp_crtc->event == NULL.
Wouldn't this trigger WARN_ON(new_crtc_state->event) in
drm_atomic_helper_commit_hw_done(), leave commit->flip_done unsignaled (stalling
subsequent commits in drm_atomic_helper_wait_for_dependencies()), and leak the
drm_pending_vblank_event and drm_crtc_commit reference? Also, if an earlier
event was still in dcp_crtc->event during dcp_crtc_atomic_flush(), could
unconditionally assigning dcp_crtc->event = crtc->state->event overwrite it?
[ ... ]
> +void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + u32 session_id;
> + int ret;
> +
[ ... ]
> + /* MSM serializes commits, so only one frame completion may be pending. */
> + if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
> + drm_err(crtc->dev,
> + "CRTC%u: frame completion already pending\n",
> + dcp_crtc->display_id);
> + cstate->hfi_error = -EBUSY;
> + return;
> + }
> + dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
> +
> + ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
> + if (ret) {
> + drm_err(crtc->dev,
> + "HFI commit failed for CRTC%u (session=%u): %d\n",
> + dcp_crtc->display_id, session_id, ret);
> + atomic_dec(&dcp_crtc->pending_kickoff_cnt);
> + wake_up(&dcp_crtc->frame_done_wq);
> + }
> +}
[Severity: Medium]
Can dcp_crtc->pending_kickoff_cnt underflow to -1 if
dcp_crtc_hfi_frame_done_cb() races with dcp_crtc_commit_kickoff()?
In dcp_crtc_commit_kickoff(), pending_kickoff_cnt is set to 1 via
atomic_cmpxchg() before dcp_crtc->pending_seq_id is updated, and then
dcp_hfi_display_commit() waits synchronously for the HFI response.
If a late HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE from a prior timed-out
commit arrives before pending_seq_id is written, or if the frame scan
complete event for the current commit arrives and runs
dcp_crtc_frame_done_callback() (decrementing pending_kickoff_cnt from 1 to 0
via atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0)) before
dcp_hfi_display_commit() returns an error, the error path executes
atomic_dec(&dcp_crtc->pending_kickoff_cnt) and drives pending_kickoff_cnt to
-1.
Once pending_kickoff_cnt is -1, dcp_crtc_wait_for_commit_done() will wait for
pending_kickoff_cnt == 0 until the 500ms timeout expires, and any further
dcp_crtc_frame_done_callback() calls will continue decrementing the negative
counter because atomic_add_unless(..., -1, 0) only stops at 0.
Should dcp_crtc->pending_seq_id be written before setting
pending_kickoff_cnt, and should the error path use atomic_set(..., 0) or
atomic_add_unless(..., -1, 0) instead of atomic_dec()?
[ ... ]
> +int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + u32 session_id;
> + int ret;
> +
> + if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))
> + return -ENODEV;
> +
> + if (enable)
> + ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
> + session_id, HFI_EVENT_VSYNC);
> + else
> + ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
> + session_id, HFI_EVENT_VSYNC);
> + if (ret)
> + return ret;
> +
> + WRITE_ONCE(dcp_crtc->vblank_enabled, enable);
> + return 0;
> +}
> +
> +void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
> +{
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> +
> + if (!READ_ONCE(dcp_crtc->vblank_enabled))
> + return;
[Severity: Medium]
Could updating dcp_crtc->vblank_enabled at the end of dcp_crtc_vblank() drop
initial VSYNC events or leave vblank_enabled stuck at true after disable?
When enable is true, dcp_hfi_display_event_register_async() queues and kicks
the HFI command before WRITE_ONCE(dcp_crtc->vblank_enabled, true) runs. If a
VSYNC event arrives on the response virtqueue before WRITE_ONCE() executes,
dcp_crtc_vblank_callback() sees vblank_enabled == false and drops it.
Conversely, when enable is false, msm_crtc_disable_vblank() runs
vblank_ctrl_worker asynchronously. If the CRTC session has already been
cleared via dcp_crtc_clear_session() by the time dcp_crtc_vblank(crtc, false)
runs (or if dcp_hfi_display_event_deregister_async() fails), dcp_crtc_vblank()
returns early without clearing dcp_crtc->vblank_enabled.
[ ... ]
> +static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
> + struct drm_crtc_state *crtc_state,
> + struct dcp_crtc_state *cstate)
> +{
[ ... ]
> + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
> + bool fullscreen;
> + bool assigned = false;
> + u32 mixer_idx;
> +
> + if (!pstate || !pstate->visible || !pstate->fb)
> + continue;
> +
> + for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
> + struct dcp_mixer_roi *mixer = &cstate->mixer_roi[mixer_idx];
> +
> + if (pstate->crtc_x >= mixer->x &&
> + pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w) {
> + assigned = true;
> + break;
> + }
> + }
[Severity: Medium]
In dcp_crtc_setup_mixer_rois(), pstate->crtc_x is a signed int32_t while
mixer->x, mixer->w, and pstate->crtc_w are unsigned u32. What happens when a
visible plane has a negative crtc_x?
Because C integer promotion converts pstate->crtc_x to u32 in both
comparisons:
pstate->crtc_x >= mixer->x
pstate->crtc_x + pstate->crtc_w <= mixer->x + mixer->w
a negative pstate->crtc_x becomes a large unsigned value (>= 0x80000000) that
is always >= mixer->x, while (u32)pstate->crtc_x + pstate->crtc_w wraps
around to the plane's positive right edge. On a multi-mixer topology
(mixer_count > 1), a plane starting at crtc_x < 0 and extending across the
mixer boundary into mixer 1 will satisfy both checks for mixer 1 and set
assigned = true instead of returning -EOPNOTSUPP.
In addition, should this check use the clipped destination rectangle
(pstate->dst.x1 and pstate->dst.x2) rather than unclipped pstate->crtc_x and
pstate->crtc_w so that planes partially clipped at the display edges are
validated against the active mixer ROIs?
[ ... ]
> +static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
> +{
> + return drm_crtc_vblank_count(crtc);
> +}
[Severity: High]
Is it valid for dcp_crtc_get_vblank_counter() to return
drm_crtc_vblank_count(crtc) as the drm_crtc_funcs.get_vblank_counter callback?
drm_crtc_funcs.get_vblank_counter is called by __get_vblank_counter() in
drivers/gpu/drm/drm_vblank.c to read a raw hardware frame counter, whereas
drm_crtc_vblank_count() returns DRM core's own cooked software counter
(vblank->count).
During drm_crtc_vblank_on() -> drm_reset_vblank_timestamp() (and during
drm_crtc_handle_vblank()), store_vblank() sets vblank->last to cur_vblank (N)
and increments vblank->count to N + 1. On the next non-IRQ call to
drm_update_vblank_count(dev, pipe, false) (such as drm_vblank_enable() in
drm_crtc_vblank_get() or drm_vblank_disable_and_save() in
drm_crtc_vblank_off()), __get_vblank_counter() returns N + 1 while
vblank->last is N:
if (max_vblank_count == 0)
drm_WARN_ON_ONCE(dev, cur_vblank != vblank->last);
When max_vblank_count is 0, this immediately triggers drm_WARN_ON_ONCE();
when max_vblank_count is non-zero, it spuriously increments the software
vblank counter on every vblank enable and disable.
If DCP does not expose a hardware frame counter register, shouldn't
get_vblank_counter be omitted (left NULL in dcp_crtc_funcs)?
> +
> +static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
> + struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
> + u32 session_id;
> + int ret;
> +
> + if (!dcp_crtc_get_session(crtc, &session_id))
> + return;
> +
> + ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
> + HFI_MODE_DPMS_ON);
> + if (ret) {
> + drm_err(crtc->dev, "CRTC%u: DPMS ON failed: %d\n",
> + to_dcp_crtc(crtc)->display_id, ret);
> + cstate->hfi_error = ret;
> + }
> +
> + drm_crtc_vblank_on(crtc);
> +}
[Severity: Medium]
If dcp_crtc_get_session() returns false in dcp_crtc_atomic_enable(), does
returning early before drm_crtc_vblank_on(crtc) unbalance the vblank refcount?
When a CRTC is initialized or disabled via drm_crtc_vblank_off(),
vblank->inmodeset is 1 and vblank->refcount is 1. Later in
msm_atomic_commit_tail(), vblank_get() checks crtc->state->active (which is
true) and calls drm_crtc_vblank_get(crtc); because drm_crtc_vblank_on() was
skipped, vblank->inmodeset is still 1, so drm_crtc_vblank_get() fails with
-EINVAL without incrementing refcount, and then vblank_put() unconditionally
calls drm_crtc_vblank_put(crtc), decrementing vblank->refcount from 1 to 0.
In addition, dcp_crtc_atomic_disable() unconditionally calls
drm_crtc_vblank_off(crtc) regardless of whether a session is present.
Should drm_crtc_vblank_on(crtc) always be called in dcp_crtc_atomic_enable()
even if dcp_crtc_get_session() fails?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=11
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 12/23] drm/msm/dcp: Add DCP planes
2026-10-26 6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
@ 2026-10-26 11:57 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 11:57 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [Critical] drm/msm/dcp: missing HFI pipe detach in dcp_plane_atomic_update()
- [Critical] drm/msm/dcp: unlocked plane->state access in dcp_plane_pipe_busy()
- [High] drm/msm/dcp: stale plane visibility used in dcp_plane_assign_pipe()
- [High] drm/msm/dcp: 90/270 rotation ignored in dcp_plane_atomic_check()
- [High] drm/msm/dcp: dcp_plane_atomic_create_state() returns NULL on OOM
- [High] drm/msm/dcp: broken IS_ERR() and NULL checks in dcp_plane_atomic_check()
- [High] drm/msm/dcp: divide-by-zero in dcp_plane_calc_bw() on zero vblank
- [Medium] drm/msm/dcp: chroma planes omitted in dcp_plane_calc_perf() bpp
- [Medium] drm/msm/dcp: non-alpha formats not forced to opaque blending in dcp_plane
- [Medium] drm/msm/dcp: missing minimum source size check for RGB formats in dcp_plane_atomic_check()
--
commit 22e98b5154e2eb947949b5c49894d72a16ccd158
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add DCP planes
This commit adds DRM plane support for the DCP display driver, including
format and scaling validation, firmware pipe assignment, bandwidth and
clock calculation, and HFI property packet construction and submission.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.c b/drivers/gpu/drm/msm/dcp/dcp_plane.c
> new file mode 100644
> index 0000000000000..af8aa2162b03d
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
[ ... ]
> +static bool dcp_plane_pipe_busy(struct drm_atomic_commit *state,
> + struct drm_plane *self, u32 pipe_id)
> +{
> + struct drm_plane *plane;
> + struct drm_plane_state *plane_state;
> + struct drm_plane *iter_plane;
> + struct drm_plane_state *new_plane_state;
> + int i;
> +
> + drm_for_each_plane(plane, state->dev) {
> + if (plane == self)
> + continue;
> +
> + plane_state = plane->state;
> + for_each_new_plane_in_state(state, iter_plane, new_plane_state, i) {
> + if (iter_plane == plane) {
> + plane_state = new_plane_state;
> + break;
> + }
> + }
> +
> + if (plane_state && plane_state->crtc && plane_state->visible &&
> + to_dcp_plane_state(plane_state)->hfi_pipe_id == pipe_id)
> + return true;
> + }
> +
> + return false;
> +}
> +
> +static u32 dcp_plane_assign_pipe(struct dcp_dpu_kms *dcp_kms,
> + struct drm_atomic_commit *state,
> + struct drm_plane *plane,
> + struct dcp_plane_state *pstate,
> + bool yuv, bool scaling)
> +{
> + const struct dcp_caps *caps = dcp_kms->caps;
> + u32 i, pipe_id;
> +
> + /* YUV scanout requires a VIG pipe with the CSC path. */
> + if (dcp_plane_hfi_pipe_valid(dcp_kms, pstate->hfi_pipe_id) &&
> + (!yuv || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
> + (!scaling || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
> + !dcp_plane_pipe_busy(state, plane, pstate->hfi_pipe_id))
> + return pstate->hfi_pipe_id;
> +
> + /*
> + * R1/virtual pipes are intentionally not allocated as independent
> + * planes yet. They require transactional physical-pipe/R1 ownership
> + * and multirect programming, which will be added with the resource
> + * manager.
> + */
> + for (i = 0; i < caps->num_vig_pipes; i++) {
> + pipe_id = caps->vig_pipe_ids[i];
> + if (!dcp_plane_pipe_busy(state, plane, pipe_id))
> + return pipe_id;
> + }
[Severity: Critical]
Is it safe for dcp_plane_pipe_busy() to read plane->state for planes that
are not part of the current drm_atomic_commit without holding plane->mutex?
When dcp_plane_atomic_check() calls dcp_plane_assign_pipe() ->
dcp_plane_pipe_busy(), it iterates over all planes on the device via
drm_for_each_plane() and falls back to plane_state = plane->state for any
plane not present in state.
Because DCP supports multiple CRTCs and planes are shared across
possible_crtcs, a concurrent atomic commit on another CRTC holding that
other plane's mutex can swap plane->state in drm_atomic_helper_swap_state()
and free the old state via drm_atomic_commit_put() ->
dcp_plane_atomic_destroy_state() -> kfree() while dcp_plane_pipe_busy() is
dereferencing plane_state->crtc, plane_state->visible, and
to_dcp_plane_state(plane_state)->hfi_pipe_id.
In addition, without serializing pipe assignment through a shared
drm_private_obj state or lock, can two concurrent atomic checks on
different CRTCs both see the same pipe_id as free in dcp_plane_pipe_busy()
and assign the same hardware HFI pipe to two active planes?
[ ... ]
> +static u64 dcp_plane_calc_bw(u32 bpp,
> + const struct drm_display_mode *mode,
> + const struct drm_rect *src,
> + const struct drm_rect *dst)
> +{
> + int src_w = drm_rect_width(src) >> 16;
> + int src_h = drm_rect_height(src) >> 16;
> + int dst_h = drm_rect_height(dst);
> + int fps = drm_mode_vrefresh(mode);
> + int vbp = mode->vtotal - mode->vsync_end;
> + int vpw = mode->vsync_end - mode->vsync_start;
> + int vfp = mode->vsync_start - mode->vdisplay;
> + u32 hw_latency = 25;
> + u64 scale_factor = (src_h > dst_h) ? mult_frac(src_h, 1, dst_h) : 1;
> + u64 plane_bw, prefill_bw;
> +
> + plane_bw = (u64)src_w * mode->vtotal * fps * bpp * scale_factor;
> + prefill_bw = plane_bw * hw_latency;
> +
> + if ((vbp + vpw) > hw_latency)
> + do_div(prefill_bw, (vbp + vpw));
> + else if ((vbp + vpw + vfp) < hw_latency)
> + do_div(prefill_bw, (vbp + vpw + vfp));
> + else
> + do_div(prefill_bw, hw_latency);
[Severity: High]
Can dcp_plane_calc_bw() trigger a divide-by-zero when (vbp + vpw + vfp) is
zero?
Here, vbp + vpw + vfp equals mode->vtotal - mode->vdisplay. Neither
drm_mode_validate_basic() nor dcp_crtc_mode_valid() rejects a mode with
vdisplay == vsync_start == vsync_end == vtotal.
If userspace submits an atomic commit or DRM_MODE_ATOMIC_TEST_ONLY check
with such a mode and a visible plane, (vbp + vpw) > hw_latency is false and
(vbp + vpw + vfp) < hw_latency is true (0 < 25), causing do_div(prefill_bw,
(vbp + vpw + vfp)) to divide by zero.
[ ... ]
> +void dcp_plane_calc_perf(const struct drm_plane_state *state,
> + const struct drm_display_mode *mode,
> + struct dcp_plane_perf *perf)
> +{
> + struct drm_rect src, dst;
> + u32 bpp;
> +
> + memset(perf, 0, sizeof(*perf));
> +
> + if (!state || !state->visible || !state->fb || !mode || !mode->clock ||
> + !drm_mode_vrefresh(mode))
> + return;
> +
> + src = drm_plane_state_src(state);
> + dst = drm_plane_state_dest(state);
> + bpp = drm_format_info_bpp(state->fb->format, 0);
> +
> + perf->fetch_bw = dcp_plane_calc_bw(bpp, mode, &src, &dst);
> + perf->clk = dcp_plane_calc_clk(mode, &src, &dst);
> +}
[Severity: Medium]
Does querying drm_format_info_bpp(state->fb->format, 0) in
dcp_plane_calc_perf() undercount the bandwidth for multi-planar YUV
formats?
For formats such as DRM_FORMAT_NV12, DRM_FORMAT_NV21, DRM_FORMAT_YUV420,
DRM_FORMAT_YVU420, and DRM_FORMAT_P010, plane 0 only accounts for the luma
plane (8 bpp for 8-bit 4:2:0 or 16 bpp for P010), omitting the chroma
planes (which add another 4 bpp or 8 bpp) from perf->fetch_bw.
[ ... ]
> +static int dcp_plane_build_one_hfi_props_packet(struct dcp_plane_state *pstate,
> + const struct drm_plane_state *state,
> + u32 session_id, u32 packet_id,
> + u8 *packet)
> +{
[ ... ]
> + hfi_rotation = dcp_drm_to_hfi_rotation(state->rotation);
> + hfi_blend_op = dcp_drm_to_hfi_blend_op(state->pixel_blend_mode);
> +
> + src_x = state->src_x >> 16;
> + src_y = state->src_y >> 16;
> + src_w = state->src_w >> 16;
> + src_h = state->src_h >> 16;
> + dst_x = state->crtc_x;
> + dst_y = state->crtc_y;
> + dst_w = state->crtc_w;
> + dst_h = state->crtc_h;
> +
> + hfi_alpha = dcp_hfi_scale_alpha(state, state->alpha);
> + hfi_bg_alpha = dcp_hfi_scale_alpha(state,
> + dcp_compute_bg_alpha(state->pixel_blend_mode,
> + state->alpha));
[Severity: Medium]
When !fb->format->has_alpha (for example with DRM_FORMAT_XRGB8888,
DRM_FORMAT_RGB565, or YUV formats), should dcp_plane_build_one_hfi_props_packet()
fall back to DRM_MODE_BLEND_PIXEL_NONE instead of using
state->pixel_blend_mode directly?
dcp_plane_init() attaches the pixel blend mode property with
DRM_MODE_BLEND_PREMULTI as the default. When a non-alpha framebuffer is
used, passing DRM_MODE_BLEND_PREMULTI or DRM_MODE_BLEND_COVERAGE to
dcp_drm_to_hfi_blend_op() and dcp_compute_bg_alpha() programs
HFI_BLEND_OP_PREMULTIPLIED or HFI_BLEND_OP_COVERAGE and sets bg_alpha to
fg_alpha (or 0 when opaque) rather than HFI_BLEND_OP_OPAQUE with
DRM_BLEND_ALPHA_OPAQUE - fg_alpha.
[ ... ]
> +static int dcp_plane_atomic_check(struct drm_plane *plane,
> + struct drm_atomic_commit *state)
> +{
[ ... ]
> + if (!new_state->crtc)
> + return 0;
> +
> + crtc_state = drm_atomic_get_new_crtc_state(state, new_state->crtc);
> + if (IS_ERR(crtc_state)) {
> + ret = PTR_ERR(crtc_state);
> + drm_err(plane->dev, "plane%u: failed to get CRTC state: %d\n",
> + plane->base.id, ret);
> + return ret;
> + }
[Severity: High]
drm_atomic_get_new_crtc_state() returns NULL when the CRTC is not in state,
rather than an ERR_PTR(). Should this check for !crtc_state instead of
IS_ERR(crtc_state) so a NULL crtc_state is not passed to
drm_atomic_helper_check_plane_state()?
Similarly, in dcp_plane_assign_pipe(), dcp_plane_hfi_pipe_valid() checks
if (!dcp_kms->caps), but when it returns false, dcp_plane_assign_pipe()
immediately dereferences caps->num_vig_pipes and caps->num_dma_pipes
without checking if caps is NULL.
> + if (!new_state->fb)
> + return 0;
> +
> + scaling = new_state->src_w != ((u32)new_state->crtc_w << 16) ||
> + new_state->src_h != ((u32)new_state->crtc_h << 16);
> +
> + pstate->hfi_pipe_id = dcp_plane_assign_pipe(dcp_kms, state, plane,
> + pstate, new_state->fb->format->is_yuv,
> + scaling);
> + if (pstate->hfi_pipe_id == U32_MAX) {
> + drm_err(plane->dev, "plane%u: no compatible HFI pipe available\n",
> + plane->base.id);
> + return -ENOSPC;
> + }
> +
> + if (dcp_plane_pipe_is_vig(dcp_kms->caps, pstate->hfi_pipe_id)) {
> + min_scale = FRAC_16_16(1, DCP_MAX_UPSCALE_RATIO);
> + max_scale = DCP_MAX_DOWNSCALE_RATIO << 16;
> + }
> +
> + ret = drm_atomic_helper_check_plane_state(new_state, crtc_state,
> + min_scale, max_scale,
> + true, true);
[Severity: High]
Because dcp_plane_assign_pipe() -> dcp_plane_pipe_busy() runs before
drm_atomic_helper_check_plane_state() computes new_state->visible, won't
dcp_plane_pipe_busy() inspect stale visibility flags?
Specifically:
1. When drm_atomic_helper_check_planes() checks planes in ascending index
order, any higher-indexed plane in the same atomic commit has not run
dcp_plane_atomic_check() yet, so its new_plane_state->visible still
holds the old state's visibility. If that higher-indexed plane was
previously disabled and is being enabled in this commit,
dcp_plane_pipe_busy() sees new_plane_state->visible == false and allows
an earlier plane to take its hfi_pipe_id.
2. If the current plane is clipped off-screen so
drm_atomic_helper_check_plane_state() sets new_state->visible = false,
it has already claimed a pipe in dcp_plane_assign_pipe().
3. When a previously visible plane is disabled (!new_state->crtc or
!new_state->fb), dcp_plane_atomic_check() returns 0 early before calling
drm_atomic_helper_check_plane_state(), leaving new_state->visible set to
true on the disabled state.
> + if (ret) {
> + drm_err(plane->dev, "plane%u: plane state check failed: %d\n",
> + plane->base.id, ret);
> + return ret;
> + }
> +
> + if (!new_state->visible)
> + return 0;
[ ... ]
> + dcp_mode_limits(&crtc_state->adjusted_mode,
> + dcp_kms->caps->max_width, dcp_kms->caps->max_height,
> + &max_width, &max_height);
> +
> + if (new_state->fb->width > max_width ||
> + new_state->fb->height > max_height) {
> + drm_err(plane->dev,
> + "plane%u: framebuffer %ux%u exceeds caps %ux%u\n",
> + plane->base.id, new_state->fb->width, new_state->fb->height,
> + dcp_kms->caps->max_width, dcp_kms->caps->max_height);
> + return -E2BIG;
> + }
[Severity: High]
How does dcp_plane_atomic_check() handle 90 and 270 degree rotations?
dcp_plane_init() advertises DRM_MODE_ROTATE_90 and DRM_MODE_ROTATE_270, and
drm_atomic_helper_check_plane_state() rotates the source rectangle via
drm_rect_rotate() before checking hscale and vscale. However:
1. The scaling check earlier in dcp_plane_atomic_check():
scaling = new_state->src_w != ((u32)new_state->crtc_w << 16) ||
new_state->src_h != ((u32)new_state->crtc_h << 16);
compares unrotated source dimensions against crtc_w and crtc_h. For a
non-square plane rotated 90 or 270 degrees, an unscaled plane (where
src_w == crtc_h << 16 and src_h == crtc_w << 16) is treated as scaled
and forced onto a VIG pipe, whereas a scaled plane with
src_w == crtc_w << 16 and src_h == crtc_h << 16 is treated as unscaled,
assigned a DMA pipe with DRM_PLANE_NO_SCALING, and rejected by
drm_atomic_helper_check_plane_state() with -ERANGE.
2. The dcp_mode_limits() check compares unrotated new_state->fb->width and
new_state->fb->height against mode-oriented max_width and max_height,
rejecting a landscape framebuffer rotated 90/270 degrees onto a portrait
mode with -E2BIG.
3. dcp_plane_calc_perf() passes unrotated drm_plane_state_src(state) to
dcp_plane_calc_bw() and dcp_plane_calc_clk(), using unrotated src_w and
src_h for bandwidth and clock scaling calculations.
> + for (i = 0; i < new_state->fb->format->num_planes; i++) {
> + if (new_state->fb->pitches[i] > DCP_SSPP_MAX_PITCH_SIZE) {
> + drm_err(plane->dev,
> + "plane%u: pitch[%d]=%u exceeds limit %u\n",
> + plane->base.id, i, new_state->fb->pitches[i],
> + DCP_SSPP_MAX_PITCH_SIZE);
> + return -E2BIG;
> + }
> + }
> +
> + src = drm_plane_state_src(new_state);
> +
> + pstate->src_img_w = new_state->fb->width;
> + pstate->src_img_h = new_state->fb->height;
> +
> + if (new_state->fb->format->is_yuv) {
> + u32 src_x = src.x1 >> 16;
> + u32 src_y = src.y1 >> 16;
> + u32 src_w = drm_rect_width(&src) >> 16;
> + u32 src_h = drm_rect_height(&src) >> 16;
> +
> + if (src_w < 2 || src_h < 2) {
> + drm_err(plane->dev,
> + "plane%u: YUV source too small: %ux%u\n",
> + plane->base.id, src_w, src_h);
> + return -EINVAL;
> + }
[Severity: Medium]
Should non-YUV formats also enforce a minimum integer source size of
src_w >= 1 and src_h >= 1?
When an RGB plane is assigned to a VIG pipe, min_scale is
FRAC_16_16(1, DCP_MAX_UPSCALE_RATIO), which allows sub-pixel 16.16 source
dimensions (such as src_w or src_h in [3276, 65535] with crtc_w = 1 or
crtc_h = 1) to pass drm_atomic_helper_check_plane_state() with
new_state->visible = true.
Because minimum source dimensions are only checked inside the is_yuv block,
state->src_w >> 16 or state->src_h >> 16 then truncates to 0 in
dcp_plane_calc_bw() (resulting in a 0 bandwidth vote for a visible plane)
and in dcp_plane_build_one_hfi_props_packet() (sending a 0-width or
0-height HFI_PROPERTY_LAYER_SRC_ROI to the firmware).
[ ... ]
> +static void dcp_plane_atomic_update(struct drm_plane *plane,
> + struct drm_atomic_commit *state)
> +{
> + struct drm_plane_state *new_state =
> + drm_atomic_get_new_plane_state(state, plane);
> + struct dcp_plane_state *pstate = to_dcp_plane_state(new_state);
> + int ret;
> +
> + drm_dbg_driver(plane->dev,
> + "plane%u: atomic_update visible=%d fb=%u pipe_id=%u\n",
> + plane->base.id, new_state->visible,
> + new_state->fb ? new_state->fb->base.id : 0,
> + pstate->hfi_pipe_id);
> +
> + pstate->prop_pkt_size = 0;
> +
> + if (!new_state->visible || !new_state->fb)
> + return;
> +
> + ret = dcp_plane_submit_update(plane, new_state);
> + if (ret) {
> + drm_err(plane->dev,
> + "plane%u: HFI update failed: %d\n", plane->base.id, ret);
> + dcp_plane_set_hfi_error(new_state, ret);
> + return;
> + }
> +}
> +
> +static void dcp_plane_atomic_disable(struct drm_plane *plane,
> + struct drm_atomic_commit *state)
> +{
> + struct drm_plane_state *old_state =
> + drm_atomic_get_old_plane_state(state, plane);
> + struct dcp_plane_state *pstate = to_dcp_plane_state(old_state);
> + int ret;
> +
> + drm_dbg_driver(plane->dev,
> + "plane%u: atomic_disable pipe_id=%u\n",
> + plane->base.id, pstate->hfi_pipe_id);
> +
> + ret = dcp_plane_submit_disable(plane, old_state);
[Severity: Critical]
Can HFI pipes remain attached to unmapped framebuffers or be erroneously
detached from another plane during atomic commits?
There appear to be three related issues in the update/disable paths:
1. When an active visible plane remains bound to a CRTC and framebuffer
(new_state->crtc != NULL and new_state->fb != NULL) but is moved
off-screen so new_state->visible becomes false,
drm_atomic_plane_disabling() is false and
drm_atomic_helper_commit_planes() calls dcp_plane_atomic_update() rather
than dcp_plane_atomic_disable(). dcp_plane_atomic_update() returns early
at if (!new_state->visible || !new_state->fb) without detaching
old_state->hfi_pipe_id, leaving the DCP firmware scanning out from the
old framebuffer IOVA after dcp_plane_cleanup_fb() ->
msm_framebuffer_cleanup() unmaps it.
2. When an already-visible plane is reassigned to a different pipe by
dcp_plane_assign_pipe() (for example, switching from an unscaled RGB
buffer on a DMA pipe to a YUV or scaled buffer requiring a VIG pipe, so
old_pstate->hfi_pipe_id != pstate->hfi_pipe_id),
dcp_plane_atomic_update() attaches the new pipe but never sends
HFI_PROPERTY_DISPLAY_DETACH_LAYER for old_pstate->hfi_pipe_id.
3. Because dcp_plane_pipe_busy() treats an invisible or disabling plane's
hfi_pipe_id as free, a lower-indexed plane can be assigned and attach
that pipe in dcp_plane_atomic_update() before the higher-indexed plane
runs dcp_plane_atomic_disable() (which executes in ascending plane index
order and does not check old_state->visible), causing
dcp_plane_atomic_disable() to detach the pipe that the lower-indexed
plane just attached.
[ ... ]
> +static struct drm_plane_state *
> +dcp_plane_atomic_create_state(struct drm_plane *plane)
> +{
> + struct dcp_plane_state *pstate;
> + u32 hfi_pipe_id = to_dcp_plane(plane)->hfi_pipe_id;
> +
> + pstate = kzalloc_obj(*pstate, GFP_KERNEL);
> + if (!pstate)
> + return NULL;
[Severity: High]
Should dcp_plane_atomic_create_state() return ERR_PTR(-ENOMEM) instead of
NULL on allocation failure?
Its caller drm_mode_config_plane_create_state() checks for errors using
IS_ERR(plane_state), so returning NULL bypasses error handling and leaves
plane->state as NULL.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=12
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 13/23] drm/msm/dcp: Route HFI display events
2026-10-26 6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
@ 2026-10-26 12:08 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 12:08 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/msm/dcp: unsynchronized frame done state in dcp_crtc_hfi_frame_done_cb()
- [Medium] drm/msm/dcp: missing payload validation in dcp_hfi_dispatch_frame_done()
--
commit 7606ba3e12102c6ab2bc26f57c18f121dc3e91f7
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Route HFI display events
This commit routes asynchronous HFI VSYNC and frame-scan completion display
events from DCP firmware to the corresponding DRM CRTC callbacks, and adds a
NULL check for dma_dev in dcp_hfi_init().
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> index 774a5afac0f0a..e54ccfafdae0f 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> @@ -19,6 +19,7 @@
> #include "dcp_hfi.h"
> #include "dcp_hfi_priv.h"
> #include "dcp_drv.h"
> +#include "dcp_crtc.h"
> #include "hfi/hfi_packet.h"
> #include "hfi/hfi_packer.h"
> #include "hfi/hfi_unpacker.h"
> @@ -260,18 +261,35 @@ static void dcp_hfi_store_caps_payload(struct dcp_hfi *hfi,
> dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
> }
>
> +static void dcp_hfi_dispatch_frame_done(struct dcp_hfi *hfi,
> + const struct hfi_packet_info *pkt_info)
> +{
> + const struct hfi_display_frame_event_data *frame = NULL;
> + u32 sequence_id = 0;
> +
> + if (pkt_info->payload_ptr &&
> + pkt_info->payload_size >= sizeof(*frame)) {
> + frame = pkt_info->payload_ptr;
> + sequence_id = frame->bufferflip_index;
> + }
> + dcp_crtc_hfi_frame_done_cb(hfi->drv, pkt_info->id & 0xFFFF,
> + sequence_id);
> +}
[Severity: Medium]
Should dcp_hfi_dispatch_frame_done() drop the packet and return early when
pkt_info->payload_ptr is NULL or pkt_info->payload_size is smaller than
sizeof(*frame)?
As written, a truncated or empty HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE
packet falls through and invokes dcp_crtc_hfi_frame_done_cb() with a
fabricated sequence_id of 0:
drivers/gpu/drm/msm/dcp/dcp_crtc.c:dcp_crtc_hfi_frame_done_cb() {
...
if (crtc) {
struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
/* A single pending commit makes equality valid across sequence wrap. */
if (sequence_id != dcp_crtc->pending_seq_id) {
drm_warn(crtc->dev,
"CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u expected=%u\n",
dcp_crtc->display_id, sequence_id,
dcp_crtc->pending_seq_id);
return;
}
dcp_crtc_frame_done_callback(crtc);
}
...
}
If dcp_crtc->pending_seq_id happens to be 0 (such as before the first commit
sequence is latched or when the 32-bit commit_seq_id counter wraps around to
0), this will falsely match pending_seq_id and prematurely complete the frame.
When pending_seq_id is non-zero, it logs an unratelimited warning for a bogus
sequence 0 event.
> +
> /* Route asynchronous events and capability packets before flag handling. */
> static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
> const struct hfi_packet_info *pkt_info)
> {
> switch (pkt_info->cmd) {
> case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
> + dcp_crtc_hfi_vblank_cb(hfi->drv, pkt_info->id & 0xFFFF);
> return true;
>
> case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
> return true;
>
> case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
> + dcp_hfi_dispatch_frame_done(hfi, pkt_info);
> return true;
[Severity: High]
With HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE now routed from the
asynchronous VirtIO response callback (dcp_hfi_resp_callback() ->
dcp_hfi_dispatch_event() -> dcp_hfi_dispatch_frame_done()) into
dcp_crtc_hfi_frame_done_cb(), can concurrent access to dcp_crtc->pending_seq_id
and dcp_crtc->pending_kickoff_cnt race with the commit path?
In drivers/gpu/drm/msm/dcp/dcp_crtc.c, dcp_crtc_commit_kickoff() updates
pending_kickoff_cnt and pending_seq_id without lock synchronization against
dcp_crtc_hfi_frame_done_cb():
if (atomic_cmpxchg(&dcp_crtc->pending_kickoff_cnt, 0, 1)) {
drm_err(crtc->dev,
"CRTC%u: frame completion already pending\n",
dcp_crtc->display_id);
cstate->hfi_error = -EBUSY;
return;
}
dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
if (ret) {
drm_err(crtc->dev,
"HFI commit failed for CRTC%u (session=%u): %d\n",
dcp_crtc->display_id, session_id, ret);
atomic_dec(&dcp_crtc->pending_kickoff_cnt);
wake_up(&dcp_crtc->frame_done_wq);
}
This appears to allow several races when asynchronous frame-done events arrive:
1. pending_kickoff_cnt is set to 1 before dcp_crtc->pending_seq_id is updated
to dcp_crtc->commit_seq_id. If a FRAME_SCAN_COMPLETE event carrying the
previous sequence ID arrives in that window (for example after a prior commit
timeout in dcp_crtc_wait_for_commit_done() reset pending_kickoff_cnt to 0 while
leaving pending_seq_id at the old value, or from periodic scanout),
dcp_crtc_hfi_frame_done_cb() sees sequence_id == dcp_crtc->pending_seq_id and
calls dcp_crtc_frame_done_callback(), decrementing pending_kickoff_cnt back to
0 before dcp_hfi_display_commit() even runs.
2. If dcp_crtc_frame_done_callback() decrements pending_kickoff_cnt from 1 to
0 (either via the window above or because firmware sent FRAME_SCAN_COMPLETE
while dcp_hfi_display_commit() timed out waiting for the command response) and
dcp_hfi_display_commit() returns an error, the error path unconditionally runs
atomic_dec(&dcp_crtc->pending_kickoff_cnt), underflowing pending_kickoff_cnt to
-1. Because dcp_crtc_frame_done_callback() uses
atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0), subsequent frame-done
events will decrement it further (-2, -3, ...) and cause future commits to
stall until DCP_FRAME_DONE_TIMEOUT_MS expires.
3. In dcp_crtc_atomic_flush(), ++dcp_crtc->commit_seq_id is sent to firmware
via dcp_hfi_crtc_set_scan_sequence_id() and autorefresh is enabled via
dcp_hfi_crtc_set_autorefresh() before dcp_crtc_commit_kickoff() sets
pending_seq_id and pending_kickoff_cnt. If firmware emits a FRAME_SCAN_COMPLETE
event for the new sequence ID before dcp_crtc_commit_kickoff() updates
pending_seq_id, dcp_crtc_hfi_frame_done_cb() will drop it as a stale sequence
mismatch.
Should pending_seq_id and pending_kickoff_cnt updates and checks be
synchronized under a spinlock across the flush, kickoff, and HFI callback
paths?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=13
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support
2026-10-26 6:21 ` [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support Jayant Shekhar
2026-10-26 11:08 ` Dmitry Baryshkov
@ 2026-10-26 12:37 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 12:37 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] drm/msm/dcp: off-by-one DSC chroma and color space enums in dcp_dsi_host_attach()
- [High] drm/msm/dcp: deadlock and unsafe teardown in dcp_dsi_bridge_cleanup()
- [Medium] drm/msm/dcp: OF node leak of dcp_np on error in dcp_dsi_bridge_init()
- [Medium] drm/msm/dcp: dcp_dsi_bridge_setup_dsc() never sets dsc_rc_override
- [Medium] drm/msm/dcp: duplicate dsi_host_dev name in dcp_dsi_bridge_init()
- [Medium] drm/msm/dcp: invalid session close and goto mixing in dcp_dsi_bridge_prepare_session()
- [Medium] drm/msm/dcp: missing session_lock in dcp_dsi_bridge_atomic_enable()
- [Medium] drm/msm/dcp: unsynchronized session_id and packet_id in power control
- [Medium] drm/msm/dcp: firmware session leak on dcp_hfi_display_close() early return
- [Medium] drm/msm/dcp: wrong byte count returned for DSI reads in dcp_dsi_host_transfer()
--
commit 7535bc53c50473e4189f52c83b74358ab514c8d0
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add DCP DSI host and panel support
This commit implements the MIPI DSI host, panel-device support, bridge
state, DSC configuration, DCS transfers, and DSI buffer management for the
DCP display path.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
> new file mode 100644
> index 0000000000000..b3e782b8f0470
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
> @@ -0,0 +1,1110 @@
[ ... ]
> +static void dcp_dsi_power_control_work(struct work_struct *work)
> +{
> + struct dcp_dsi_bridge *dsi_bridge =
> + container_of(work, struct dcp_dsi_bridge, power_control_work);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> +
> + dcp_hfi_display_power_control_ack(kms->hfi,
> + dsi_bridge->session_id,
> + dsi_bridge->power_packet_id);
> +
> + complete(&dsi_bridge->power_done);
> +}
> +
> +static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
> + u32 power_mask, bool enable,
> + u32 packet_id)
> +{
> + struct dcp_dpu_kms *kms = priv;
> + struct dcp_dsi_bridge *dsi_bridge = NULL;
> + int idx;
> +
> + for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
> + struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
> +
> + if (b && b->session_id == display_id) {
> + dsi_bridge = b;
> + break;
> + }
> + }
> +
> + if (!dsi_bridge) {
> + dev_warn(kms->pm_dev,
> + "DSI: power_control for unknown display_id=%u\n", display_id);
> + return;
> + }
> +
> + dsi_bridge->power_packet_id = packet_id;
> + schedule_work(&dsi_bridge->power_control_work);
> +}
[Severity: Medium]
Are dsi_bridge->session_id and dsi_bridge->power_packet_id synchronized and
matched safely between dcp_dsi_power_control_cb() and
dcp_dsi_power_control_work()?
When dcp_hfi_dispatch_event() drops resp_drain_lock and invokes
dcp_dsi_power_control_cb(), the loop checks b->session_id == display_id
without checking b->session_open or holding session_lock. Since closed
bridges have session_id == 0, an unopened bridge matches when
display_id == 0, whereas a temporary session opened in
dcp_dsi_host_transfer() stores its session ID in a local variable and
leaves dsi_bridge->session_id == 0, failing to match a non-zero
display_id.
Furthermore, dcp_dsi_power_control_work() reads dsi_bridge->session_id and
dsi_bridge->power_packet_id without locking while
dcp_dsi_bridge_atomic_post_disable(), dcp_dsi_bridge_atomic_enable(), and
dcp_dsi_bridge_prepare_session() reset dsi_bridge->session_id = 0 without
flushing power_control_work. And in dcp_dsi_bridge_cleanup(),
dcp_hfi_clear_power_control_cb() does not wait for an in-flight callback
that already dropped resp_drain_lock, allowing
schedule_work(&dsi_bridge->power_control_work) to run after
cancel_work_sync().
[ ... ]
> +static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
> + struct mipi_dsi_device *dsi)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
> + struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
> + int ret;
[ ... ]
> + if (dsi->dsc) {
> + struct drm_dsc_config *dsc = dsi->dsc;
> + u32 chroma_format = 0;
> + u32 color_space = 0;
> +
> + if (dsc->native_422)
> + chroma_format = 1;
> + else if (dsc->native_420)
> + chroma_format = 2;
> + if (dsc->native_422 || dsc->native_420)
> + color_space = 1;
> +
> + cfg->dsc_enabled = true;
> + cfg->dsc_version_major = dsc->dsc_version_major;
> + cfg->dsc_version_minor = dsc->dsc_version_minor;
> + cfg->dsc_scr_version = 0;
> + cfg->dsc_slice_height = dsc->slice_height;
> + cfg->dsc_slice_width = dsc->slice_width;
> + cfg->dsc_slices_per_pkt = 1;
> + cfg->dsc_bits_per_component = dsc->bits_per_component;
> + cfg->dsc_pps_delay_ms = 0;
> + cfg->dsc_bits_per_pixel = dsc->bits_per_pixel;
> + cfg->dsc_chroma_format = chroma_format;
> + cfg->dsc_color_space = color_space;
[Severity: High]
Are chroma_format and color_space off by one here?
In drivers/gpu/drm/msm/dcp/hfi/hfi_defs_panel.h, the HFI protocol enums
packed into HFI_PROPERTY_PANEL_COMPRESSION_DATA by dcp_hfi_panel_init() are
1-based:
enum hfi_panel_chroma_format_type {
HFI_PANEL_CHROMA_FORMAT_444 = 1,
HFI_PANEL_CHROMA_FORMAT_422 = 2,
HFI_PANEL_CHROMA_FORMAT_420 = 3,
};
enum hfi_panel_color_space_type {
HFI_PANEL_COLOR_SPACE_RGB = 1,
HFI_PANEL_COLOR_SPACE_YUV = 2,
};
Should dcp_dsi_host_attach() use the HFI_PANEL_CHROMA_FORMAT_* and
HFI_PANEL_COLOR_SPACE_* constants instead of 0-based values?
[ ... ]
> +static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
> + const struct mipi_dsi_msg *msg)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + u32 session_id;
> + bool temporary_session = false;
> + ssize_t ret;
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (!dsi_bridge->dcs_tx_buf)
> + return -ENODEV;
> +
> + /* Serialize DCS transfers */
> + guard(mutex)(&dsi_bridge->dcs_lock);
> +
> + if (dsi_bridge->session_open) {
> + session_id = dsi_bridge->session_id;
> + } else {
> + /*
> + * mipi_dsi_host_ops::transfer() may be called while the host is
> + * disabled. Open a transaction-only DCP session in that case;
> + * atomic_pre_enable() will create the full display session later.
> + */
> + ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
> + dsi_bridge->fw_display_id,
> + &session_id);
> + if (ret)
> + return ret;
> +
> + temporary_session = true;
> + }
> +
> + ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
> + dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova,
> + dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova, 0,
> + dsi_bridge->hfi_panel_config.ctrl_indices[0]);
[Severity: Medium]
Does dcp_dsi_host_transfer() return the wrong byte count for DSI read
transfers and reject 0-parameter generic transfers?
In drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, dcp_hfi_dcs_transfer() ends
with:
if (msg->rx_len > 0)
memcpy(msg->rx_buf, rx_buf, msg->rx_len);
return msg->tx_len;
and rejects msg->tx_len == 0 at entry with -EINVAL.
For mipi_dsi_host_ops::transfer(), callers such as mipi_dsi_dcs_read() and
mipi_dsi_generic_read() expect the number of received bytes (msg->rx_len)
on read transfers, and 0-parameter generic transfers
(MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM and
MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM) have msg->tx_len == 0.
> + if (temporary_session) {
> + int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
> +
> + if (ret >= 0 && close_ret)
> + ret = close_ret;
> + }
[Severity: Medium]
Can calling dcp_hfi_display_close() here (and on the err_close path in
dcp_dsi_bridge_atomic_enable()) leak the firmware display session if
DISPLAY_DISABLE or DISPLAY_POST_DISABLE fails?
In drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c, dcp_hfi_display_close() returns
early before sending HFI_COMMAND_DISPLAY_CLOSE if either
dcp_hfi_display_disable() or dcp_hfi_display_post_disable() returns an
error:
int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)
{
int ret;
ret = dcp_hfi_display_disable(hfi, session_id);
if (ret) {
dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
return ret;
}
ret = dcp_hfi_display_post_disable(hfi, session_id);
if (ret) {
dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
return ret;
}
When closing a temporary session in dcp_dsi_host_transfer() (which only
called dcp_hfi_display_enable() and never called
dcp_hfi_display_post_enable()) or rolling back a partially enabled session
in dcp_dsi_bridge_atomic_enable(), an error from DISPLAY_DISABLE or
DISPLAY_POST_DISABLE skips HFI_COMMAND_DISPLAY_CLOSE while the driver drops
session_id and marks session_open = false. In contrast,
dcp_dsi_bridge_atomic_disable() and dcp_dsi_bridge_atomic_post_disable()
still send HFI_COMMAND_DISPLAY_CLOSE even if the preceding disable commands
warn.
[ ... ]
> +static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
> + struct dcp_dsi_hfi_panel_config *cfg,
> + const struct drm_display_mode *mode)
> +{
[ ... ]
> + for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
> + cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
> + cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
> + cfg->dsc_rc_bpg_offsets[i] = dsc->rc_range_params[i].range_bpg_offset;
> + }
> +
> + return 0;
> +}
[Severity: Medium]
Should dcp_dsi_bridge_setup_dsc() also set cfg->dsc_rc_override = true?
dcp_dsi_host_attach() initializes cfg->dsc_rc_override = false, and
dcp_dsi_bridge_prepare_session() passes a stack-local copy of cfg to
dcp_dsi_bridge_setup_dsc() and then to dcp_hfi_panel_init(). In
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c, dcp_hfi_panel_init() only packs
HFI_PROPERTY_PANEL_COMPRESSION_RC_OVERRIDE when
(cfg->dsc_enabled && cfg->dsc_rc_override) is true, so the rate-control
parameters populated in cfg->dsc_rc_min_qp, cfg->dsc_rc_max_qp, and
cfg->dsc_rc_bpg_offsets are never sent to the firmware.
> +
> +int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
> + const struct drm_display_mode *mode)
> +{
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + struct dcp_dsi_bridge_state *state = dcp_dsi_bridge_get_state(dsi_bridge);
> + struct dcp_dsi_hfi_panel_config cfg;
> + int ret;
> + u32 opened_session_id;
> +
> + if (!mode)
> + return -ENODEV;
> +
> + guard(mutex)(&dsi_bridge->session_lock);
> + if (dsi_bridge->session_open)
> + return 0;
> +
> + dsi_bridge->session_id = dsi_bridge->fw_display_id;
> +
> + if (!dsi_bridge->dsi_device)
> + return -ENODEV;
[ ... ]
> + ret = dcp_hfi_display_enable(kms->hfi,
> + dsi_bridge->intf_idx,
> + dsi_bridge->fw_display_id,
> + &opened_session_id);
> + if (ret) {
> + drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + dsi_bridge->session_id = opened_session_id;
> + dsi_bridge->session_open = true;
> + dcp_dsi_bridge_publish_session(dsi_bridge);
> +
> + drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> + return 0;
> +
> +err_close:
> + dcp_hfi_clear_power_control_cb(kms->hfi, kms);
> + dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
> + dsi_bridge->session_id = 0;
> + dsi_bridge->session_open = false;
> + dcp_dsi_bridge_clear_session(dsi_bridge);
> +
> + return ret;
> +}
[Severity: Medium]
There appear to be several issues with error handling and cleanup in
dcp_dsi_bridge_prepare_session():
1. It mixes guard(mutex)(&dsi_bridge->session_lock) with goto err_close,
contrary to the cleanup.h guideline not to mix goto error paths and
scope-based cleanup helpers in the same function.
2. If !dsi_bridge->dsi_device is true, returning -ENODEV early leaves
dsi_bridge->session_id set to dsi_bridge->fw_display_id while
session_open is false.
3. Every goto err_close path triggers before dcp_hfi_display_enable() has
succeeded (even if dcp_dsi_bridge_setup_dsc() fails in software before
any HFI command is sent), yet err_close calls dcp_hfi_display_close() to
send DISPLAY_DISABLE, DISPLAY_POST_DISABLE, and DISPLAY_CLOSE for a
session that was never enabled.
4. Calling dcp_hfi_clear_power_control_cb(kms->hfi, kms) in err_close clears
the single shared power control callback on kms->hfi for all DSI bridges
in kms->dsi_bridge[].
> +
> +static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + int ret;
> +
> + ret = dsi_bridge->session_open ? 0 : -ENODEV;
> + dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
> + if (ret)
> + drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> +}
> +
> +static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
> + struct drm_atomic_commit *state)
> +{
> + struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
> + struct dcp_dpu_kms *kms = dsi_bridge->kms;
> + bool scan_start_registered = false;
> + bool scan_complete_registered = false;
> + bool autorefresh_enabled = false;
> + int ret;
> +
> + drm_dbg_driver(kms->dev,
> + "DSI%u: bridge atomic_enable (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> +
> + if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
> + return;
> + if (!dsi_bridge->session_open)
> + return;
> +
> + ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
[ ... ]
> +err_close:
> + if (autorefresh_enabled)
> + dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
> + false, 0);
> + if (scan_complete_registered)
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_COMPLETE);
> + if (scan_start_registered)
> + dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
> + HFI_EVENT_FRAME_SCAN_START);
> + drm_err(bridge->dev,
> + "DSI%u: bridge enable failed; session closed\n",
> + dsi_bridge->intf_idx);
> + dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
> + {
> + guard(mutex)(&dsi_bridge->session_lock);
> + dsi_bridge->session_id = 0;
> + dsi_bridge->session_open = false;
> + }
> + dcp_dsi_bridge_clear_session(dsi_bridge);
> +}
[Severity: Medium]
Should dcp_dsi_bridge_atomic_enable() (and dcp_dsi_bridge_atomic_pre_enable())
hold dsi_bridge->session_lock across the session operations rather than only
around clearing session_id and session_open at the end of err_close?
In addition to mixing goto err_close with guard(mutex), reading
dsi_bridge->session_open and dsi_bridge->session_id and calling
dcp_hfi_display_close() without holding session_lock allows a concurrent
dcp_dsi_host_transfer() call to observe session_open == true and send DCS
commands on a session ID that is actively being closed or has already been
closed in firmware.
[ ... ]
> +struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
> + struct device_node *np,
> + struct device_node *dcp_np,
> + u32 dcp_port, u32 intf_idx,
> + u32 fw_display_id)
> +{
> + struct dcp_dsi_bridge *dsi_bridge;
> + struct dcp_dsi_hfi_panel_config *cfg;
> + struct device *dev = kms->pm_dev;
> + struct device *dma_dev = kms->hfi->dma_dev;
> + int ret;
> +
> + dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
> + bridge, &dcp_dsi_bridge_funcs);
> + if (IS_ERR(dsi_bridge))
> + return ERR_CAST(dsi_bridge);
> +
> + dsi_bridge->dcp_np = of_node_get(dcp_np);
[ ... ]
> + device_initialize(&dsi_bridge->dsi_host_dev);
> + dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
> + dsi_bridge->dsi_host_dev.parent = dev;
> + dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
> + dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
> + dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
> + dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
> +
> + ret = device_add(&dsi_bridge->dsi_host_dev);
[Severity: Medium]
Won't dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np) cause a device
name collision in device_add() when two DSI bridges are present?
The %pOFn specifier strips the @unit-address suffix from the node name, so
both dsi@0 and dsi@1 receive the name "dsi" under the same parent device
(kms->pm_dev), causing the second device_add() to fail with -EEXIST.
[ ... ]
> +err_unwind_dma:
> + if (dsi_bridge->dcs_rx_buf) {
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + dsi_bridge->dcs_rx_buf,
> + dsi_bridge->dcs_rx_iova);
> + }
> + if (dsi_bridge->dcs_tx_buf) {
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf,
> + dsi_bridge->dcs_tx_iova);
> + }
> + cancel_work_sync(&dsi_bridge->power_control_work);
> + mutex_destroy(&dsi_bridge->session_lock);
> + mutex_destroy(&dsi_bridge->dcs_lock);
> + return ERR_PTR(ret);
> +}
[Severity: Medium]
Does the err_unwind_dma path in dcp_dsi_bridge_init() leak the
dsi_bridge->dcp_np device_node reference acquired via of_node_get(dcp_np) if
dma_alloc_coherent(), device_add(), or mipi_dsi_host_register() fails?
[ ... ]
> +void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
> +
> + dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
> + cancel_work_sync(&dsi_bridge->power_control_work);
> +
> + drm_bridge_remove(&dsi_bridge->bridge);
> + of_node_put(dsi_bridge->bridge.of_node);
> + of_node_put(dsi_bridge->dcp_np);
> +
> + mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
> + device_del(&dsi_bridge->dsi_host_dev);
> + put_device(&dsi_bridge->dsi_host_dev);
> +
> + dcp_dsi_bridge_unmap_buffers(dsi_bridge);
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
> +
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
> +
> + mutex_destroy(&dsi_bridge->session_lock);
> + mutex_destroy(&dsi_bridge->dcs_lock);
> +}
[Severity: High]
Can calling mipi_dsi_host_unregister() from dcp_dsi_bridge_cleanup()
deadlock on component_mutex and trigger use-after-free during teardown?
1. When a DSI device is attached, mipi_dsi_host_unregister() removes the
child device and invokes:
mipi_dsi_remove_device_fn()
-> mipi_dsi_detach()
-> dcp_dsi_host_detach()
-> component_del(&dsi_bridge->dsi_host_dev, &dcp_dsi_component_ops)
component_del() acquires the global component_mutex. When
dcp_dsi_bridge_cleanup() is called from aggregate device bringup failure
or aggregate device takedown (for example via component_master_del() /
take_down_aggregate_device() -> msm_drm_unbind() -> msm_drm_uninit() ->
dcp_dpu_kms_destroy()), component_mutex is already held by the caller,
causing a recursive deadlock.
2. struct dcp_dsi_bridge is allocated with devm_drm_bridge_alloc() on
kms->pm_dev and embeds struct device dsi_host_dev, which is refcounted
independently. Calling device_del() and put_device() on dsi_host_dev
during kms->destroy() happens before component_unbind_all() finishes
using component->dev (&dsi_bridge->dsi_host_dev). In addition,
dcs_tx_buf and dcs_rx_buf are freed without holding session_lock/dcs_lock
or clearing dsi_bridge->dcs_tx_buf to NULL, and session_lock and dcs_lock
are destroyed while the bridge object is still alive.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=15
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS
2026-10-26 6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
@ 2026-10-26 13:03 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 13:03 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] drm/msm/dcp: dcp_crtc_atomic_begin() opens session on non-modeset commits
--
commit 9b8003ef1934260f1a1ec514fc980b7068b0a204
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Integrate the DSI bridge with KMS
This commit connects the DCP DSI bridge to the DCP KMS pipeline by adding
helpers to prepare the CRTC display session during atomic_begin and check
whether planes require dirtyfb updates during atomic_check.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> index ec4f74c2920f8..173a07df71c5f 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> @@ -147,6 +147,12 @@ static void dcp_crtc_atomic_begin(struct drm_crtc *crtc,
> return;
> }
>
> + ret = dcp_dpu_kms_prepare_crtc_session(dcp_kms, crtc);
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
[Severity: Medium]
Should dcp_dpu_kms_prepare_crtc_session() only open a new session when
drm_atomic_crtc_needs_modeset(crtc->state) is true, or can calling it
unconditionally in dcp_crtc_atomic_begin() leave the session partially
initialized on non-modeset commits?
In msm_atomic_commit_tail(), drm_atomic_helper_commit_planes() (which calls
dcp_crtc_atomic_begin()) runs before drm_atomic_helper_commit_modeset_enables()
(which calls dcp_crtc_atomic_enable(), dcp_dsi_bridge_atomic_pre_enable(), and
dcp_dsi_bridge_atomic_enable(), and only runs when
drm_atomic_crtc_needs_modeset(crtc->state) is true).
If an initial modeset commit fails in dcp_dsi_bridge_prepare_session() or in
dcp_dsi_bridge_atomic_enable():
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c:dcp_dsi_bridge_atomic_enable() {
...
ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
if (ret) {
drm_err(bridge->dev,
"DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
dsi_bridge->intf_idx, ret);
goto err_close;
}
...
err_close:
dcp_hfi_clear_power_control_cb(kms->hfi, kms);
dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
dsi_bridge->session_id = 0;
dsi_bridge->session_open = false;
dcp_dsi_bridge_clear_session(dsi_bridge);
}
the session is closed and dsi_bridge->session_open is reset to false, while
crtc->state->active remains true.
On the next non-modeset plane update or pageflip where
drm_atomic_crtc_needs_modeset(crtc->state) is false,
dcp_crtc_atomic_begin() -> dcp_dpu_kms_prepare_crtc_session() ->
dcp_dsi_bridge_prepare_session() sees !dsi_bridge->session_open, opens a new
HFI session, sets dsi_bridge->session_open = true, and publishes session_id to
the CRTC:
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c:dcp_dsi_bridge_prepare_session() {
...
dsi_bridge->session_id = opened_session_id;
dsi_bridge->session_open = true;
dcp_dsi_bridge_publish_session(dsi_bridge);
...
}
Because drm_atomic_helper_commit_modeset_enables() skips the CRTC and bridge
enable callbacks on a non-modeset commit, dcp_crtc_atomic_enable() and
dcp_dsi_bridge_atomic_enable() never run, skipping HFI_MODE_DPMS_ON,
drm_crtc_vblank_on(), panel bridge enable, HFI_COMMAND_DISPLAY_POST_ENABLE,
and HFI_EVENT_FRAME_SCAN_COMPLETE registration.
When dcp_crtc_commit_kickoff() then submits frame triggers for that session,
dcp_crtc_wait_for_commit_done() will wait on frame_done_wq for an event that
was never registered and time out after 500ms on every subsequent commit:
drivers/gpu/drm/msm/dcp/dcp_crtc.c:dcp_crtc_wait_for_commit_done() {
...
if (!wait_event_timeout(dcp_crtc->frame_done_wq,
atomic_read(&dcp_crtc->pending_kickoff_cnt) == 0,
timeout)) {
drm_err(crtc->dev,
"frame done timeout for CRTC%u\n",
dcp_crtc->display_id);
atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
}
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=16
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline
2026-10-26 6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
@ 2026-10-26 13:24 ` sashiko-bot
0 siblings, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 13:24 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] drm/msm/dcp: dcp_dpu_kms_cleanup_bridges() called under component_mutex
- [High] drm/msm/dcp: missing remote port check and sequential bridge lookup
- [High] drm/msm/dcp: race and runtime PM leak in dcp_dpu_kms_enable_commit()
- [High] drm/msm/dcp: unbalanced msm_kms and runtime PM teardown in dcp_dpu_kms_destroy()
- [High] drm/msm/dcp: dangling CRTC and drm_device pointers in dcp_dpu_kms_destroy()
- [Medium] drm/msm/dcp: invalid possible_crtcs mask when max_crtc_count is clamped
--
commit 5fc21d18f27dcbb7ac8d64f95bc52703e58d9984
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Register DCP display pipeline
This commit registers the DCP display pipeline using firmware capabilities
and the device-tree display graph, integrating runtime PM and IOMMU setup and
creating the DRM planes, CRTCs, encoders, bridges, and connectors.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> index 61a4d85653594..8a73a99b2c763 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
[ ... ]
> @@ -59,3 +78,609 @@ bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dcp_kms,
>
> return false;
> }
> +
> +void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dcp_kms)
> +{
> + u32 i;
> +
> + for (i = 0; i < ARRAY_SIZE(dcp_kms->dsi_bridge); i++) {
> + if (!dcp_kms->dsi_bridge[i])
> + continue;
> +
> + dcp_dsi_bridge_cleanup(dcp_kms->dsi_bridge[i]);
> + dcp_kms->dsi_bridge[i] = NULL;
> + }
> +}
[Severity: High]
Will calling dcp_dpu_kms_cleanup_bridges() from dcp_dpu_kms_setup_bridges(),
dcp_dpu_setup_pipeline(), or dcp_dpu_kms_destroy() deadlock on
component_mutex and break deferred probe retries?
Before registering the aggregate component master, dcp_component_register()
calls dcp_dpu_kms_init_dsi_bridges() -> dcp_dsi_bridge_init(), which
registers dsi_host_dev and mipi_dsi_host and adds each dsi_np to the
component match list. When the DSI panel attaches, dcp_dsi_host_attach()
calls component_add(&dsi_bridge->dsi_host_dev, &dcp_dsi_component_ops).
Once all components are added, drivers/base/component.c holds the non-
recursive component_mutex across both aggregate bind and unbind:
try_to_bring_up_aggregate_device() [holds component_mutex]
-> msm_drm_bind()
-> msm_drm_init()
-> msm_drm_kms_init()
-> dcp_dpu_kms_hw_init()
-> dcp_dpu_drm_obj_init()
-> dcp_dpu_setup_pipeline()
-> dcp_dpu_kms_setup_bridges()
take_down_aggregate_device() [holds component_mutex]
-> msm_drm_unbind()
-> msm_drm_uninit()
-> msm_drm_kms_uninit()
-> dcp_dpu_kms_destroy()
When dcp_dpu_kms_cleanup_bridges() runs from any of those paths, it calls:
dcp_dsi_bridge_cleanup()
-> mipi_dsi_host_unregister()
-> mipi_dsi_remove_device_fn()
-> mipi_dsi_detach()
-> dcp_dsi_host_detach()
-> component_del(&dsi_bridge->dsi_host_dev, ...)
which tries to acquire component_mutex while it is already held by the
calling thread.
In addition, in msm_drm_uninit(), msm_drm_kms_uninit() runs before
component_unbind_all() and drm_dev_put(), so dcp_dpu_kms_destroy() tears down
the bridges and deletes dsi_host_dev while it is still bound as a component
and attached to encoder->bridge_chain. Destroying and clearing
dcp_kms->dsi_bridge[] on error or -EPROBE_DEFER in dcp_dpu_kms_setup_bridges()
or dcp_dpu_setup_pipeline() also prevents any subsequent deferred probe retry
from succeeding because dcp_dpu_kms_init_dsi_bridges() only runs once before
component_master_add_with_match().
[ ... ]
> +static void dcp_dpu_kms_enable_commit(struct msm_kms *kms)
> +{
> + struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
> + int ret;
> +
> + ret = pm_runtime_resume_and_get(dcp_kms->pm_dev);
> + if (ret < 0) {
> + dcp_kms->commit_pm_active = false;
> + drm_err_ratelimited(dcp_kms->dev,
> + "pm_runtime_resume_and_get failed: %d\n",
> + ret);
> + return;
> + }
> +
> + dcp_kms->commit_pm_active = true;
> +}
> +
> +static void dcp_dpu_kms_disable_commit(struct msm_kms *kms)
> +{
> + struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
> +
> + if (dcp_kms->commit_pm_active) {
> + pm_runtime_put_autosuspend(dcp_kms->pm_dev);
> + dcp_kms->commit_pm_active = false;
> + }
> +}
[Severity: High]
Can overlapping commits race on dcp_kms->commit_pm_active and leak a runtime
PM reference on dcp_kms->pm_dev?
msm_atomic_commit_tail() calls kms->funcs->enable_commit(kms) before
lock_crtcs(kms, crtc_mask) and calls kms->funcs->disable_commit(kms) after
unlock_crtcs(kms, crtc_mask), while msm_atomic_async_commit() also calls
enable_commit() and disable_commit() from per-CRTC worker threads, and
commits on separate CRTCs execute concurrently.
If two commits overlap between dcp_dpu_kms_enable_commit() and
dcp_dpu_kms_disable_commit():
1. Both commits call pm_runtime_resume_and_get(dcp_kms->pm_dev) and set the
single shared boolean dcp_kms->commit_pm_active = true.
2. When the first commit finishes and calls dcp_dpu_kms_disable_commit(), it
calls pm_runtime_put_autosuspend(dcp_kms->pm_dev) once and sets
dcp_kms->commit_pm_active = false.
3. The second in-flight commit then sees !dcp_kms->commit_pm_active in
dcp_crtc_atomic_begin() (failing the commit with cstate->hfi_error = -EIO),
and when it reaches dcp_dpu_kms_disable_commit() it sees
dcp_kms->commit_pm_active == false and skips
pm_runtime_put_autosuspend(dcp_kms->pm_dev), leaking its runtime PM usage
count.
[ ... ]
> +static void dcp_dpu_kms_destroy(struct msm_kms *kms)
> +{
> + struct dcp_dpu_kms *dcp_kms = to_dcp_dpu_kms(kms);
> +
> + dcp_dpu_kms_cleanup_bridges(dcp_kms);
> +
> + if (kms->vm) {
> + struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
> +
> + if (msm_vm->mmu && msm_vm->mmu->funcs->detach) {
> + drm_dbg_driver(dcp_kms->dev,
> + "detaching IOMMU domain from dcp-dpus\n");
> + msm_vm->mmu->funcs->detach(msm_vm->mmu);
> + }
> +
> + drm_gpuvm_put(kms->vm);
> + kms->vm = NULL;
> + }
> +
> + if (dcp_kms->rpm_enabled) {
> + pm_runtime_disable(dcp_kms->pm_dev);
> + dcp_kms->rpm_enabled = false;
> + }
> +
> + msm_kms_destroy(&dcp_kms->base);
> +}
[Severity: High]
Is the teardown in dcp_dpu_kms_destroy() balanced with dcp_kms_init() across
deferred probe or component rebind?
In msm_drm_init(), msm_drm_kms_init() calls priv->kms_init(ddev)
(dcp_kms_init()) and kms->funcs->hw_init(kms) (dcp_dpu_kms_hw_init()). If
msm_drm_kms_init() fails (for instance with -EPROBE_DEFER when a panel is not
ready in dcp_dpu_kms_setup_bridges()) or when the aggregate device unbinds,
msm_drm_uninit() calls msm_drm_kms_uninit(), which flushes kms->wq and calls
dcp_dpu_kms_destroy().
However, msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs) and
pm_runtime_enable(&drv->dcp_subsystem_dev->dev) are only called once during
virtio probe (in dcp_virtio_probe() and dcp_subsystem_dev_init()), not in
dcp_kms_init(). When dcp_dpu_kms_destroy() runs:
- pm_runtime_disable(dcp_kms->pm_dev) disables runtime PM on pm_dev (and on
driver removal, dcp_subsystem_dev_deinit() calls pm_runtime_disable() a
second time on the same device).
- msm_kms_destroy(&dcp_kms->base) destroys kms->wq and kms->pending_timers[],
leaving kms->wq as a dangling pointer that will be flushed and destroyed
again on a subsequent bind attempt or uninit.
[Severity: High]
Does dcp_dpu_kms_destroy() (and the error path in dcp_dpu_setup_pipeline())
leave dangling CRTC and drm_device pointers reachable from HFI event
callbacks?
dcp_kms_init() sets dcp_kms->dev = ddev and drv->kms = dcp_kms, and
dcp_dpu_setup_pipeline() populates dcp_kms->crtcs[] with CRTCs allocated via
drmm_kzalloc(ddev, ...) in dcp_crtc_init() and increments dcp_kms->num_crtcs.
When msm_drm_uninit() runs on init failure, component unbind, or during
dcp_virtio_remove() -> dcp_component_unregister() before
dcp_virtio_hfi_cleanup() stops the VirtIO queues, drm_dev_put(ddev) frees
ddev and all drmm-managed dcp_crtc instances while dcp_kms and drv remain
allocated. Because neither dcp_dpu_kms_destroy() nor
dcp_dpu_setup_pipeline()'s error path clears dcp_kms->num_crtcs,
dcp_kms->crtcs[], dcp_kms->dev, or drv->kms, an incoming
HFI_COMMAND_DISPLAY_EVENT_VSYNC or
HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE message in
dcp_hfi_resp_callback() will invoke dcp_crtc_hfi_vblank_cb() or
dcp_crtc_hfi_frame_done_cb() and dereference the freed dcp_crtc or drm_device.
[ ... ]
> +int dcp_dpu_kms_init_dsi_bridges(struct dcp_dpu_kms *dcp_kms,
> + struct component_match **matchptr)
> +{
> + struct device_node *dpu_np = dcp_kms->dpus_np;
> + struct device_node *ep_np, *dsi_np, *child_np;
> + struct of_endpoint ep;
> + int ret;
> +
> + for_each_of_graph_port(dpu_np, port_np) {
> + u32 dsi_idx, fw_display_id;
> + struct drm_bridge *bridge;
> +
> + ep_np = of_get_next_child(port_np, NULL);
> + if (!ep_np)
> + continue;
> +
> + ret = of_graph_parse_endpoint(ep_np, &ep);
> + of_node_put(ep_np);
> + if (ret)
> + continue;
> +
> + dsi_idx = ep.port;
> + if (dsi_idx >= ARRAY_SIZE(dcp_kms->dsi_bridge))
> + return -E2BIG;
> +
> + if (dcp_kms->dsi_bridge[dsi_idx])
> + continue;
> +
> + dsi_np = NULL;
> + for_each_available_child_of_node(dpu_np, child_np) {
> + u32 reg;
> +
> + if (!of_node_name_prefix(child_np, "dsi") ||
> + of_property_read_u32(child_np, "reg", ®) ||
> + reg != dsi_idx)
> + continue;
> +
> + dsi_np = of_node_get(child_np);
> + break;
> + }
> + of_node_put(child_np);
> + if (!dsi_np)
> + return -ENODEV;
> +
> + fw_display_id =
> + dcp_dpu_kms_resolve_fw_display_id(dcp_kms, dsi_idx);
> + if (fw_display_id == U32_MAX) {
> + of_node_put(dsi_np);
> + return -EINVAL;
> + }
> +
> + bridge = dcp_dsi_bridge_init(dcp_kms, dsi_np, dpu_np,
> + ep.port, dsi_idx, fw_display_id);
> + if (IS_ERR(bridge)) {
> + ret = PTR_ERR(bridge);
> + of_node_put(dsi_np);
> + return ret;
> + }
> +
> + dcp_kms->dsi_bridge[dsi_idx] = to_dcp_dsi_bridge(bridge);
> + drm_of_component_match_add(dcp_kms->pm_dev, matchptr,
> + component_compare_of, dsi_np);
> + of_node_put(dsi_np);
> + }
> +
> + return 0;
> +}
[ ... ]
> +static int dcp_dpu_setup_pipeline(struct dcp_dpu_kms *dcp_kms,
> + struct device_node *dpu_np)
> +{
[ ... ]
> + dcp_kms->num_crtcs = 0;
> + for (i = 0; i < max_crtc_count; i++) {
> + u32 display_id;
> +
> + if (!dcp_kms->dsi_bridge[i]) {
> + drm_err(dev, "missing DSI bridge for CRTC%u\n", i);
> + ret = -ENODEV;
> + goto err_cleanup_bridges;
> + }
> +
> + display_id = dcp_kms->dsi_bridge[i]->fw_display_id;
[Severity: High]
What happens if a DT graph port has no available remote panel or if port@0 is
omitted or disabled while port@1 is active?
First, dcp_dpu_kms_setup_bridges() checks of_graph_get_remote_port_parent()
and of_device_is_available(remote_np) and skips ports without an available
panel, but dcp_dpu_kms_init_dsi_bridges() does not perform this check before
creating dcp_kms->dsi_bridge[dsi_idx] and adding dsi_np to matchptr via
drm_of_component_match_add(). Because component_add() is only called from
dcp_dsi_host_attach() when a panel driver attaches to the DSI host, any
unpopulated or disabled panel endpoint in DT will prevent the component
master from ever binding.
Second, both dcp_dpu_kms_init_dsi_bridges() and dcp_dpu_kms_setup_bridges()
index dcp_kms->dsi_bridge[] by ep.port, whereas dcp_dpu_setup_pipeline()
loops from i = 0 to max_crtc_count - 1 and looks up dcp_kms->dsi_bridge[i]
using the sequential CRTC index i. If port@0 is absent or disabled and only
port@1 is active, dcp_kms->dsi_bridge[1] holds the active bridge while
max_crtc_count is 1, so dcp_dpu_setup_pipeline() checks
dcp_kms->dsi_bridge[0] and either fails with -ENODEV or binds CRTC 0 to the
wrong bridge's fw_display_id.
> + if (dcp_dpu_kms_get_crtc(dcp_kms, display_id)) {
> + drm_err(dev, "duplicate firmware display ID %u\n",
> + display_id);
> + ret = -EINVAL;
> + goto err_cleanup_bridges;
> + }
> +
> + crtc = dcp_crtc_init(dcp_kms, primary_planes[i],
> + display_id);
> + if (IS_ERR(crtc)) {
> + ret = PTR_ERR(crtc);
> + drm_err(dev, "CRTC init failed: %d\n", ret);
> + goto err_cleanup_bridges;
> + }
> + dcp_kms->crtcs[dcp_kms->num_crtcs].crtc = crtc;
> + dcp_kms->crtcs[dcp_kms->num_crtcs++].display_id = display_id;
> + }
> +
> + return 0;
> +
> +err_cleanup_bridges:
> + dcp_dpu_kms_cleanup_bridges(dcp_kms);
> + return ret;
> +}
> +
> +int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dcp_kms)
> +{
> + struct drm_device *dev = dcp_kms->dev;
> + struct device_node *dpus_np = dcp_kms->dpus_np;
> + struct drm_encoder *encoder;
> + int enc_idx = 0;
> + int ret;
> +
> + if (!dpus_np) {
> + drm_err(dev, "no DCP subsystem node — cannot create DRM objects\n");
> + return -ENODEV;
> + }
> +
> + ret = dcp_dpu_setup_pipeline(dcp_kms, dpus_np);
> + if (ret)
> + return dev_err_probe(dev->dev, ret, "pipeline setup failed\n");
> +
> + /* v1 - Encoders map one-to-one to CRTCs; cloning is unsupported. */
> + drm_for_each_encoder(encoder, dev) {
> + encoder->possible_crtcs = BIT(enc_idx);
> + encoder->possible_clones = 0;
> + enc_idx++;
> + }
[Severity: Medium]
Can plane and encoder possible_crtcs masks reference non-existent CRTCs when
max_crtc_count is clamped in dcp_dpu_setup_pipeline()?
In dcp_dpu_setup_pipeline(), dcp_dpu_kms_setup_bridges() creates an encoder
for each active DSI port before CRTC and plane limits are applied:
max_crtc_count = min(max_crtc_count, DCP_DPU_MAX_CRTCS);
max_crtc_count = min_t(int, max_crtc_count,
dcp_kms->caps->num_displays);
max_crtc_count = min_t(int, max_crtc_count,
ARRAY_SIZE(dcp_kms->dsi_bridge));
Then, during plane creation, possible_crtcs is computed from max_crtc_count
before max_crtc_count is clamped again to min(max_crtc_count, primary_idx).
Finally, in dcp_dpu_drm_obj_init(), every created encoder is assigned
encoder->possible_crtcs = BIT(enc_idx).
If caps->num_displays or total_planes clamps the number of created CRTCs
below the number of encoders, planes and encoders will have possible_crtcs
bits set for CRTC indices that do not exist, triggering a WARN() in
validate_encoder_possible_crtcs() during drm_mode_config_validate().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=17
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper
2026-10-26 6:21 ` [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper Jayant Shekhar
2026-10-26 10:07 ` Dmitry Baryshkov
@ 2026-10-26 14:05 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 14:05 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] drm/msm/dcp: use-after-free of vdev due to devm_kzalloc() and empty release
- [High] drm/msm/dcp: broken virtqueue PM sequencing in suspend and resume
- [Medium] drm/msm/dcp: unbalanced IRQ wake in dcp_virtio_transport_resume() on error
- [Low] drm/msm/dcp: build failure in dcp_virtio_transport_suspend() when !PM_SLEEP
--
commit 8e04d90ffae5a36ffd1db1c49707f1bc447c570d
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add Qualcomm VirtIO transport wrapper
This commit adds the Qualcomm-specific VirtIO MMIO transport wrapper for DCP
firmware, handling IPCC power notifications, virtqueue setup, interrupt
handling, and system suspend/resume sequencing.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
> new file mode 100644
> index 0000000000000..02c0e2a9fafe9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c
[ ... ]
> +struct dcp_virtio_transport {
> + struct virtio_device vdev;
> + struct platform_device *pdev;
> +
> + void __iomem *base;
> + unsigned long version;
> +
> + bool wake_irq_enabled;
> + struct dcp_ipcc ipcc;
> +};
[ ... ]
> +static void dcp_virtio_release_dev(struct device *dev)
> +{
> +}
> +
> +struct dcp_ipcc *dcp_virtio_get_ipcc(struct virtio_device *vdev)
> +{
> + return &to_dcp_virtio_transport(vdev)->ipcc;
> +}
> +
> +static int dcp_virtio_transport_probe(struct platform_device *pdev)
> +{
> + struct dcp_virtio_transport *transport;
> + unsigned long magic;
> + int ret;
> +
> + transport = devm_kzalloc(&pdev->dev, sizeof(*transport), GFP_KERNEL);
> + if (!transport)
> + return -ENOMEM;
> +
> + transport->pdev = pdev;
> + transport->vdev.dev.parent = &pdev->dev;
> + transport->vdev.dev.release = dcp_virtio_release_dev;
> + transport->vdev.config = &dcp_virtio_config_ops;
> + platform_set_drvdata(pdev, transport);
[ ... ]
> + ret = register_virtio_device(&transport->vdev);
> + if (ret) {
> + put_device(&transport->vdev.dev);
> + dcp_ipcc_deinit(&transport->ipcc);
> + platform_set_drvdata(pdev, NULL);
> + return ret;
> + }
> +
> + return 0;
> +
> +err_node:
> +err_ipcc:
> + dcp_ipcc_deinit(&transport->ipcc);
> +err_free:
> + platform_set_drvdata(pdev, NULL);
> + return ret;
> +}
> +
> +static void dcp_virtio_transport_remove(struct platform_device *pdev)
> +{
> + struct dcp_virtio_transport *transport = platform_get_drvdata(pdev);
> +
> + unregister_virtio_device(&transport->vdev);
> + platform_set_drvdata(pdev, NULL);
> + dcp_ipcc_deinit(&transport->ipcc);
> +}
[Severity: High]
struct dcp_virtio_transport embeds struct virtio_device vdev, which in turn
embeds a reference-counted struct device dev. In dcp_virtio_transport_probe(),
transport is allocated with devm_kzalloc(&pdev->dev, ...) and
transport->vdev.dev.release is set to the empty callback
dcp_virtio_release_dev().
Once register_virtio_device(&transport->vdev) calls
device_initialize(&dev->dev), the lifetime of transport->vdev.dev is governed
by its reference count rather than the parent platform device's devres
lifetime. When dcp_virtio_transport_remove() returns (or when
register_virtio_device() fails), devres_release_all(&pdev->dev) immediately
frees transport regardless of whether references to transport->vdev.dev are
still held.
Additionally, if transport is instead allocated with kzalloc() and freed in
dcp_virtio_release_dev() (as in drivers/virtio/virtio_mmio.c), calling
dcp_ipcc_deinit(&transport->ipcc) after put_device(&transport->vdev.dev) in
dcp_virtio_transport_probe() or after unregister_virtio_device(&transport->vdev)
in dcp_virtio_transport_remove() would access transport->ipcc after the final
reference drops and frees transport.
Can this cause a use-after-free of transport->vdev.dev and transport->ipcc?
> +
> +static int dcp_virtio_transport_suspend(struct device *dev)
> +{
> + struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = virtio_device_freeze(&transport->vdev);
> + if (ret)
> + return ret;
> +
> + ret = dcp_ipcc_enable_irq_wake(&transport->ipcc);
> + if (ret)
> + goto err_restore;
> +
> + ret = dcp_ipcc_send_power_notify(&transport->ipcc);
> + if (ret) {
> + dcp_ipcc_disable_irq_wake(&transport->ipcc);
> + goto err_restore;
> + }
> +
> + return 0;
> +
> +err_restore:
> + virtio_device_restore(&transport->vdev);
> + return ret;
> +}
> +
> +static int dcp_virtio_transport_resume(struct device *dev)
> +{
> + struct dcp_virtio_transport *transport = dev_get_drvdata(dev);
> + int ret;
> +
> + ret = dcp_ipcc_send_power_notify(&transport->ipcc);
> + if (ret)
> + return ret;
[Severity: Medium]
During system suspend, dcp_virtio_transport_suspend() enables IRQ wake on
transport->ipcc.power_notify_irq via dcp_ipcc_enable_irq_wake(&transport->ipcc).
During system resume, dcp_virtio_transport_resume() calls
dcp_ipcc_send_power_notify(&transport->ipcc) before calling
dcp_ipcc_disable_irq_wake(&transport->ipcc).
If dcp_ipcc_send_power_notify() fails or times out, does returning early here
leave wake_depth unbalanced on transport->ipcc.power_notify_irq?
> +
> + dcp_ipcc_disable_irq_wake(&transport->ipcc);
> + if (transport->version == 1)
> + writel(PAGE_SIZE,
> + transport->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
> +
> + return virtio_device_restore(&transport->vdev);
> +}
[Severity: High]
In dcp_virtio_transport_suspend(), virtio_device_freeze(&transport->vdev) runs
before dcp_ipcc_send_power_notify(&transport->ipcc), while
transport->ipcc.resp_vq remains non-NULL because neither this function nor
dcp_virtio_driver clears it during freeze. When the firmware acknowledges the
power notification, dcp_ipcc_handle_rx() executes:
drivers/gpu/drm/msm/dcp/dcp_ipcc.c:dcp_ipcc_handle_rx() {
...
complete(&ipcc->power_notify_done);
if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
virtqueue_notify(ipcc->resp_vq);
...
}
Because VIRTIO_RING_F_EVENT_IDX is not negotiated, virtqueue_kick_prepare()
returns true whenever VRING_USED_F_NO_NOTIFY is clear, writing to
VIRTIO_MMIO_QUEUE_NOTIFY after the VirtIO device has already been frozen.
Similarly, in dcp_virtio_transport_resume(), dcp_ipcc_send_power_notify() is
called before virtio_device_restore(&transport->vdev), so the RX
acknowledgment kicks ipcc->resp_vq before restore. Immediately after,
virtio_device_restore() calls virtio_reset_device() -> dcp_virtio_reset(),
which writes 0 to VIRTIO_MMIO_STATUS and resets the hardware virtqueue state,
and nothing in the restore path or dcp_virtio_driver reprograms the virtqueue
registers before setting VIRTIO_CONFIG_S_DRIVER_OK.
Will this leave the virtqueues unconfigured in hardware and break the display
transport across suspend and resume?
> +
> +static const struct dev_pm_ops dcp_virtio_transport_pm_ops = {
> + .suspend = dcp_virtio_transport_suspend,
> + .resume = dcp_virtio_transport_resume,
> +};
> +
> +static const struct of_device_id dcp_virtio_transport_of_match[] = {
> + { .compatible = "qcom,dcp-virtio" },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, dcp_virtio_transport_of_match);
> +
> +static struct platform_driver dcp_virtio_transport_driver = {
> + .probe = dcp_virtio_transport_probe,
> + .remove = dcp_virtio_transport_remove,
> + .driver = {
> + .name = "qcom-dcp-virtio",
> + .of_match_table = dcp_virtio_transport_of_match,
> + .pm = &dcp_virtio_transport_pm_ops,
> + },
> +};
[Severity: Low]
In include/linux/virtio.h, virtio_device_freeze() and virtio_device_restore()
are only declared under #ifdef CONFIG_PM_SLEEP:
#ifdef CONFIG_PM_SLEEP
int virtio_device_freeze(struct virtio_device *dev);
int virtio_device_restore(struct virtio_device *dev);
#endif
Since CONFIG_DRM_MSM_DCP depends on CONFIG_DRM_MSM (which only depends on
CONFIG_PM) and CONFIG_VIRTIO, this file can be built with CONFIG_PM=y and
CONFIG_PM_SLEEP=n.
Won't compiling dcp_virtio_transport_suspend() and
dcp_virtio_transport_resume() unconditionally without #ifdef CONFIG_PM_SLEEP
(and without pm_sleep_ptr() / pm_ptr() on dcp_virtio_transport_pm_ops) cause a
build failure when CONFIG_PM_SLEEP is disabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=19
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver
2026-10-26 6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
2026-10-26 10:15 ` Dmitry Baryshkov
@ 2026-10-26 14:31 ` sashiko-bot
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 14:31 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] drm/msm/dcp: workqueue/kthread leak and UAF from msm_kms_init() in dcp_virtio_probe()
- [High] drm/msm/dcp: missing virtio_driver freeze() and restore() callbacks break resume
- [High] drm/msm/dcp: component_mutex self-deadlock on bridge cleanup during unbind
- [High] drm/msm/dcp: missing GPU split and match checks in dcp_component_register()
- [High] drm/msm/dcp: teardown race in dcp_virtio_hfi_cleanup() allows work re-queue
- [High] drm/msm/dcp: unserialized concurrent virtqueue operations on resp_vq
- [Medium] drm/msm/dcp: missing HFI stop and device reset in dcp_virtio_shutdown()
- [Medium] drm/msm/dcp: unbalanced pm_runtime_disable() on dcp_subsystem_dev
- [Medium] drm/msm/dcp: missing 64-bit DMA mask setup in dcp_subsystem_dev_init()
- [Medium] drm/msm/dcp: OF node reference leak in dcp_dsi_bridge_init() error path
--
commit c14944800f78cc1aa467fd751736b4f2706040b6
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm/dcp: Add DCP VirtIO driver
This commit adds the DCP VirtIO function driver to set up the DCP DMA and
IOMMU identity, initialize the VirtIO and HFI transport, register the MSM
DRM component master, and manage suspend, resume, remove, and shutdown.
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_drv.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c
> new file mode 100644
> index 0000000000000..8c38de883bf68
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c
[ ... ]
> @@ -0,0 +1,191 @@
> +int dcp_subsystem_dev_init(struct dcp_drv *drv)
> +{
> + struct virtio_device *vdev = drv->vdev;
> + struct device_node *dcp_subsystem_node;
> + struct platform_device *pdev;
> + struct device *dev;
> + char busid[32];
> + bool iommu_mapped;
> + int ret;
> +
> + dcp_subsystem_node = of_node_get(vdev->dev.of_node);
> + if (!dcp_subsystem_node) {
> + dev_err(&vdev->dev, "no DCP subsystem DT node\n");
> + return -ENODEV;
> + }
> +
> + snprintf(busid, sizeof(busid), "dcp-subsystem.%s", dev_name(vdev->dev.parent));
> +
> + /*
> + * DCP is discovered through VirtIO, but display/HFI buffers must be
> + * mapped through the dcp-subsystem node's DMA/IOMMU identity. Create a
> + * platform device for that DT node so DMA API and msm_kms_init_vm() use
> + * the display function's IOMMU domain.
> + */
> + pdev = of_platform_device_create(dcp_subsystem_node, busid, vdev->dev.parent);
> + if (!pdev) {
> + dev_err(&vdev->dev, "dcp_subsystem_dev creation failed\n");
> + of_node_put(dcp_subsystem_node);
> + return -ENODEV;
> + }
> + dev = &pdev->dev;
> +
> + dev->dma_parms = &drv->dcp_subsystem_dma_parms;
> + dma_set_max_seg_size(dev, UINT_MAX);
> +
> + ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
> + if (ret) {
> + dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
> + goto err_destroy;
> + }
[Severity: Medium]
Should dcp_subsystem_dev_init() also call dma_set_mask_and_coherent(dev, ~0)
after of_dma_configure_id()?
of_platform_device_create() initializes pdev->dev.coherent_dma_mask to
DMA_BIT_MASK(32) and sets pdev->dev.dma_mask to point to it, and
of_dma_configure_id() only bitwise-ANDs those masks without widening them.
Other MSM KMS master devices go through msm_drv_probe() in
drivers/gpu/drm/msm/msm_drv.c:
ret = dma_set_mask_and_coherent(master_dev, ~0);
if (ret)
return ret;
Without widening the mask on dcp_subsystem_dev->dev (which becomes ddev->dev
and drm_dev_dma_dev(ddev)), DMA operations such as dma_map_sgtable() in
sync_for_device() in drivers/gpu/drm/msm/msm_gem.c or PRIME imports are
restricted to 32-bit addressing, causing SWIOTLB bounce buffering or mapping
failures for 2 MiB THP GEM folios above 4 GiB when passthrough DMA is used,
and limiting default-domain IOVA allocations to 32 bits under iommu-dma.
[ ... ]
> +void dcp_subsystem_dev_deinit(struct dcp_drv *drv)
> +{
> + if (!drv->dcp_subsystem_dev)
> + return;
> +
> + pm_runtime_disable(&drv->dcp_subsystem_dev->dev);
> + of_platform_device_destroy(&drv->dcp_subsystem_dev->dev, NULL);
> + of_node_put(drv->dcp_subsystem_node);
> + drv->dcp_subsystem_dev = NULL;
> +}
[Severity: Medium]
Does this result in an unbalanced pm_runtime_disable() on
drv->dcp_subsystem_dev->dev?
In dcp_subsystem_dev_init(), pm_runtime_enable(dev) is called once on
&drv->dcp_subsystem_dev->dev. Later, dcp_virtio_probe() sets
dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev and dcp_kms->rpm_enabled = true
without a matching pm_runtime_enable() call.
When the DRM component master is torn down via dcp_component_unregister() ->
component_master_del() -> msm_drm_unbind() -> msm_drm_uninit() ->
msm_drm_kms_uninit() -> dcp_dpu_kms_destroy() in
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c:
if (dcp_kms->rpm_enabled) {
pm_runtime_disable(dcp_kms->pm_dev);
dcp_kms->rpm_enabled = false;
}
dcp_dpu_kms_destroy() disables runtime PM on dcp_kms->pm_dev, and then
dcp_subsystem_dev_deinit() calls pm_runtime_disable() a second time on the
same device.
In addition, if a subordinate component unbinds (or if msm_drm_init() fails
after component_bind_all() during a deferred bind), dcp_dpu_kms_destroy()
disables runtime PM on dcp_subsystem_dev->dev and clears
dcp_kms->rpm_enabled, causing any subsequent bind attempt to fail in
dcp_dpu_kms_hw_init() -> pm_runtime_resume_and_get() with -EACCES.
[ ... ]
> +int dcp_component_register(struct dcp_drv *drv)
> +{
> + struct device *dev = &drv->dcp_subsystem_dev->dev;
> + struct component_match *match = NULL;
> + int ret;
> +
> + ret = dcp_dpu_kms_init_dsi_bridges(drv->kms, &match);
> + if (ret) {
> + dev_err(dev, "DSI host initialization failed: %d\n", ret);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> + return ret;
> + }
[Severity: Medium]
Can dcp_dpu_kms_init_dsi_bridges() -> dcp_dsi_bridge_init() leak the
dcp_np device_node reference on error?
In drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c, dcp_dsi_bridge_init() takes a
reference with dsi_bridge->dcp_np = of_node_get(dcp_np). If a subsequent
dma_alloc_coherent(), device_add(), or mipi_dsi_host_register() call fails,
it jumps to err_unwind_dma:
err_unwind_dma:
if (dsi_bridge->dcs_rx_buf) {
dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
dsi_bridge->dcs_rx_buf,
dsi_bridge->dcs_rx_iova);
}
if (dsi_bridge->dcs_tx_buf) {
dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
dsi_bridge->dcs_tx_buf,
dsi_bridge->dcs_tx_iova);
}
cancel_work_sync(&dsi_bridge->power_control_work);
mutex_destroy(&dsi_bridge->session_lock);
mutex_destroy(&dsi_bridge->dcs_lock);
return ERR_PTR(ret);
Because dcp_dsi_bridge_init() returns ERR_PTR(ret) before the bridge is
stored in dcp_kms->dsi_bridge[dsi_idx], dcp_dpu_kms_cleanup_bridges() will
not clean it up, and err_unwind_dma never calls
of_node_put(dsi_bridge->dcp_np).
> +
> + /* A GPU is optional for the DCP display-only DRM device. */
> + msm_add_gpu_components(dev, &match);
> +
> + ret = component_master_add_with_match(dev, &msm_drm_ops, match);
> + if (ret) {
> + dev_err(dev, "component_master_add_with_match failed: %d\n",
> + ret);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> + return ret;
> + }
> +
> + return 0;
> +}
[Severity: High]
What happens in dcp_component_register() when msm_separate_gpu_kms_components()
is true, or when match is NULL or an error pointer?
First, msm_drv_probe() in drivers/gpu/drm/msm/msm_drv.c guards
msm_add_gpu_components() with:
if (!msm_separate_gpu_kms_components()) {
ret = msm_add_gpu_components(master_dev, &match);
if (ret)
return ret;
}
When msm_separate_gpu_kms_components() returns true (for example, when
separate_gpu_kms=1 is set or in multi-GPU configurations), adreno_probe() in
drivers/gpu/drm/msm/adreno/adreno_device.c probes the GPU as a standalone DRM
device via msm_gpu_probe() instead of calling component_add(). Calling
msm_add_gpu_components() unconditionally here adds a GPU component to match
that will never be registered, preventing the DCP DRM master from ever
binding.
Second, drm_of_component_match_add() -> __component_match_add() sets
*matchptr = ERR_PTR(-ENOMEM) on allocation failure while
dcp_dpu_kms_init_dsi_bridges() and msm_add_gpu_components() still return 0,
and match remains NULL if neither DSI bridges nor GPU components are matched.
Passing a NULL or ERR_PTR(match) to component_master_add_with_match() in
drivers/base/component.c immediately dereferences match->num:
ret = component_match_realloc(match, match->num);
> +
> +void dcp_component_unregister(struct dcp_drv *drv)
> +{
> + struct device *dev = &drv->dcp_subsystem_dev->dev;
> +
> + component_master_del(dev, &msm_drm_ops);
> + dcp_dpu_kms_cleanup_bridges(drv->kms);
> +}
[Severity: High]
Won't component_master_del() self-deadlock on component_mutex when a MIPI DSI
device is attached to the DCP DSI host?
During probe, dcp_dpu_kms_init_dsi_bridges() registers the MIPI DSI host, and
when a DSI panel attaches, dcp_dsi_host_attach() in
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c calls component_add() and sets
dsi_bridge->component_added = true.
On removal (or if msm_drm_init() fails after component_bind_all() and unwinds
through msm_drm_uninit()), component_master_del() acquires component_mutex in
drivers/base/component.c and runs the following call chain while holding
component_mutex:
component_master_del() [locks component_mutex]
-> take_down_aggregate_device()
-> msm_drm_unbind()
-> msm_drm_uninit()
-> msm_drm_kms_uninit()
-> dcp_dpu_kms_destroy()
-> dcp_dpu_kms_cleanup_bridges()
-> dcp_dsi_bridge_cleanup()
-> mipi_dsi_host_unregister()
-> mipi_dsi_remove_device_fn()
-> mipi_dsi_detach()
-> dcp_dsi_host_detach()
-> component_del() [locks component_mutex]
Because component_del() attempts to acquire component_mutex on the same
thread, this deadlocks deterministically. Additionally, msm_drm_kms_uninit()
runs before component_unbind_all() in msm_drm_uninit(), so
dcp_dsi_bridge_cleanup() calls device_del(&dsi_bridge->dsi_host_dev) and
put_device(&dsi_bridge->dsi_host_dev) while the component is still bound.
[ ... ]
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_virtio.c b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
> new file mode 100644
> index 0000000000000..15d5caf02ff58
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
[ ... ]
> @@ -0,0 +1,258 @@
> +static int dcp_virtio_probe(struct virtio_device *vdev)
> +{
[ ... ]
> + ret = dcp_hfi_init(drv, vdev, vqs[DCP_VQ_CMD], vqs[DCP_VQ_RESP],
> + vdev->dev.parent);
> + if (ret) {
> + dev_err(&vdev->dev, "HFI init failed: %d\n", ret);
> + goto err_del_vqs;
> + }
> +
> + dcp_ipcc_set_resp_vq(drv->ipcc, vqs[DCP_VQ_RESP]);
> +
> + /*
> + * Keep the device unready until HFI state and virtqueues are initialized.
> + * KMS registration below depends on synchronous firmware initialization.
> + */
> + virtio_device_ready(vdev);
> + virtqueue_kick(drv->hfi->resp_vq);
[Severity: High]
Is access to drv->hfi->resp_vq properly serialized across dcp_virtio_probe(),
dcp_ipcc_handle_rx(), and dcp_hfi_resp_callback()?
Here in dcp_virtio_probe(), dcp_ipcc_set_resp_vq() publishes resp_vq to the
IPCC interrupt/RX handler and virtio_device_ready() enables virtqueue
callbacks before virtqueue_kick(drv->hfi->resp_vq) is called without holding
any lock.
In drivers/gpu/drm/msm/dcp/dcp_hfi_core.c, dcp_hfi_resp_callback() and
dcp_hfi_resp_repost_work() serialize resp_vq operations under
hfi->resp_drain_lock, whereas dcp_ipcc_handle_rx() in
drivers/gpu/drm/msm/dcp/dcp_ipcc.c calls virtqueue_kick_prepare(ipcc->resp_vq)
under ipcc->state_lock without acquiring hfi->resp_drain_lock:
complete(&ipcc->power_notify_done);
if (ipcc->resp_vq && virtqueue_kick_prepare(ipcc->resp_vq))
virtqueue_notify(ipcc->resp_vq);
Can concurrent calls to virtqueue_kick_prepare() and
virtqueue_add_inbuf_premapped() / virtqueue_get_buf() / virtqueue_enable_cb()
on resp_vq corrupt vq->num_added in virtio_ring.c or trigger START_USE(vq)
assertions?
[ ... ]
> + dcp_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dcp_kms), GFP_KERNEL);
> + if (!dcp_kms) {
> + ret = -ENOMEM;
> + goto err_hfi_deinit;
> + }
> +
> + dcp_kms->drv = drv;
> + dcp_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
> + dcp_kms->hfi = drv->hfi;
> + dcp_kms->caps = &drv->caps;
> + dcp_kms->dpus_np = drv->dcp_subsystem_node;
> + dcp_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
> + drv->kms = dcp_kms;
> +
> + ret = msm_kms_init(&dcp_kms->base, &dcp_dpu_kms_funcs);
> + if (ret) {
> + dev_err(&vdev->dev, "msm_kms_init failed: %d\n", ret);
> + goto err_hfi_deinit;
> + }
> +
> + priv = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*priv), GFP_KERNEL);
> + if (!priv) {
> + ret = -ENOMEM;
> + goto err_hfi_deinit;
> + }
> +
> + priv->kms = &dcp_kms->base;
> + priv->kms_init = dcp_kms_init;
> + dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
> + dcp_kms->rpm_enabled = true;
> +
> + /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
> + ret = dcp_component_register(drv);
> + if (ret) {
> + dev_err_probe(&vdev->dev, ret, "component register failed\n");
> + goto err_hfi_deinit;
> + }
[Severity: High]
Does jumping to err_hfi_deinit after msm_kms_init() (or removing the driver
in dcp_virtio_remove() when the component master never bound) leak kms->wq
and the pending_timers kthreads, and cause a use-after-free on dcp_kms?
msm_kms_init() in drivers/gpu/drm/msm/msm_kms.h allocates the ordered "msm"
workqueue (kms->wq) and initializes kms->pending_timers[] via
msm_atomic_init_pending_timer(), which spawns real-time kthread_worker
threads holding pointers into dcp_kms->base.
However, msm_kms_destroy(&dcp_kms->base) is only called from
dcp_dpu_kms_destroy() when a bound DRM aggregate device is torn down via
msm_drm_uninit(). If allocating priv or calling dcp_component_register(drv)
fails in dcp_virtio_probe(), or if dcp_virtio_remove() runs while component
binding is still pending or deferred, dcp_subsystem_dev_deinit() destroys
drv->dcp_subsystem_dev via of_platform_device_destroy() and frees the
devm-allocated dcp_kms while kms->wq and the atomic-worker kthreads/hrtimers
remain active.
> +
> + dcp_panic_notifier_register(drv);
> +
> + dev_dbg(&vdev->dev, "DCP VirtIO probe complete\n");
> + return 0;
> +
> +err_hfi_deinit:
> + dcp_virtio_hfi_cleanup(drv);
> + goto err_fn_dev;
> +err_del_vqs:
> + virtio_reset_device(vdev);
> + vdev->config->del_vqs(vdev);
> +err_fn_dev:
> + dcp_subsystem_dev_deinit(drv);
> + return ret;
> +}
> +
> +static void dcp_virtio_hfi_cleanup(struct dcp_drv *drv)
> +{
> + struct virtio_device *vdev = drv->vdev;
> +
> + if (!drv->hfi)
> + return;
> +
> + dcp_ipcc_set_resp_vq(drv->ipcc, NULL);
> +
> + dcp_hfi_stop(drv->hfi);
> + virtio_reset_device(vdev);
> + virtio_synchronize_cbs(vdev);
> + dcp_hfi_quiesce_queues(drv->hfi);
> + vdev->config->del_vqs(vdev);
> + dcp_hfi_deinit(drv->hfi);
> +}
[Severity: High]
Can an in-flight dcp_hfi_resp_callback() re-queue resource_vote_work or
resp_repost_work after dcp_hfi_stop() has cancelled them in
dcp_virtio_hfi_cleanup()?
In dcp_virtio_hfi_cleanup(), dcp_hfi_stop() sets hfi->shutdown_in_progress = 1
and calls cancel_work_sync(&hfi->resource_vote_work) and
cancel_delayed_work_sync(&hfi->resp_repost_work) before virtio_reset_device()
and virtio_synchronize_cbs() have finished draining running virtqueue
callbacks.
If dcp_hfi_resp_callback() in drivers/gpu/drm/msm/dcp/dcp_hfi_core.c already
passed its initial atomic_read(&hfi->shutdown_in_progress) check on another
CPU, it drops hfi->resp_drain_lock while parsing packets and can schedule
hfi->resource_vote_work or hfi->resp_repost_work after dcp_hfi_stop()
returned. Because neither dcp_hfi_quiesce_queues() nor dcp_hfi_deinit()
cancels those work items after virtio_synchronize_cbs(), can those work items
remain pending in freed struct dcp_hfi memory?
[ ... ]
> +/* KMS shutdown must complete while the firmware transport is available. */
> +static void dcp_virtio_shutdown(struct virtio_device *vdev)
> +{
> + struct dcp_drv *drv = vdev->priv;
> +
> + pm_runtime_get_sync(&drv->dcp_subsystem_dev->dev);
> + msm_kms_shutdown(drv->dcp_subsystem_dev);
> + pm_runtime_put_noidle(&drv->dcp_subsystem_dev->dev);
> +}
[Severity: Medium]
Should dcp_virtio_shutdown() also stop HFI activity, clear ipcc->resp_vq, and
reset/quiesce the VirtIO device after msm_kms_shutdown()?
In drivers/virtio/virtio.c, virtio_dev_shutdown() skips the default
virtio_device_shutdown(dev) path whenever a driver provides its own
.shutdown callback:
/* If the driver has its own shutdown method, use that. */
if (drv->shutdown) {
drv->shutdown(dev);
return;
}
virtio_device_shutdown(dev);
Because dcp_virtio_shutdown() only runs msm_kms_shutdown(), it returns
without clearing ipcc->resp_vq via dcp_ipcc_set_resp_vq(drv->ipcc, NULL),
without calling dcp_hfi_stop(drv->hfi) to cancel resource_vote_work and
resp_repost_work, and without calling virtio_device_shutdown(vdev) (or
breaking and resetting the virtqueues), leaving the VirtIO DMA rings and
pre-posted HFI response buffers active while downstream SMMU shutdown
disables translation.
> +
> +static const struct virtio_device_id dcp_virtio_id_table[] = {
> + { VIRTIO_ID_QPSFI, VIRTIO_VENDOR_ID_DCP_HFI },
> + { 0 },
> +};
> +MODULE_DEVICE_TABLE(virtio, dcp_virtio_id_table);
> +
> +static const struct dev_pm_ops dcp_virtio_pm_ops = {
> + .prepare = dcp_pm_prepare,
> + .complete = dcp_pm_complete,
> +};
> +
> +static struct virtio_driver dcp_virtio_driver = {
> + .driver = {
> + .name = "qcom-dcp-hfi",
> + .owner = THIS_MODULE,
> + .pm = &dcp_virtio_pm_ops,
> + },
> + .id_table = dcp_virtio_id_table,
> + .feature_table = dcp_virtio_features,
> + .feature_table_size = ARRAY_SIZE(dcp_virtio_features),
> + .probe = dcp_virtio_probe,
> + .remove = dcp_virtio_remove,
> + .shutdown = dcp_virtio_shutdown,
> +};
[Severity: High]
How are virtqueues and HFI state preserved across system suspend and resume
without .freeze and .restore callbacks on dcp_virtio_driver?
In drivers/gpu/drm/msm/dcp/dcp_virtio_transport.c,
dcp_virtio_transport_suspend() calls virtio_device_freeze(&transport->vdev)
and dcp_virtio_transport_resume() calls
virtio_device_restore(&transport->vdev).
In drivers/virtio/virtio.c, virtio_device_freeze() invokes drv->freeze(dev)
to quiesce driver activity and delete virtqueues before sleep, and
virtio_device_restore_priv() unconditionally calls virtio_reset_device(dev)
(which calls dcp_virtio_reset() and writes 0 to VIRTIO_MMIO_STATUS, resetting
hardware queue state) before invoking drv->restore(dev) to recreate the
virtqueues.
With .freeze and .restore left NULL on dcp_virtio_driver, HFI work items and
virtqueues are not torn down during suspend, and on resume the VirtIO MMIO
device is reset without recreating cmd_vq and resp_vq or reposting HFI
response buffers, leaving the virtqueues unusable when dcp_pm_complete()
resumes KMS.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=20
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 22/23] drm/msm: Register the DCP backend
2026-10-26 6:21 ` [PATCH v2 22/23] drm/msm: Register the DCP backend Jayant Shekhar
@ 2026-10-26 14:58 ` sashiko-bot
2026-10-27 11:38 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 14:58 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] drm/msm: misordered msm_dcp_register() and msm_dcp_unregister() calls
- [High] drm/msm: missing msm_separate_gpu_kms_components() check and DCP count
- [Low] drm/msm: link failure in msm_dcp_register() when DRM_MSM=y and VIRTIO=m
--
commit 8c20c0a0e35ec53b4a670a45026a398c4476bb45
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
drm/msm: Register the DCP backend
This commit registers and unregisters the DCP platform driver from the MSM
DRM module init and exit paths, and provides no-op stubs in msm_drv.h when
CONFIG_DRM_MSM_DCP is disabled.
> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
> index 5816c64d7ce7d..d413c361c4426 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
> DBG("init");
> msm_mdp_register();
> msm_dpu_register();
> + msm_dcp_register();
> msm_dsi_register();
> msm_hdmi_register();
> msm_dp_register();
> @@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
> msm_dsi_unregister();
> msm_mdp_unregister();
> msm_dpu_unregister();
> + msm_dcp_unregister();
[Severity: High]
Are msm_dcp_register() and msm_dcp_unregister() ordered in the right place
relative to adreno_register() and adreno_unregister()?
In msm_drm_register() and msm_drm_unregister(), MDP5 (msm_mdp_register()) and
DPU (msm_dpu_register()) are registered first and unregistered last because
their platform devices are children of the top-level msm_mdss device. They
are populated when msm_mdss_register() runs at the end of msm_drm_register()
and depopulated via mdss_remove()->of_platform_depopulate() when
msm_mdss_unregister() runs at the start of msm_drm_unregister(), which tears
down the component master before adreno_unregister() is called.
By contrast, DCP (qcom,dcp-virtio) is a standalone top-level platform device
rather than a child of msm_mdss:
drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_register() {
...
/* A GPU is optional for the DCP display-only DRM device. */
msm_add_gpu_components(dev, &match);
ret = component_master_add_with_match(dev, &msm_drm_ops, match);
...
}
drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_unregister() {
struct msm_drm_private *drv = dev_get_drvdata(dev);
component_master_del(dev, &msm_drm_ops);
dcp_dpu_kms_cleanup_bridges(drv->kms);
}
Calling msm_dcp_register() before adreno_register() registers the DCP
component master before the Adreno component driver is registered. On module
unload, calling msm_dcp_unregister() at the end of msm_drm_unregister() runs
adreno_unregister() while the DCP component master (dcp_subsystem_dev) is
still active and bound, rather than first deleting the component master via
msm_dcp_unregister()->dcp_virtio_unregister()->dcp_virtio_remove()->
dcp_component_unregister()->component_master_del().
Should msm_dcp_register() be called at the end of msm_drm_register() and
msm_dcp_unregister() at the start of msm_drm_unregister(), alongside
msm_mdp4_register()/msm_mdss_register() and
msm_mdss_unregister()/msm_mdp4_unregister()?
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index aa5dfd82ac6f7..befaa9f2cb5c5 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
> static inline void msm_dpu_unregister(void) {}
> #endif
>
> +#ifdef CONFIG_DRM_MSM_DCP
> +void msm_dcp_register(void);
> +void msm_dcp_unregister(void);
[Severity: Low]
Will enabling CONFIG_DRM_MSM_DCP cause a vmlinux link failure when
CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m?
In drivers/gpu/drm/msm/Kconfig, DRM_MSM_DCP is a bool symbol that depends on
tristate DRM_MSM and tristate VIRTIO:
config DRM_MSM_DCP
bool "Enable DCP firmware-delegated display support in MSM DRM driver"
depends on DRM_MSM
depends on VIRTIO
When CONFIG_DRM_MSM=y and CONFIG_VIRTIO=m, CONFIG_DRM_MSM_DCP can be enabled
as y, building msm_drv.o and the DCP VirtIO backend objects into vmlinux
while virtio.ko and virtio_ring.ko are built as loadable modules, leaving
msm_drm_register()->msm_dcp_register()->dcp_virtio_register() with unresolved
references to VirtIO symbols such as register_virtio_driver(),
register_virtio_device(), and vring_create_virtqueue().
> +#else
> +static inline void msm_dcp_register(void) {}
> +static inline void msm_dcp_unregister(void) {}
> +#endif
[Severity: High]
How does DCP registration interact with msm_separate_gpu_kms_components() and
msm_separate_gpu_kms_auto()?
When msm_dcp_register()->dcp_virtio_register()->dcp_virtio_probe()->
dcp_component_register() runs, it adds the GPU to the component match list
unconditionally:
drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_component_register() {
...
/* A GPU is optional for the DCP display-only DRM device. */
msm_add_gpu_components(dev, &match);
ret = component_master_add_with_match(dev, &msm_drm_ops, match);
...
}
Unlike msm_drv_probe() in drivers/gpu/drm/msm/msm_drv.c, this does not check
!msm_separate_gpu_kms_components():
drivers/gpu/drm/msm/msm_drv.c:msm_drv_probe() {
...
if (!msm_separate_gpu_kms_components()) {
ret = msm_add_gpu_components(master_dev, &match);
if (ret)
return ret;
}
...
}
When msm_separate_gpu_kms_components() is true and an Adreno GPU is present
in DT, adreno_probe() calls msm_gpu_probe() instead of component_add().
Because dcp_component_register() still adds the GPU node to its match list,
component_master_add_with_match() will wait indefinitely for a GPU component
that is never registered, preventing the DCP DRM device from ever binding.
In addition, unlike CONFIG_DRM_MSM_MDP4 and CONFIG_DRM_MSM_MDSS in
drivers/gpu/drm/msm/msm_drv.h, no msm_count_dcp() helper is provided or
included in msm_separate_gpu_kms_auto():
drivers/gpu/drm/msm/msm_drv.c:msm_separate_gpu_kms_auto() {
int gpus = msm_count_gpus();
int display_subsystems = msm_count_mdss() + msm_count_mdp4();
if (gpus <= 0 || display_subsystems <= 0)
return false;
/* Single card if there is exactly one GPU and one display subsystem */
return gpus > 1 || display_subsystems > 1;
}
Without counting DCP nodes in display_subsystems, won't
msm_separate_gpu_kms_auto() miscalculate the number of display subsystems on
platforms with DCP?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=22
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support
2026-10-26 6:21 ` [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
@ 2026-10-26 15:10 ` sashiko-bot
2026-10-27 11:44 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: sashiko-bot @ 2026-10-26 15:10 UTC (permalink / raw)
To: Jayant Shekhar
Cc: virtualization, dri-devel, devicetree, robh, conor+dt, media-ci,
Michael S. Tsirkin, Eugenio Perez
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] arm64: dts: qcom: hawi: dcp virtio node placed under root instead of soc@0
- [High] arm64: dts: qcom: hawi: panel reset-gpios pin 210 mismatches pinctrl gpio211
- [Medium] arm64: dts: qcom: hawi: dcp left enabled in dtsi with unconnected endpoint
- [Low] arm64: dts: qcom: hawi: sde_disp0_vdd_active violates qcom,pmic-gpio schema
- [Low] arm64: dts: qcom: hawi: duplicate tlmm block and unsorted nodes in hawi-mtp
- [Low] arm64: dts: qcom: hawi: raw integer 13 used instead of IPCC_MPROC_DCP macro
--
commit 1a3114977b7eb966fe7d2b04c935e54daa02d988
Author: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
arm64: dts: qcom: hawi: Add DCP support
This commit adds device tree nodes for the Qualcomm DCP VirtIO display
transport, subsystem, IOMMU mappings, and DSI endpoints in hawi.dtsi, and
adds the DSI panel, regulator, and pinctrl configuration in hawi-mtp.dts.
> diff --git a/arch/arm64/boot/dts/qcom/hawi-mtp.dts b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
> index c38e5fab7a2cb..a27003f69d024 100644
> --- a/arch/arm64/boot/dts/qcom/hawi-mtp.dts
> +++ b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
[ ... ]
> @@ -995,3 +1008,119 @@ &usb_dp_qmpphy {
> &usb_dp_qmpphy_out {
> remote-endpoint = <&pmic_glink_ss_in>;
> };
> +
> +&dcp_dsi0 {
> + panel@0 {
> + compatible = "novatek,nt37801";
> + reg = <0>;
> +
> + pinctrl-0 = <&sde_dsi_active &sde_te_active &sde_esync0_suspend
> + &sde_mdp_vsync_p_1p2_active &sde_mdp_vsync_p_1p8_active
> + &sde_disp0_rst_1p2_active &sde_disp0_rst_1p8_active>;
> + pinctrl-1 = <&sde_dsi_suspend &sde_te_suspend &sde_esync0_suspend
> + &sde_mdp_vsync_p_1p2_active &sde_mdp_vsync_p_1p8_active
> + &sde_disp0_rst_1p2_active &sde_disp0_rst_1p8_active>;
> + pinctrl-names = "default", "sleep";
> +
> + vddio-supply = <&vreg_l12b_1p8>;
> + vci-supply = <&vreg_l13b_3p0>;
> + vdd-supply = <&display_panel_vdd>;
> +
> + reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
> +
> + port {
> + panel_dcp_in: endpoint {
> + remote-endpoint = <&dcp_intf0_out>;
> + };
> + };
> + };
> +};
> +
> +&dcp_intf0_out {
> + remote-endpoint = <&panel_dcp_in>;
> +};
> +
> +&pmd8028_e1_gpios {
> + sde_disp0_vdd_active: sde_disp0_vdd_active {
> + pins = "gpio2";
[Severity: Low]
Does the child node name sde_disp0_vdd_active under &pmd8028_e1_gpios in
arch/arm64/boot/dts/qcom/hawi-mtp.dts cause a dtbs_check failure?
Documentation/devicetree/bindings/pinctrl/qcom,pmic-gpio.yaml sets
additionalProperties: false and requires pin configuration child node names
to match:
patternProperties:
'-state$':
oneOf:
- $ref: '#/$defs/qcom-pmic-gpio-state'
Should this node be named sde-disp0-vdd-active-state to match the schema and
the other PMIC GPIO state nodes in this file?
[ ... ]
> +&tlmm {
> + sde_te_active: sde-te-active-state {
[Severity: Low]
This isn't a bug, but arch/arm64/boot/dts/qcom/hawi-mtp.dts already has a
&tlmm block earlier in the file and keeps &label overrides sorted
alphabetically. Should the new TLMM pin states be merged into the existing
&tlmm node, and the new &dcp_dsi0, &dcp_intf0_out, &pmd8028_e1_gpios,
&pmh0110_d_e0_gpios, and &pmh0110_f_e0_gpios nodes be placed in alphabetical
order?
[ ... ]
> + sde_dsi_active: sde-dsi-active-state {
> + pins = "gpio211";
> + function = "gpio";
> + drive-strength = <8>;
> + bias-disable;
> + };
> +
> + sde_dsi_suspend: sde-dsi-suspend-state {
> + pins = "gpio211";
> + function = "gpio";
> + drive-strength = <2>;
> + bias-pull-down;
> + };
> +};
[Severity: High]
Is there a pin number mismatch between reset-gpios in panel@0 and the
sde_dsi_active / sde_dsi_suspend pinctrl states in
arch/arm64/boot/dts/qcom/hawi-mtp.dts?
When the novatek,nt37801 panel driver probes and transitions between default
and sleep pinctrl states, panel@0 under &dcp_dsi0 requests pin 210 for reset:
reset-gpios = <&tlmm 210 GPIO_ACTIVE_LOW>;
However, sde_dsi_active and sde_dsi_suspend under &tlmm configure "gpio211"
instead of "gpio210". Since the Qualcomm TLMM controller uses 0-based pin
numbering, does this leave pin 210 without its active/sleep drive-strength
and pull-down settings while reconfiguring pin 211 instead, or should
reset-gpios and the pinctrl states reference the same pin?
> diff --git a/arch/arm64/boot/dts/qcom/hawi.dtsi b/arch/arm64/boot/dts/qcom/hawi.dtsi
> index 6b0a0ff0faa45..836970cdef718 100644
> --- a/arch/arm64/boot/dts/qcom/hawi.dtsi
> +++ b/arch/arm64/boot/dts/qcom/hawi.dtsi
> @@ -6841,4 +6841,44 @@ timer {
> <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
> <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
> };
> +
> + dcp: virtio@9be8000 {
> + compatible = "qcom,dcp-virtio";
> + reg = <0x0 0x09be8000 0x0 0x1000>;
[Severity: High]
Was dcp: virtio@9be8000 in arch/arm64/boot/dts/qcom/hawi.dtsi meant to be
placed inside soc@0 rather than directly under the root node?
In arch/arm64/boot/dts/qcom/hawi.dtsi, all SoC MMIO peripherals live under
soc@0, which defines the bus ranges and 36-bit dma-ranges:
soc@0 {
compatible = "simple-bus";
#address-cells = <2>;
#size-cells = <2>;
dma-ranges = <0x0 0x0 0x0 0x0 0x10 0x0>;
ranges = <0x0 0x0 0x0 0x0 0x10 0x0>;
Because dcp: virtio@9be8000 is added after timer directly under the root
node, which has no ranges or dma-ranges properties, root-node schema
validation fails and of_dma_configure_id()->of_dma_get_range() cannot find
the SoC dma-ranges when configuring either the virtio@9be8000 transport
device or the child dcp-subsystem device in
drivers/gpu/drm/msm/dcp/dcp_drv.c:dcp_subsystem_dev_init():
ret = of_dma_configure_id(dev, dcp_subsystem_node, true, NULL);
if (ret) {
dev_err(dev, "of_dma_configure_id failed: %d\n", ret);
goto err_destroy;
}
Should dcp: virtio@9be8000 be moved inside soc@0 (ordered by unit address
between camcc: clock-controller@956d000 and usb_1: usb@a600000)?
> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
> + iommus = <&apps_smmu 0x15a3 0x0>;
> + iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
> + dma-coherent;
> + mboxes = <&ipcc 13 5>;
[Severity: Low]
This isn't a bug, but since arch/arm64/boot/dts/qcom/hawi.dtsi includes
hawi-ipcc.h, could interrupts-extended and mboxes in dcp: virtio@9be8000 use
the symbolic constant for client 13 (defined as IPCC_MPROC_DCP in
hawi-ipcc.h) to match the other IPCC consumers in this file?
> + mbox-names = "dcp-power-tx";
> +
> + dcp-subsystem {
> + compatible = "virtio,device34";
> +
> + iommus = <&apps_smmu 0x400 0x2>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_intf0_out: endpoint {
> +
> + };
> + };
> +
> + };
[Severity: Medium]
Should dcp: virtio@9be8000 in arch/arm64/boot/dts/qcom/hawi.dtsi have
status = "disabled"; by default, with arch/arm64/boot/dts/qcom/hawi-mtp.dts
enabling &dcp?
The commit message notes "Enable the DCP display device on the Hawi MTP
platform", but dcp is left enabled in hawi.dtsi with an empty
dcp_intf0_out: endpoint node that has no remote-endpoint property.
In Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml, every port
endpoint requires remote-endpoint:
patternProperties:
"^port@[0-9a-f]+$":
$ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
properties:
endpoint:
$ref: /schemas/graph.yaml#/$defs/endpoint-base
unevaluatedProperties: false
required:
- remote-endpoint
In addition, on any Hawi board DTS that includes hawi.dtsi without attaching
a panel to dcp_intf0_out and dcp_dsi0, leaving dcp enabled causes
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c:dcp_dpu_kms_init_dsi_bridges() to add
dsi@0 to the DRM component match list even though no panel ever attaches to
register the component:
dcp_kms->dsi_bridge[dsi_idx] = to_dcp_dsi_bridge(bridge);
drm_of_component_match_add(dcp_kms->pm_dev, matchptr,
component_compare_of, dsi_np);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261026-jshekhar-dcp-hlos-v2-0-7cf014efdee1@oss.qualcomm.com?part=23
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
2026-10-26 9:38 ` sashiko-bot
2026-10-26 9:57 ` Dmitry Baryshkov
@ 2026-10-26 19:44 ` Rob Herring (Arm)
2026-10-27 8:36 ` Krzysztof Kozlowski
3 siblings, 0 replies; 55+ messages in thread
From: Rob Herring (Arm) @ 2026-10-26 19:44 UTC (permalink / raw)
To: Jayant Shekhar
Cc: iommu, Abhinav Kumar, Simona Vetter, Robin Murphy,
Dmitry Baryshkov, Sean Paul, Conor Dooley, David Airlie,
linux-arm-msm, Christian König, devicetree, Rob Clark,
linaro-mm-sig, virtualization, Jason Wang, linux-kernel,
Maxime Ripard, Eugenio Pérez, Joerg Roedel (AMD),
linux-media, Mahadevan P, freedreno, Michael S. Tsirkin,
Bjorn Andersson, Jessica Zhang, Flemin Jose, Marijn Suijten,
dri-devel, Abel Vesa, Thomas Zimmermann, Xuan Zhuo, Will Deacon,
Sumit Semwal, Philipp Stanner, Krzysztof Kozlowski,
Maarten Lankhorst, Konrad Dybcio
On Mon, 26 Oct 2026 11:51:11 +0530, Jayant Shekhar wrote:
> DCP platforms place the display pipeline under firmware control, so Linux
> cannot discover display resources through direct register access. The DRM
> driver therefore needs device-tree descriptions of the Qualcomm transport
> wrapper, display topology, IOMMU contexts, and panel connections.
>
> Add bindings for the Qualcomm DCP transport wrapper and VirtIO display
> subsystem. Document the IPCC power-notify resources, transport and
> framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and
> DSI panel connections. The wrapper creates a standard VirtIO-MMIO child
> after the IPCC power-notify sequence has completed.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> .../bindings/display/msm/qcom,dcp-dsi.yaml | 79 ++++++++++++
> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 123 +++++++++++++++++++
> .../bindings/virtio/qcom,dcp-virtio.yaml | 132 +++++++++++++++++++++
> 3 files changed, 334 insertions(+)
>
My bot found errors running 'make dt_binding_check' on your patch:
yamllint warnings/errors:
dtschema/dtc warnings/errors:
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml: iommu-ranges: multiple incompatible types: uint32-array
/usr/local/lib/python3.13/dist-packages/dtschema/schemas/iommu/iommu.yaml: iommu-ranges: multiple incompatible types: uint32-matrix
Could not decode node virtio@9be8000 property iommu-ranges with value b'\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00'
Traceback (most recent call last):
File "/usr/local/bin/dt-validate", line 8, in <module>
sys.exit(main())
~~~~^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 443, in main
dtb_diagnostics = sg.check_dtb(filename)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb_validate.py", line 331, in check_dtb
dt = self.validator.decode_dtb(f.read(), decode_errors)
File "/usr/local/lib/python3.13/dist-packages/dtschema/validator.py", line 631, in decode_dtb
return [dtschema.dtb.fdt_unflatten(self, dtb)]
~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 591, in fdt_unflatten
dt = fdt_scan_node(validator, fdt, '/', offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 320, in fdt_scan_node
node = fdt_scan_node(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 312, in fdt_scan_node
node_dict = node_props(validator, fdt, nodename, offset)
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 258, in node_props
props_dict[p.name] = prop_value(validator, nodename, p)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.13/dist-packages/dtschema/dtb.py", line 185, in prop_value
if len(p) / type_format[fmt].size >= min_dim:
~~~~~~~~~~~^^^^^
KeyError: 'uint32-array'
doc reference errors (make refcheckdocs):
See https://patchwork.kernel.org/project/devicetree/patch/20261026-jshekhar-dcp-hlos-v2-1-7cf014efdee1@oss.qualcomm.com
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] 55+ messages in thread
* Re: [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
` (2 preceding siblings ...)
2026-10-26 19:44 ` Rob Herring (Arm)
@ 2026-10-27 8:36 ` Krzysztof Kozlowski
3 siblings, 0 replies; 55+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-27 8:36 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:11AM +0530, Jayant Shekhar wrote:
> DCP platforms place the display pipeline under firmware control, so Linux
What is a DCP platform?
> cannot discover display resources through direct register access. The DRM
> driver therefore needs device-tree descriptions of the Qualcomm transport
> wrapper, display topology, IOMMU contexts, and panel connections.
>
> Add bindings for the Qualcomm DCP transport wrapper and VirtIO display
> subsystem. Document the IPCC power-notify resources, transport and
> framebuffer IOMMU contexts, firmware-visible IOVA range, INTF graph, and
> DSI panel connections. The wrapper creates a standard VirtIO-MMIO child
> after the IPCC power-notify sequence has completed.
It's still DT for real hardware, so I wonder why no SoC specific
compatibles were used.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands
2026-10-26 6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
2026-10-26 11:13 ` sashiko-bot
@ 2026-10-27 11:06 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-27 11:06 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:20AM +0530, Jayant Shekhar wrote:
> Translate DSI host and panel operations into HFI commands. Firmware
> owns the DSI hardware, so mode validation, display power, panel setup,
> and DCS transfers must cross the HFI interface instead of accessing
> controller registers from Linux.
>
> Implement DSI mode validation, display session and power management,
> panel initialization, and DCS transfer helpers while keeping them
> separate from generic display commands.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/Makefile | 2 +
> drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 81 ++++
> drivers/gpu/drm/msm/dcp/dcp_hfi.h | 25 ++
> drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c | 252 ++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 592 ++++++++++++++++++++++++++++
Please stop calling it a panel. There can be any bridge after the DSI
device. From the Linux point of view, drm_panel is a subclass of
drm_bridge.
> 5 files changed, 952 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index bdc5e3b8efa7..77c268aabca6 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -74,6 +74,8 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
> msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_core.o \
> dcp/dcp_hfi_crtc.o \
> + dcp/dcp_hfi_dsi.o \
> + dcp/dcp_hfi_dsi_panel.o \
> dcp/dcp_hfi_encoder.o \
> dcp/dcp_hfi_kms.o \
> dcp/dcp_hfi_plane.o \
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
> new file mode 100644
> index 000000000000..a8898e469b44
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
> @@ -0,0 +1,81 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP DSI panel configuration shared by the HFI panel implementation and
What if there is a DSI2DPI or DSI2LVDS bridge and then DPI or LVDS
panel?
> + * the DRM DSI bridge.
> + */
> +
> +#ifndef __DCP_DSI_PANEL_H__
> +#define __DCP_DSI_PANEL_H__
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/sizes.h>
> +#include <linux/types.h>
> +
> +#define DCP_DSI_MAX_PHY_TIMINGS 32
> +
> +/**
> + * struct dcp_dsi_hfi_panel_config - Complete HFI PANEL_INIT configuration.
> + *
> + * This contains the bridge-owned panel configuration plus the
> + * mode-dependent values assembled before the HFI PANEL_INIT command.
> + */
> +struct dcp_dsi_hfi_panel_config {
> + u32 display_type;
> + u32 num_ctrls;
> + u32 ctrl_indices[4];
> + u32 num_phys;
> + u32 phy_indices[4];
> + u32 vsync_source;
> + u32 te_dcs_command;
> + u32 dma_trigger;
> + u32 stream_trigger;
> + u32 wr_mem_start;
> + u32 wr_mem_continue;
> + u32 dma_sched_line;
> + u32 dma_sched_window;
> + u32 phy_type;
> + bool cphy_mode;
> + bool lp11_init;
> + bool ulps_supported;
> + bool bllp_eof_power_mode;
> + bool bllp_power_mode;
> + u32 lane_map;
> + u32 color_order;
> + u32 esd_mode;
> + u32 jitter_numer;
> + u32 jitter_denom;
> + dma_addr_t dcs_tx_dva;
> + dma_addr_t dcs_tx_iova;
> + u32 dcs_tx_buf_size;
> + u32 phy_timings[DCP_DSI_MAX_PHY_TIMINGS];
> + u32 num_phy_timings;
> + bool dsc_enabled;
> + u32 dsc_version_major;
> + u32 dsc_version_minor;
> + u32 dsc_scr_version;
> + u32 dsc_slice_height;
> + u32 dsc_slice_width;
> + u32 dsc_slices_per_pkt;
> + u32 dsc_bits_per_component;
> + u32 dsc_pps_delay_ms;
> + u32 dsc_bits_per_pixel;
> + u32 dsc_chroma_format;
> + u32 dsc_color_space;
> + u32 dsc_block_prediction_enable;
Why do you need to copy those? Can't you use struct drm_dsc directly?
Having it as a non-NULL pointer would have saved you from having
the dsc_enabled flag.
> + /* Optional DSC rate-control override panel configuration. */
> + bool dsc_rc_override;
> + u8 dsc_rc_min_qp[15];
> + u8 dsc_rc_max_qp[15];
> + u8 dsc_rc_bpg_offsets[15];
> + u32 topology_mixer_count;
> + u32 topology_enc_count;
> + u32 topology_intf_count;
> +};
> +
> +/* DCS buffers use IOVAs directly in the firmware command descriptor. */
> +#define DCP_DSI_DCS_TX_BUF_SIZE SZ_4K
> +#define DCP_DSI_DCS_RX_BUF_SIZE SZ_4K
> +
> +#endif /* __DCP_DSI_PANEL_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> index 6d3eadb5e811..88d73de69383 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> @@ -242,6 +242,31 @@ int dcp_hfi_send_resource_register(struct dcp_hfi *hfi);
> int dcp_hfi_set_display_property(struct dcp_hfi *hfi, u32 session_id,
> u32 prop_id, u32 value);
>
> +struct dcp_dsi_hfi_panel_config;
> +int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
> + const struct drm_display_mode *mode,
> + const struct mipi_dsi_device *dsi,
> + const struct dcp_dsi_hfi_panel_config *cfg);
> +int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx,
> + u32 display_id, u32 *session_id);
> +int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id);
> +int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
> + const struct drm_display_mode *mode,
> + u64 clk_rate_hz);
> +int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
> + const struct drm_display_mode *mode,
> + u64 clk_rate_hz);
> +int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id);
> +int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
> + u32 packet_id);
> +int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id);
> +int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id);
> +int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id);
> +ssize_t dcp_hfi_dcs_transfer(struct dcp_hfi *hfi, u32 session_id,
> + const struct mipi_dsi_msg *msg,
> + void *tx_buf, dma_addr_t tx_iova,
> + void *rx_buf, dma_addr_t rx_iova,
> + u32 post_wait_ms, u32 ctrl_idx);
> #endif /* __DCP_HFI_H__ */
> void dcp_hfi_set_power_control_cb(struct dcp_hfi *hfi, void *priv,
> void (*cb)(void *priv, u32 display_id,
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
> new file mode 100644
> index 000000000000..fbeec03cf90d
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
> @@ -0,0 +1,252 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * HFI commands for the DSI display-session lifecycle.
> + * Panel initialization and DCS transfers are implemented in dcp_hfi_dsi_panel.c.
> + */
> +
> +#include <linux/device.h>
> +#include <drm/drm_modes.h>
> +
> +#include "dcp_hfi.h"
> +#include "dcp_hfi_priv.h"
> +#include "hfi/hfi_commands_display.h"
> +#include "hfi/hfi_defs_common.h"
> +#include "hfi/hfi_defs_display.h"
> +#include "hfi/hfi_packet.h"
> +#include "hfi/hfi_packer.h"
> +#include "hfi/hfi_properties_display.h"
> +
> +static void dcp_hfi_fill_mode_info(struct hfi_display_mode_info *info,
> + const struct drm_display_mode *mode)
> +{
> + memset(info, 0, sizeof(*info));
> + info->size = sizeof(*info);
> + info->h_active = mode->hdisplay;
> + info->h_back_porch = mode->htotal - mode->hsync_end;
> + info->h_sync_width = mode->hsync_end - mode->hsync_start;
> + info->h_front_porch = mode->hsync_start - mode->hdisplay;
> + info->h_skew = mode->hskew;
> + info->h_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PHSYNC);
> + info->v_active = mode->vdisplay;
> + info->v_back_porch = mode->vtotal - mode->vsync_end;
> + info->v_sync_width = mode->vsync_end - mode->vsync_start;
> + info->v_front_porch = mode->vsync_start - mode->vdisplay;
> + info->v_sync_polarity = !!(mode->flags & DRM_MODE_FLAG_PVSYNC);
> + info->refresh_rate = drm_mode_vrefresh(mode);
> + info->clk_rate_hz_lo = lower_32_bits((u64)mode->clock * 1000);
> + info->clk_rate_hz_hi = upper_32_bits((u64)mode->clock * 1000);
> +}
> +
> +static void dcp_hfi_dump_mode_info(struct dcp_hfi *hfi, const char *cmd,
> + u32 session_id,
> + const struct hfi_display_mode_info *info)
> +{
> + dev_dbg(hfi->dev,
> + "HFI %s s=%u h=%u/%u/%u/%u v=%u/%u/%u/%u p=%u/%u r=%u clk=%llu sz=%u\n",
> + cmd, session_id,
> + info->h_active, info->h_back_porch, info->h_sync_width,
> + info->h_front_porch,
> + info->v_active, info->v_back_porch, info->v_sync_width,
> + info->v_front_porch,
> + info->h_sync_polarity, info->v_sync_polarity,
> + info->refresh_rate,
> + (u64)info->clk_rate_hz_lo | ((u64)info->clk_rate_hz_hi << 32),
> + info->size);
> +}
> +
> +int dcp_hfi_display_mode_validate(struct dcp_hfi *hfi, u32 session_id,
> + const struct drm_display_mode *mode, u64 clk_rate_hz)
> +{
> + struct hfi_display_mode_info info;
> + int ret;
> +
> + dcp_hfi_fill_mode_info(&info, mode);
> + info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
> + info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
> + dcp_hfi_dump_mode_info(hfi, "HFI MODE_VALIDATE", session_id, &info);
> +
> + ret = dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_MODE_VALIDATE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_U32_ARRAY,
> + &info, sizeof(info),
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_MODE_MS);
> +
> + return ret;
> +}
> +
> +int dcp_hfi_display_set_mode(struct dcp_hfi *hfi, u32 session_id,
> + const struct drm_display_mode *mode, u64 clk_rate_hz)
> +{
> + struct hfi_display_mode_info info;
> + int ret;
> +
> + dcp_hfi_fill_mode_info(&info, mode);
> + info.clk_rate_hz_lo = lower_32_bits(clk_rate_hz);
> + info.clk_rate_hz_hi = upper_32_bits(clk_rate_hz);
> + dcp_hfi_dump_mode_info(hfi, "HFI SET_MODE", session_id, &info);
> +
> + ret = dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_SET_MODE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_U32_ARRAY,
> + &info, sizeof(info),
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_MODE_MS);
> +
> + return ret;
> +}
> +
> +int dcp_hfi_display_power_register(struct dcp_hfi *hfi, u32 session_id)
> +{
> + return dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_POWER_REGISTER,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> +}
> +
> +int dcp_hfi_display_power_control_ack(struct dcp_hfi *hfi, u32 session_id,
> + u32 packet_id)
> +{
> + struct dcp_hfi_buf *buf;
> + struct hfi_cmd_buff_hdl hdl;
> + struct hfi_header_info hdr_info;
> + struct hfi_packet_info pkt_info;
> + int ret;
> +
> + buf = dcp_hfi_buf_alloc(hfi);
> + if (!buf)
> + return -ENOMEM;
> +
> + hdl.cmd_buffer = buf->data;
> + hdl.size = DCP_HFI_BUF_SIZE;
> +
> + hdr_info.num_packets = 0;
> + hdr_info.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
> + hdr_info.object_id = session_id;
> + hdr_info.header_id = dcp_hfi_next_packet_id(hfi);
> +
> + ret = hfi_create_header(&hdl, &hdr_info);
> + if (ret)
> + goto err_free_buf;
> +
> + pkt_info.cmd = HFI_COMMAND_DISPLAY_POWER_CONTROL;
> + pkt_info.id = session_id & 0xFFFF;
> + pkt_info.flags = HFI_RX_FLAGS_SUCCESS;
> + pkt_info.packet_id = packet_id;
> + pkt_info.payload_type = HFI_PAYLOAD_NONE;
> + pkt_info.payload_size = 0;
> + pkt_info.payload_ptr = NULL;
> +
> + ret = hfi_create_packet_header(&hdl, &pkt_info);
> + if (ret)
> + goto err_free_buf;
> +
> + return dcp_hfi_send_cmd_async(hfi, buf, hfi_get_header_size(&hdl), NULL);
> +
> +err_free_buf:
> + dcp_hfi_buf_free(hfi, buf);
> + return ret;
> +}
> +
> +int dcp_hfi_display_enable(struct dcp_hfi *hfi, u32 intf_idx, u32 display_id,
> + u32 *session_id)
> +{
> + int ret;
> +
> + ret = dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_ENABLE,
> + HFI_CMD_BUFF_DISPLAY, display_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> + if (ret) {
> + dev_err(hfi->dev,
> + "HFI DISPLAY_ENABLE intf=%u display_id=%u failed: %d\n",
> + intf_idx, display_id, ret);
> + return ret;
> + }
> +
> + *session_id = display_id;
> +
> + return 0;
> +}
> +
> +int dcp_hfi_display_post_enable(struct dcp_hfi *hfi, u32 session_id)
> +{
> + return dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_POST_ENABLE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> +}
> +
> +int dcp_hfi_display_disable(struct dcp_hfi *hfi, u32 session_id)
> +{
> + return dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_DISABLE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> +}
> +
> +int dcp_hfi_display_post_disable(struct dcp_hfi *hfi, u32 session_id)
> +{
> + return dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_POST_DISABLE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> +}
> +
> +int dcp_hfi_display_close(struct dcp_hfi *hfi, u32 session_id)
> +{
> + int ret;
> +
> + ret = dcp_hfi_display_disable(hfi, session_id);
> + if (ret) {
> + dev_warn(hfi->dev, "HFI DISPLAY_DISABLE failed: %d\n", ret);
> + return ret;
> + }
> +
> + ret = dcp_hfi_display_post_disable(hfi, session_id);
> + if (ret) {
> + dev_warn(hfi->dev, "HFI DISPLAY_POST_DISABLE failed: %d\n", ret);
> + return ret;
> + }
Why are there two types of calls, pre and post? Should they go to the
corresponding separate DRM callbacks?
> +
> + ret = dcp_hfi_send_sync(hfi,
> + HFI_COMMAND_DISPLAY_CLOSE,
> + HFI_CMD_BUFF_DISPLAY, session_id,
> + HFI_PAYLOAD_NONE, NULL, 0,
> + HFI_TX_FLAGS_RESPONSE_REQUIRED |
> + HFI_TX_FLAGS_NON_DISCARDABLE,
> + NULL, 0,
> + DCP_HFI_TIMEOUT_DISPLAY_MS);
> + if (ret)
> + dev_warn(hfi->dev, "HFI DISPLAY_CLOSE failed: %d\n", ret);
> +
> + return ret;
> +}
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
> new file mode 100644
> index 000000000000..b27fa8fa3160
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
> @@ -0,0 +1,592 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * HFI commands for panel initialization and DCS transfers.
> + * Firmware owns the DSI controller and PHY.
> + */
> +
> +#include <linux/device.h>
> +#include <linux/math64.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_mipi_dsi.h>
> +
> +#include "dcp_dsi_panel.h"
> +#include "dcp_hfi.h"
> +#include "dcp_hfi_priv.h"
> +#include "hfi/hfi_commands_display.h"
> +#include "hfi/hfi_commands_panel.h"
> +#include "hfi/hfi_defs_common.h"
> +#include "hfi/hfi_defs_panel.h"
> +#include "hfi/hfi_packet.h"
> +#include "hfi/hfi_packer.h"
> +#include "hfi/hfi_properties_panel.h"
> +
> +#define DCP_DSI_CTRL_CMD_READ BIT(0)
> +#define DCP_DSI_CTRL_CMD_FETCH_MEMORY BIT(5)
> +#define DCP_DSI_CTRL_CMD_LAST_COMMAND BIT(6)
> +
> +static u32 dcp_bpp_to_hfi(u32 bpp)
> +{
> + switch (bpp) {
> + case 16: return HFI_PANEL_BPP_16;
> + case 18: return HFI_PANEL_BPP_18;
> + case 30: return HFI_PANEL_BPP_30;
> + case 24:
> + default: return HFI_PANEL_BPP_24;
> + }
> +}
> +
> +static u32 dcp_dsi_traffic_mode(const struct mipi_dsi_device *dsi)
> +{
> + if (!(dsi->mode_flags & MIPI_DSI_MODE_VIDEO))
> + return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
> + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_BURST)
> + return HFI_PANEL_TRAFFIC_BURST_MODE;
> + if (dsi->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE)
> + return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_PULSE_MODE;
> + return HFI_PANEL_TRAFFIC_NON_BURST_SYNC_EVENT_MODE;
> +}
> +
> +static u32 dcp_lanes_to_hfi(u32 num_lanes)
> +{
> + u32 mask = 0;
> +
> + if (num_lanes >= 1)
> + mask |= HFI_PANEL_LANE_0;
There is always at least one lane. Set the mask to HFI_PANEL_LANE_0.
> + if (num_lanes >= 2)
> + mask |= HFI_PANEL_LANE_1;
> + if (num_lanes >= 3)
> + mask |= HFI_PANEL_LANE_2;
> + if (num_lanes >= 4)
> + mask |= HFI_PANEL_LANE_3;
> +
> + return mask ?: HFI_PANEL_LANE_0;
> +}
> +
> +static bool dcp_hfi_panel_wait(struct dcp_hfi *hfi,
> + struct dcp_hfi_completion *comp)
> +{
> + unsigned long flags;
> +
> + if (wait_for_completion_timeout(&comp->done,
> + msecs_to_jiffies(DCP_HFI_TIMEOUT_DISPLAY_MS)))
> + return false;
> +
> + /* Serialize removal with response dispatch before the stack object dies. */
> + spin_lock_irqsave(&hfi->resp_drain_lock, flags);
> + dcp_hfi_pending_find(hfi, comp->packet_id);
> + spin_unlock_irqrestore(&hfi->resp_drain_lock, flags);
> + return true;
> +}
> +
> +int dcp_hfi_panel_init(struct dcp_hfi *hfi, u32 intf_idx,
> + const struct drm_display_mode *mode,
> + const struct mipi_dsi_device *dsi,
> + const struct dcp_dsi_hfi_panel_config *cfg)
> +{
> + u32 num_lanes;
> + u32 bpp;
> + bool is_cmd_mode;
> + u32 display_type;
> + u32 vsync_source;
> + u32 traffic_mode;
> + u32 phy_type;
> +
> + struct dcp_hfi_buf *buf1, *buf2;
> + struct hfi_cmd_buff_hdl hdl1, hdl2;
> + struct hfi_header_info hdr_info1, hdr_info2;
> + struct hfi_packet_info pkt_info;
> + struct dcp_hfi_completion comp1, comp2, comp3;
> + u32 payload[128];
> + u32 *p;
> + u32 packet_id, packet_id2, packet_id3;
> + u64 clk_hz;
> + int ret;
> + bool timeout1, timeout2, timeout3;
> + u32 link_bpp_x16;
Reverse christmas tree, apply universally to all your code.
> +
> + if (!hfi || !mode || !dsi || !dsi->lanes || !cfg)
> + return -EINVAL;
And which of those can actually be passed as NULL?
> + if (cfg->num_ctrls > ARRAY_SIZE(cfg->ctrl_indices) ||
> + cfg->num_phys > ARRAY_SIZE(cfg->phy_indices))
> + return -EINVAL;
> +
> + display_type = cfg->display_type;
> + vsync_source = cfg->vsync_source;
> + phy_type = cfg->phy_type;
> + num_lanes = dsi->lanes;
> + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> + if (bpp < 0)
> + return -EINVAL;
> + is_cmd_mode = !(dsi->mode_flags & MIPI_DSI_MODE_VIDEO);
> + traffic_mode = dcp_dsi_traffic_mode(dsi);
> + link_bpp_x16 = bpp << 4;
> +
> + buf1 = dcp_hfi_buf_alloc(hfi);
> + if (!buf1) {
> + dev_err(hfi->dev, "HFI PANEL_INIT: TX buffer exhausted\n");
> + return -ENOMEM;
> + }
> +
> + hdl1.cmd_buffer = buf1->data;
> + hdl1.size = DCP_HFI_BUF_SIZE;
> +
> + hdr_info1.num_packets = 0;
> + hdr_info1.cmd_buff_type = HFI_CMD_BUFF_DISPLAY;
> + hdr_info1.object_id = intf_idx;
> + hdr_info1.header_id = dcp_hfi_next_packet_id(hfi);
> +
> + ret = hfi_create_header(&hdl1, &hdr_info1);
> + if (ret) {
> + dev_err(hfi->dev, "PANEL_INIT: hfi_create_header (A) failed: %d\n",
> + ret);
> + goto err_free_buf1;
> + }
> +
> + {
Jayant, you really like to ignore comments. Please stop doing that. And
also please stop having unconditional nested blocks. If you need one,
extract it to a function.
> + u32 num_props = 0;
> +
> + p = payload + 1;
> + num_props++;
> + p += HFI_PACK(p, HFI_PROPERTY_PANEL_TIMING_MODE_COUNT, 1);
> +
> + /* DCS TX buffer is described by both DVA and IOVA properties. */
> + if (cfg->dcs_tx_iova && cfg->dcs_tx_dva &&
> + !(cfg->dcs_tx_iova & 0x3ff) && !(cfg->dcs_tx_dva & 0x3ff)) {
> + num_props++;
> + p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_DVA,
> + lower_32_bits(cfg->dcs_tx_dva),
> + upper_32_bits(cfg->dcs_tx_dva),
> + cfg->dcs_tx_buf_size, 0, 0);
> + num_props++;
> + p += HFI_PACK(p, HFI_PROPERTY_PANEL_DCS_CMD_TX_BUF_IOVA,
> + lower_32_bits(cfg->dcs_tx_iova),
> + upper_32_bits(cfg->dcs_tx_iova),
> + cfg->dcs_tx_buf_size, 0, 0);
> + }
> + payload[0] = num_props;
> + }
> +
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ
2026-10-26 6:21 ` [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
@ 2026-10-27 11:37 ` Dmitry Baryshkov
0 siblings, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-27 11:37 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:31AM +0530, Jayant Shekhar wrote:
> Some MSM KMS backends receive display events through firmware or
Which ones?
> another transport and therefore have no host-visible display interrupt.
> Rejecting such a backend during common KMS initialization prevents
> it from using the MSM DRM framework.
>
> Allow KMS backends to use IRQ_NOTCONNECTED. DCP receives VSYNC and
> frame-scan completion as asynchronous HFI events over VirtIO; its IPCC
> interrupt is reserved for power-notify acknowledgments. Existing
> IRQ-backed KMS implementations retain their current request and
> teardown flow.
With the commit message fixed:
Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/msm_kms.c | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 22/23] drm/msm: Register the DCP backend
2026-10-26 6:21 ` [PATCH v2 22/23] drm/msm: Register the DCP backend Jayant Shekhar
2026-10-26 14:58 ` sashiko-bot
@ 2026-10-27 11:38 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-27 11:38 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:32AM +0530, Jayant Shekhar wrote:
> The DCP platform driver provides an MSM KMS backend. Register it from the
> MSM DRM driver init and exit paths so the VirtIO driver can use the backend
> when the DCP function is discovered.
>
> Add DCP registration hooks with no-op stubs when CONFIG_DRM_MSM_DCP is
> disabled. This keeps backend registration consistent with the other MSM
> display backends while allowing DCP support to remain optional.
There is no need to describe patch contents. Please update the prompts
you pass to your AI.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> drivers/gpu/drm/msm/msm_drv.c | 2 ++
> drivers/gpu/drm/msm/msm_drv.h | 8 ++++++++
> 2 files changed, 10 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/msm_drv.c b/drivers/gpu/drm/msm/msm_drv.c
> index 5816c64d7ce7..d413c361c442 100644
> --- a/drivers/gpu/drm/msm/msm_drv.c
> +++ b/drivers/gpu/drm/msm/msm_drv.c
> @@ -1208,6 +1208,7 @@ static int __init msm_drm_register(void)
> DBG("init");
> msm_mdp_register();
> msm_dpu_register();
> + msm_dcp_register();
> msm_dsi_register();
> msm_hdmi_register();
> msm_dp_register();
> @@ -1229,6 +1230,7 @@ static void __exit msm_drm_unregister(void)
> msm_dsi_unregister();
> msm_mdp_unregister();
> msm_dpu_unregister();
> + msm_dcp_unregister();
> }
>
> module_init(msm_drm_register);
> diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
> index aa5dfd82ac6f..befaa9f2cb5c 100644
> --- a/drivers/gpu/drm/msm/msm_drv.h
> +++ b/drivers/gpu/drm/msm/msm_drv.h
> @@ -417,6 +417,14 @@ static inline void msm_dpu_register(void) {}
> static inline void msm_dpu_unregister(void) {}
> #endif
>
> +#ifdef CONFIG_DRM_MSM_DCP
> +void msm_dcp_register(void);
> +void msm_dcp_unregister(void);
This means that the earlier patches generate "function without
prototype" warnings. Please fix that. The prototypes must come together
with the function definition.
> +#else
> +static inline void msm_dcp_register(void) {}
> +static inline void msm_dcp_unregister(void) {}
> +#endif
> +
> #ifdef CONFIG_DRM_MSM_MDSS
> void msm_mdss_register(void);
> void msm_mdss_unregister(void);
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support
2026-10-26 6:21 ` [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
2026-10-26 15:10 ` sashiko-bot
@ 2026-10-27 11:44 ` Dmitry Baryshkov
1 sibling, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-27 11:44 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:33AM +0530, Jayant Shekhar wrote:
> Hawi display hardware is owned by DCP firmware, so the platform device tree
> must describe the Qualcomm transport wrapper, firmware display subsystem,
> power-notify channel, IOMMU contexts, and display graph used by the MSM DRM
> driver.
>
> Describe the Qualcomm DCP transport and its VirtIO-MMIO child,
> the IPCC power-notify channel, IOMMU assignments, firmware-visible IOVA
> range, and DSI display endpoints on Hawi. Enable the DCP display device on
> the Hawi MTP platform.
>
> Assisted-by: LLM
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> ---
> arch/arm64/boot/dts/qcom/hawi-mtp.dts | 129 ++++++++++++++++++++++++++++++++++
> arch/arm64/boot/dts/qcom/hawi.dtsi | 40 +++++++++++
> 2 files changed, 169 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/qcom/hawi-mtp.dts b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
> index c38e5fab7a2c..a27003f69d02 100644
> --- a/arch/arm64/boot/dts/qcom/hawi-mtp.dts
> +++ b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
> @@ -34,6 +34,18 @@ chosen {
> stdout-path = "serial0:115200n8";
> };
>
> + display_panel_vdd: regulator-display-panel-vdd {
> + compatible = "regulator-fixed";
> + regulator-name = "display-panel-vdd";
Is it the actual name in the schematics?
> + regulator-min-microvolt = <1100000>;
> + regulator-max-microvolt = <1100000>;
> + gpio = <&pmd8028_e1_gpios 2 GPIO_ACTIVE_HIGH>;
> + enable-active-high;
> + regulator-boot-on;
> + pinctrl-0 = <&sde_disp0_vdd_active>;
> + pinctrl-names = "default";
> + };
> +
> clocks {
> bi_tcxo_ao_div2: bi-tcxo-ao-div2-clk {
> compatible = "fixed-factor-clock";
> @@ -142,6 +154,7 @@ sdcard_vdd_3p0: sdcard-vdd-3p0 {
>
> vin-supply = <&vreg_bob1>;
> };
> +
Unrelated
> };
>
> &apps_rsc {
[..]
> +&tlmm {
> + sde_te_active: sde-te-active-state {
> + pins = "gpio86";
> + function = "mdp_vsync";
> + drive-strength = <2>;
> + bias-pull-down;
> + };
> +
> + sde_te_suspend: sde-te-suspend-state {
> + pins = "gpio86";
> + function = "mdp_vsync";
> + drive-strength = <2>;
> + bias-pull-down;
> + };
What's the difference from the previous entry?
> +
> + sde_esync0_suspend: sde-esync0-suspend-state {
> + pins = "gpio88";
> + function = "mdp_esync0";
> + drive-strength = <2>;
> + bias-pull-down;
> + };
> +
> + sde_dsi_active: sde-dsi-active-state {
> + pins = "gpio211";
> + function = "gpio";
> + drive-strength = <8>;
> + bias-disable;
> + };
> +
> + sde_dsi_suspend: sde-dsi-suspend-state {
> + pins = "gpio211";
> + function = "gpio";
> + drive-strength = <2>;
> + bias-pull-down;
> + };
What is it used for? I don't see GPIO 211 to be used. GPIO 210 is used
for panel reset.
> +};
> diff --git a/arch/arm64/boot/dts/qcom/hawi.dtsi b/arch/arm64/boot/dts/qcom/hawi.dtsi
> index 6b0a0ff0faa4..836970cdef71 100644
> --- a/arch/arm64/boot/dts/qcom/hawi.dtsi
> +++ b/arch/arm64/boot/dts/qcom/hawi.dtsi
> @@ -6841,4 +6841,44 @@ timer {
> <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
> <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
> };
> +
> + dcp: virtio@9be8000 {
> + compatible = "qcom,dcp-virtio";
> + reg = <0x0 0x09be8000 0x0 0x1000>;
> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
Align properly
> + iommus = <&apps_smmu 0x15a3 0x0>;
> + iommu-ranges = <0x0 0x84000000 0x0 0x04000000>;
> + dma-coherent;
> + mboxes = <&ipcc 13 5>;
> + mbox-names = "dcp-power-tx";
> +
> + dcp-subsystem {
> + compatible = "virtio,device34";
> +
> + iommus = <&apps_smmu 0x400 0x2>;
> +
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_intf0_out: endpoint {
> +
No need for an empty line.
> + };
> + };
> +
> + };
> +
> + dcp_dsi0: dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> + };
> + };
> + };
> };
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
* Re: [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (22 preceding siblings ...)
2026-10-26 6:21 ` [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
@ 2026-10-27 11:58 ` Dmitry Baryshkov
23 siblings, 0 replies; 55+ messages in thread
From: Dmitry Baryshkov @ 2026-10-27 11:58 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Michael S. Tsirkin, Jason Wang,
Eugenio Pérez, Xuan Zhuo, Sumit Semwal, Christian König,
Philipp Stanner, Bjorn Andersson, Konrad Dybcio, Abel Vesa,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Flemin Jose,
Mahadevan P, virtualization, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Mon, Oct 26, 2026 at 11:51:10AM +0530, Jayant Shekhar wrote:
> On Qualcomm platforms using the Display Co-Processor (DCP), the display
> hardware is owned and programmed by firmware. Linux does not access the
> display controller, DSI controller, DSI PHY, display clocks, or other
> display pipeline registers.
>
> The display pipeline must nevertheless be exposed through the DRM/KMS
> interfaces so userspace can perform modesetting, framebuffer updates,
> panel control, and page flips.
>
> This series adds a DCP KMS backend to MSM DRM. It represents the
> firmware-owned display pipeline and translates DRM operations into HFI
> commands.
>
> DCP communicates with Linux over VirtIO and uses the HFI protocol for
> command and response handling, capability discovery, and display events.
>
> This series adds:
>
> - Device-tree bindings for the Qualcomm DCP VirtIO transport wrapper,
> firmware-visible IOVA ranges, separate transport and framebuffer IOMMU
> contexts, IPCC power notification, interrupts, and the external DSI
> display graph.
>
> - HFI protocol definitions and packet helpers for capability discovery,
> modesetting, plane and CRTC configuration, DSI, and panel control.
>
> - A Qualcomm DCP transport wrapper which initializes the IPCC channel,
> sends the required power notification, and registers the DCP VirtIO
> device directly. The wrapper also implements the MMIO transport and
> keeps VirtIO queue activity ordered with DCP power transitions.
>
> - VirtIO command and response queues. HFI command, response, and
> capability buffers are allocated through the DMA API in the address
> space visible to DCP firmware.
>
> - Synchronous command tracking, asynchronous response dispatch, timeout
> handling, and queue teardown synchronization for safe device removal.
>
> - Capability discovery for displays, CRTCs, mixers, VIG and DMA pipes,
> DSI interfaces, and panel resources.
>
> - DRM planes and CRTCs using the standard atomic-state model. DRM state is
> translated into HFI properties for layer configuration, mixer limits,
> bandwidth and clock properties, frame sequencing, and triggering.
>
> - An MSM KMS backend, DCP encoder, and DSI bridge. The backend reuses the
> existing MSM DRM device, GEM and framebuffer handling, atomic commit
> infrastructure, encoder, bridge, and connector support.
>
> - Asynchronous VSYNC, frame-scan completion, and error events propagated to
> DRM for vblank and page-flip completion.
>
> - IPCC power notifications to wake DCP firmware and coordinate HFI queue
> activity across firmware power transitions.
>
> DCP is integrated as an MSM KMS backend rather than a separate DRM device.
> This preserves the userspace-visible MSM DRM device and allows Linux to
> retain normal DRM/KMS semantics while display hardware remains owned by
> firmware.
>
> The initial implementation supports the DSI output described below and
> uses resources reported by firmware. Split-pipe, multirect, cursor-pipe,
> partial-update, DisplayPort, and writeback support are deferred.
>
> Tested on the Qualcomm Hawi MTP with an NT37801 DSC command-mode panel:
>
> - Display console at 1440x3200.
> - Panel initialization and DCS transfers complete successfully.
>
> Device Tree Topology
> ====================
>
> qcom,dcp-virtio
> ├── transport IOMMU context
> ├── firmware-visible iommu-ranges
> ├── VirtIO and power-notify interrupts
> ├── dcp-power-tx mailbox
> └── dcp-subsystem
> ├── compatible = "virtio,device34"
> ├── framebuffer IOMMU context
> ├── port@0
> │ └── endpoint ─────────────── panel endpoint
> └── dsi@0
> └── panel@0
>
> The DCP INTF endpoint connects directly to the panel endpoint. Firmware
> internal paths, including the INTF-to-DSI connection, are not described in
> the device tree.
>
> Runtime Architecture
> ====================
>
> Linux DRM/KMS
> └── DCP MSM KMS backend
> ├── planes, CRTCs, encoder, connector, and DSI bridge
> └── DRM atomic state and commit handling
> │
> ▼
> DCP VirtIO function driver
> │
> ▼
> VirtIO core
> │
> ▼
> qcom,dcp-virtio transport wrapper
> ├── IPCC initialization and power notification
> ├── MMIO register access
> ├── VirtIO queue setup and interrupt handling
> ├── VirtIO device registration
> └── suspend/resume power sequencing
> │
> ▼
> DCP firmware
> ├── HFI command and response handling
> ├── display pipeline and VIG/DMA pipes
> ├── DSI PHY and controller
> └── display clocks and resource handling
Going through the series, I have come with the quite generic comment.
Please split the series into two independent parts:
- Documentation/devicetree/bindings/virtio/qcom,dcp-virtio.yaml
drivers/virtio/virtio_dcp_*.[ch]
Driver for the DCP + IPCC VirtIO host. The driver should not know
anything about the display interface.
- Documentation/devicetree/bindings/display/msm
drivers/gpu/drm/msm/dcp
The actual driver for the DCP display engines. It must not know or
care at all about the particular VirtIO host (or SoC FWIW). It should
not use IPCC. If we pass it through to the VM via virtio-mmio or
virtio-pci it must work as is.
Possibly submit those as two separate series.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 55+ messages in thread
end of thread, other threads:[~2026-10-27 11:59 UTC | newest]
Thread overview: 55+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-26 6:21 [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 01/23] dt-bindings: display: msm: Add Qualcomm DCP Jayant Shekhar
2026-10-26 9:38 ` sashiko-bot
2026-10-26 9:57 ` Dmitry Baryshkov
2026-10-26 19:44 ` Rob Herring (Arm)
2026-10-27 8:36 ` Krzysztof Kozlowski
2026-10-26 6:21 ` [PATCH v2 02/23] drm/msm/dcp: Add HFI packet helpers Jayant Shekhar
2026-10-26 9:48 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 04/23] drm/msm/dcp: Add HFI display definitions Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 05/23] drm/msm/dcp: Add HFI panel definitions Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 06/23] drm/msm/dcp: Add HFI VirtIO transport Jayant Shekhar
2026-10-26 10:07 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
2026-10-26 10:22 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
2026-10-26 10:36 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
2026-10-26 10:52 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
2026-10-26 11:13 ` sashiko-bot
2026-10-27 11:06 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 11/23] drm/msm/dcp: Add DCP CRTCs and shared plane state Jayant Shekhar
2026-10-26 10:49 ` Dmitry Baryshkov
2026-10-26 11:33 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
2026-10-26 11:57 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
2026-10-26 12:08 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder Jayant Shekhar
2026-10-26 6:21 ` [PATCH v2 15/23] drm/msm/dcp: Add DCP DSI host and panel support Jayant Shekhar
2026-10-26 11:08 ` Dmitry Baryshkov
2026-10-26 12:37 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
2026-10-26 13:03 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
2026-10-26 13:24 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
2026-10-26 9:59 ` Dmitry Baryshkov
2026-10-26 10:27 ` Michael S. Tsirkin
2026-10-26 6:21 ` [PATCH v2 19/23] drm/msm/dcp: Add Qualcomm VirtIO transport wrapper Jayant Shekhar
2026-10-26 10:07 ` Dmitry Baryshkov
2026-10-26 14:05 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
2026-10-26 10:15 ` Dmitry Baryshkov
2026-10-26 14:31 ` sashiko-bot
2026-10-26 6:21 ` [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
2026-10-27 11:37 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 22/23] drm/msm: Register the DCP backend Jayant Shekhar
2026-10-26 14:58 ` sashiko-bot
2026-10-27 11:38 ` Dmitry Baryshkov
2026-10-26 6:21 ` [PATCH v2 23/23] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
2026-10-26 15:10 ` sashiko-bot
2026-10-27 11:44 ` Dmitry Baryshkov
2026-10-27 11:58 ` [PATCH v2 00/23] drm/msm/dcp: Add Qualcomm DCP display support Dmitry Baryshkov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox