mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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; 38+ 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] 38+ 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:57   ` Dmitry Baryshkov
                     ` (2 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, 3 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 03/23] drm/msm/dcp: Add HFI device definitions Jayant Shekhar
                   ` (21 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
                   ` (17 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
                   ` (16 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
                   ` (15 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
                   ` (14 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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-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, 1 reply; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
                   ` (12 subsequent siblings)
  23 siblings, 1 reply; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
                   ` (11 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 14/23] drm/msm/dcp: Add DCP encoder Jayant Shekhar
                   ` (10 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
                   ` (8 subsequent siblings)
  23 siblings, 1 reply; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
                   ` (7 subsequent siblings)
  23 siblings, 0 replies; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 18/23] virtio: Add Qualcomm Platform Services Firmware Interface ID Jayant Shekhar
                   ` (6 subsequent siblings)
  23 siblings, 0 replies; 38+ 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) ||
+			    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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 20/23] drm/msm/dcp: Add DCP VirtIO driver Jayant Shekhar
                   ` (4 subsequent siblings)
  23 siblings, 1 reply; 38+ 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	[flat|nested] 38+ 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  6:21 ` [PATCH v2 21/23] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
                   ` (3 subsequent siblings)
  23 siblings, 1 reply; 38+ 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	[flat|nested] 38+ 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; 38+ 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	[flat|nested] 38+ 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-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, 1 reply; 38+ 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	[flat|nested] 38+ 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-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, 1 reply; 38+ 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	[flat|nested] 38+ 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:57   ` Dmitry Baryshkov
  2026-10-26 19:44   ` Rob Herring (Arm)
  2026-10-27  8:36   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 38+ 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] 38+ 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; 38+ 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] 38+ 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
  0 siblings, 0 replies; 38+ 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] 38+ 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
  0 siblings, 0 replies; 38+ 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] 38+ 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; 38+ 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] 38+ 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
  0 siblings, 0 replies; 38+ 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] 38+ 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
  0 siblings, 0 replies; 38+ 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] 38+ 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:57   ` Dmitry Baryshkov
@ 2026-10-26 19:44   ` Rob Herring (Arm)
  2026-10-27  8:36   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 38+ 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] 38+ 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:57   ` Dmitry Baryshkov
  2026-10-26 19:44   ` Rob Herring (Arm)
@ 2026-10-27  8:36   ` Krzysztof Kozlowski
  2 siblings, 0 replies; 38+ 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] 38+ 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-27 11:06   ` Dmitry Baryshkov
  0 siblings, 0 replies; 38+ 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] 38+ 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; 38+ 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] 38+ 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-27 11:38   ` Dmitry Baryshkov
  0 siblings, 0 replies; 38+ 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] 38+ 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-27 11:44   ` Dmitry Baryshkov
  0 siblings, 0 replies; 38+ 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] 38+ 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; 38+ 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] 38+ messages in thread

end of thread, other threads:[~2026-10-27 11:58 UTC | newest]

Thread overview: 38+ 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: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  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  6:21 ` [PATCH v2 07/23] drm/msm/dcp: Handle asynchronous HFI responses Jayant Shekhar
2026-10-26  6:21 ` [PATCH v2 08/23] drm/msm/dcp: Add IPCC power notifications Jayant Shekhar
2026-10-26  6:21 ` [PATCH v2 09/23] drm/msm/dcp: Add generic HFI display commands Jayant Shekhar
2026-10-26  6:21 ` [PATCH v2 10/23] drm/msm/dcp: Add HFI DSI and panel commands Jayant Shekhar
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  6:21 ` [PATCH v2 12/23] drm/msm/dcp: Add DCP planes Jayant Shekhar
2026-10-26  6:21 ` [PATCH v2 13/23] drm/msm/dcp: Route HFI display events Jayant Shekhar
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  6:21 ` [PATCH v2 16/23] drm/msm/dcp: Integrate the DSI bridge with KMS Jayant Shekhar
2026-10-26  6:21 ` [PATCH v2 17/23] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
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  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  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-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: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

all inboxes | Powered by JetHome®