* [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 10:50 ` Krzysztof Kozlowski
2026-10-15 12:17 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 02/13] drm/msm/dcp: Add HFI protocol definitions Jayant Shekhar
` (11 subsequent siblings)
12 siblings, 2 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
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 firmware transport,
display topology, IOMMU contexts, and panel connections.
Add bindings for the Qualcomm DCP VirtIO display function, including the
INTF graph and DSI host outputs. Document the framebuffer IOMMU context,
IPCC power-notify mailbox and interrupts, firmware display topology, and
firmware-visible IOVA window supplied by the parent VirtIO-MMIO transport.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
.../bindings/display/msm/qcom,dcp-dsi.yaml | 75 +++++++++++++
.../bindings/display/msm/qcom,dcp-virtio.yaml | 113 +++++++++++++++++++
.../devicetree/bindings/display/msm/qcom,dcp.yaml | 119 +++++++++++++++++++++
3 files changed, 307 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..f7178c66c09e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
@@ -0,0 +1,75 @@
+# 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). DCP is a firmware protocol shared across multiple
+ Qualcomm SoCs, not specific to any one of them; Hawi is its first
+ platform. The DSI host and its connection to a panel are described
+ using the standard DSI controller and OF graph properties.
+
+allOf:
+ - $ref: /schemas/display/dsi-controller.yaml#
+
+properties:
+ reg:
+ maxItems: 1
+ description: Index of this DSI host among its siblings.
+
+ status: true
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+ description: |
+ port@0: Input from the DCP virtio device's DPU INTF output port.
+ port@1: Output to the attached panel.
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+required:
+ - reg
+ - ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ dcp-subsystem {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_dsi0_in: endpoint {
+ remote-endpoint = <&dcp_intf1_out>;
+ };
+ };
+
+ };
+ };
+ };
diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
new file mode 100644
index 000000000000..018694310dfb
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
@@ -0,0 +1,113 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/display/msm/qcom,dcp-virtio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm DCP VirtIO host
+
+maintainers:
+ - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
+
+description: |
+ Qualcomm's DCP VirtIO host provides the generic VirtIO-MMIO transport and
+ the host-side IPCC channel used to wake the DCP firmware.
+
+properties:
+ compatible:
+ contains:
+ const: qcom,dcp-virtio
+
+ reg:
+ maxItems: 1
+
+ iommus:
+ maxItems: 1
+
+ iommu-ranges:
+ $ref: /schemas/types.yaml#/definitions/uint32-array
+
+ interrupts-extended:
+ minItems: 2
+
+ interrupt-names:
+ items:
+ - const: virtio
+ - const: dcp_power_notify_irq
+
+ dma-coherent: true
+
+ mboxes:
+ maxItems: 1
+
+ mbox-names:
+ const: dcp-power-tx
+
+ dcp-subsystem:
+ $ref: qcom,dcp.yaml#
+
+required:
+ - compatible
+ - reg
+ - interrupts-extended
+ - interrupt-names
+ - iommus
+ - dcp-subsystem
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+ #include <dt-bindings/mailbox/qcom-ipcc.h>
+
+ virtio@9be8000 {
+ compatible = "qcom,dcp-virtio", "virtio,mmio";
+ reg = <0x09be8000 0x1000>;
+ interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+ <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "virtio", "dcp_power_notify_irq";
+ 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@1 {
+ reg = <1>;
+ dcp_intf1_out: endpoint {
+ remote-endpoint = <&dcp_dsi0_in>;
+ };
+ };
+ };
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_dsi0_in: endpoint {
+ remote-endpoint = <&dcp_intf1_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..0ca00a567e9e
--- /dev/null
+++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp.yaml
@@ -0,0 +1,119 @@
+# 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 DSI hosts. Firmware capabilities and internal resources,
+ including pipes, mixers, CRTCs, and clocks, are discovered at runtime.
+
+ The compatible string "virtio,device34" encodes device ID 52 (0x34). It is
+ a provisional local ID pending registration of the DCP HFI VirtIO device.
+
+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 virtio,mmio 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 ports. Port number matches INTF index:
+ port@0: INTF0 (no bridge in this series)
+ port@1: INTF1 -> DSI0
+ port@2: INTF2 -> DSI1
+ port@3: INTF3 (no bridge in this series)
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/properties/port
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ port@2:
+ $ref: /schemas/graph.yaml#/properties/port
+ port@3:
+ $ref: /schemas/graph.yaml#/properties/port
+
+patternProperties:
+ "^dsi@[0-9]+$":
+ $ref: qcom,dcp-dsi.yaml#
+
+required:
+ - compatible
+ - iommus
+ - "#address-cells"
+ - "#size-cells"
+ - ports
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ dcp-subsystem {
+ compatible = "virtio,device34";
+
+ iommus = <&apps_smmu 0x400 0x2>;
+
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@1 {
+ reg = <1>;
+ dcp_intf1_out: endpoint {
+ remote-endpoint = <&dcp_dsi0_in>;
+ };
+ };
+ };
+
+ dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_dsi0_in: endpoint {
+ remote-endpoint = <&dcp_intf1_out>;
+ };
+ };
+
+ };
+ };
+ };
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings
2026-10-15 7:38 ` [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings Jayant Shekhar
@ 2026-10-15 10:50 ` Krzysztof Kozlowski
2026-10-15 12:17 ` Dmitry Baryshkov
1 sibling, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-15 10:50 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:21PM +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 firmware transport,
> display topology, IOMMU contexts, and panel connections.
Due to dependencies this cannot be tested, so limited review.
A nit, subject: drop second/last, redundant "bindings". The
"dt-bindings" prefix is already stating that these are bindings.
See also:
https://elixir.bootlin.com/linux/v7.1-rc7/source/Documentation/devicetree/bindings/submitting-patches.rst#L23
>
> Add bindings for the Qualcomm DCP VirtIO display function, including the
> INTF graph and DSI host outputs. Document the framebuffer IOMMU context,
> IPCC power-notify mailbox and interrupts, firmware display topology, and
> firmware-visible IOVA window supplied by the parent VirtIO-MMIO transport.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> .../bindings/display/msm/qcom,dcp-dsi.yaml | 75 +++++++++++++
> .../bindings/display/msm/qcom,dcp-virtio.yaml | 113 +++++++++++++++++++
> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 119 +++++++++++++++++++++
> 3 files changed, 307 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..f7178c66c09e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
> @@ -0,0 +1,75 @@
> +# 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). DCP is a firmware protocol shared across multiple
> + Qualcomm SoCs, not specific to any one of them; Hawi is its first
> + platform. The DSI host and its connection to a panel are described
> + using the standard DSI controller and OF graph properties.
> +
> +allOf:
> + - $ref: /schemas/display/dsi-controller.yaml#
> +
> +properties:
> + reg:
> + maxItems: 1
> + description: Index of this DSI host among its siblings.
> +
> + status: true
I see slop, I finished here. Mind this maintainers summit words - if you
use AI, you must use good one, not the shloppy slop.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings
2026-10-15 7:38 ` [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings Jayant Shekhar
2026-10-15 10:50 ` Krzysztof Kozlowski
@ 2026-10-15 12:17 ` Dmitry Baryshkov
2026-10-21 8:45 ` Jayant Shekhar
1 sibling, 1 reply; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 12:17 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:21PM +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 firmware transport,
> display topology, IOMMU contexts, and panel connections.
>
> Add bindings for the Qualcomm DCP VirtIO display function, including the
> INTF graph and DSI host outputs. Document the framebuffer IOMMU context,
> IPCC power-notify mailbox and interrupts, firmware display topology, and
> firmware-visible IOVA window supplied by the parent VirtIO-MMIO transport.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> .../bindings/display/msm/qcom,dcp-dsi.yaml | 75 +++++++++++++
> .../bindings/display/msm/qcom,dcp-virtio.yaml | 113 +++++++++++++++++++
> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 119 +++++++++++++++++++++
> 3 files changed, 307 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..f7178c66c09e
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
> @@ -0,0 +1,75 @@
> +# 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). DCP is a firmware protocol shared across multiple
> + Qualcomm SoCs, not specific to any one of them; Hawi is its first
> + platform. The DSI host and its connection to a panel are described
> + using the standard DSI controller and OF graph properties.
> +
> +allOf:
> + - $ref: /schemas/display/dsi-controller.yaml#
> +
> +properties:
> + reg:
> + maxItems: 1
> + description: Index of this DSI host among its siblings.
> +
> + status: true
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> + description: |
> + port@0: Input from the DCP virtio device's DPU INTF output port.
> + port@1: Output to the attached panel.
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/properties/port
> + port@1:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + unevaluatedProperties: false
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> + unevaluatedProperties: false
Why do we need OF graph here? Would it be enough to link one of the
ports of the root node with the panel's input?
> +
> +required:
> + - reg
> + - ports
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + dcp-subsystem {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_dsi0_in: endpoint {
> + remote-endpoint = <&dcp_intf1_out>;
> + };
> + };
> +
> + };
> + };
> + };
> diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
> new file mode 100644
> index 000000000000..018694310dfb
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
> @@ -0,0 +1,113 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/display/msm/qcom,dcp-virtio.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm DCP VirtIO host
> +
> +maintainers:
> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> +
> +description: |
> + Qualcomm's DCP VirtIO host provides the generic VirtIO-MMIO transport and
> + the host-side IPCC channel used to wake the DCP firmware.
> +
> +properties:
> + compatible:
> + contains:
> + const: qcom,dcp-virtio
> +
> + reg:
> + maxItems: 1
> +
> + iommus:
> + maxItems: 1
> +
> + iommu-ranges:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> +
> + interrupts-extended:
> + minItems: 2
> +
> + interrupt-names:
> + items:
> + - const: virtio
> + - const: dcp_power_notify_irq
> +
> + dma-coherent: true
> +
> + mboxes:
> + maxItems: 1
> +
> + mbox-names:
> + const: dcp-power-tx
> +
> + dcp-subsystem:
> + $ref: qcom,dcp.yaml#
> +
> +required:
> + - compatible
> + - reg
> + - interrupts-extended
> + - interrupt-names
> + - iommus
> + - dcp-subsystem
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/interrupt-controller/arm-gic.h>
> + #include <dt-bindings/mailbox/qcom-ipcc.h>
> +
> + virtio@9be8000 {
> + compatible = "qcom,dcp-virtio", "virtio,mmio";
Is it really compatible with virtio,mmio? Can it be handled completely
by virtio,mmio?
> + reg = <0x09be8000 0x1000>;
> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
> + interrupt-names = "virtio", "dcp_power_notify_irq";
Why do you need names? Can they be dropped in favour of using indices?
> + 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@1 {
> + reg = <1>;
> + dcp_intf1_out: endpoint {
> + remote-endpoint = <&dcp_dsi0_in>;
Can this point directly to the panel_in? What's the point of describing
internal paths in the DT?
> + };
> + };
> + };
> +
> + dsi@0 {
> + reg = <0>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + ports {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + port@0 {
> + reg = <0>;
> + dcp_dsi0_in: endpoint {
> + remote-endpoint = <&dcp_intf1_out>;
> + };
> + };
> + };
> + };
> + };
> + };
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings
2026-10-15 12:17 ` Dmitry Baryshkov
@ 2026-10-21 8:45 ` Jayant Shekhar
2026-10-21 11:39 ` Jayant Shekhar
0 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-21 8:45 UTC (permalink / raw)
To: Dmitry Baryshkov, Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On 15-10-2026 17:47, Dmitry Baryshkov wrote:
> On Thu, Oct 15, 2026 at 01:08:21PM +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 firmware transport,
>> display topology, IOMMU contexts, and panel connections.
>>
>> Add bindings for the Qualcomm DCP VirtIO display function, including the
>> INTF graph and DSI host outputs. Document the framebuffer IOMMU context,
>> IPCC power-notify mailbox and interrupts, firmware display topology, and
>> firmware-visible IOVA window supplied by the parent VirtIO-MMIO transport.
>>
>> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
>> Assisted-by: LLM
>> ---
>> .../bindings/display/msm/qcom,dcp-dsi.yaml | 75 +++++++++++++
>> .../bindings/display/msm/qcom,dcp-virtio.yaml | 113 +++++++++++++++++++
>> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 119 +++++++++++++++++++++
>> 3 files changed, 307 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..f7178c66c09e
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
>> @@ -0,0 +1,75 @@
>> +# 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). DCP is a firmware protocol shared across multiple
>> + Qualcomm SoCs, not specific to any one of them; Hawi is its first
>> + platform. The DSI host and its connection to a panel are described
>> + using the standard DSI controller and OF graph properties.
>> +
>> +allOf:
>> + - $ref: /schemas/display/dsi-controller.yaml#
>> +
>> +properties:
>> + reg:
>> + maxItems: 1
>> + description: Index of this DSI host among its siblings.
>> +
>> + status: true
>> +
>> + ports:
>> + $ref: /schemas/graph.yaml#/properties/ports
>> + description: |
>> + port@0: Input from the DCP virtio device's DPU INTF output port.
>> + port@1: Output to the attached panel.
>> + properties:
>> + port@0:
>> + $ref: /schemas/graph.yaml#/properties/port
>> + port@1:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + unevaluatedProperties: false
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> + unevaluatedProperties: false
> Why do we need OF graph here? Would it be enough to link one of the
> ports of the root node with the panel's input?
>
>> +
>> +required:
>> + - reg
>> + - ports
>> +
>> +unevaluatedProperties: false
>> +
>> +examples:
>> + - |
>> + dcp-subsystem {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + dsi@0 {
>> + reg = <0>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + ports {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + port@0 {
>> + reg = <0>;
>> + dcp_dsi0_in: endpoint {
>> + remote-endpoint = <&dcp_intf1_out>;
>> + };
>> + };
>> +
>> + };
>> + };
>> + };
>> diff --git a/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
>> new file mode 100644
>> index 000000000000..018694310dfb
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
>> @@ -0,0 +1,113 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/display/msm/qcom,dcp-virtio.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Qualcomm DCP VirtIO host
>> +
>> +maintainers:
>> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
>> +
>> +description: |
>> + Qualcomm's DCP VirtIO host provides the generic VirtIO-MMIO transport and
>> + the host-side IPCC channel used to wake the DCP firmware.
>> +
>> +properties:
>> + compatible:
>> + contains:
>> + const: qcom,dcp-virtio
>> +
>> + reg:
>> + maxItems: 1
>> +
>> + iommus:
>> + maxItems: 1
>> +
>> + iommu-ranges:
>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>> +
>> + interrupts-extended:
>> + minItems: 2
>> +
>> + interrupt-names:
>> + items:
>> + - const: virtio
>> + - const: dcp_power_notify_irq
>> +
>> + dma-coherent: true
>> +
>> + mboxes:
>> + maxItems: 1
>> +
>> + mbox-names:
>> + const: dcp-power-tx
>> +
>> + dcp-subsystem:
>> + $ref: qcom,dcp.yaml#
>> +
>> +required:
>> + - compatible
>> + - reg
>> + - interrupts-extended
>> + - interrupt-names
>> + - iommus
>> + - dcp-subsystem
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> + - |
>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>> + #include <dt-bindings/mailbox/qcom-ipcc.h>
>> +
>> + virtio@9be8000 {
>> + compatible = "qcom,dcp-virtio", "virtio,mmio";
> Is it really compatible with virtio,mmio? Can it be handled completely
> by virtio,mmio?
There are two possible approaches:
Option 1 — Use the standard VirtIO-MMIO parent (preferred)
Keep the parent as:
compatible = "virtio,mmio";
reg = <...>;
interrupts = <...>;
iommus = <...>;
iommu-ranges = <...>;
dma-coherent;
The generic virtio/mmio.yaml binding would be extended only to document
the generic iommu-ranges property.
The DCP-specific resources would be placed on the virtio,deviceXX child:
dcp-subsystem {
compatible = "virtio,deviceXX";
iommus = <...>;
interrupts-extended = <&ipcc ...>;
mboxes = <&ipcc ...>;
mbox-names = "dcp-power-tx";
};
The parent uses the standard VirtIO-MMIO interrupts property. The child
has one DCP-specific interrupt, so interrupt-names is unnecessary and
the driver uses interrupt index 0.
Option 2 — Add a Qualcomm-specific VirtIO-MMIO parent binding
Keep a Qualcomm-specific compatible such as:
compatible = "qcom,dcp-virtio", "virtio,mmio";
and extend the parent binding to describe iommu-ranges, the additional
IPCC interrupt, mailbox, and related DCP resources.
However, this approach duplicates the generic VirtIO-MMIO binding and is
only justified if the transport requires Qualcomm-specific handling.
Since the DCP transport is handled completely by the standard
VirtIO-MMIO driver.
Which one do you prefer?
I will be addressing other comments in v2.
Thanks
Jayant
>> + reg = <0x09be8000 0x1000>;
>> + interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
>> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
>> + interrupt-names = "virtio", "dcp_power_notify_irq";
> Why do you need names? Can they be dropped in favour of using indices?
>
>> + 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@1 {
>> + reg = <1>;
>> + dcp_intf1_out: endpoint {
>> + remote-endpoint = <&dcp_dsi0_in>;
> Can this point directly to the panel_in? What's the point of describing
> internal paths in the DT?
>
>> + };
>> + };
>> + };
>> +
>> + dsi@0 {
>> + reg = <0>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + ports {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + port@0 {
>> + reg = <0>;
>> + dcp_dsi0_in: endpoint {
>> + remote-endpoint = <&dcp_intf1_out>;
>> + };
>> + };
>> + };
>> + };
>> + };
>> + };
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings
2026-10-21 8:45 ` Jayant Shekhar
@ 2026-10-21 11:39 ` Jayant Shekhar
0 siblings, 0 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-21 11:39 UTC (permalink / raw)
To: Dmitry Baryshkov, Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On 21-10-2026 14:15, Jayant Shekhar wrote:
>
> On 15-10-2026 17:47, Dmitry Baryshkov wrote:
>> On Thu, Oct 15, 2026 at 01:08:21PM +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 firmware
>>> transport,
>>> display topology, IOMMU contexts, and panel connections.
>>>
>>> Add bindings for the Qualcomm DCP VirtIO display function, including
>>> the
>>> INTF graph and DSI host outputs. Document the framebuffer IOMMU
>>> context,
>>> IPCC power-notify mailbox and interrupts, firmware display topology,
>>> and
>>> firmware-visible IOVA window supplied by the parent VirtIO-MMIO
>>> transport.
>>>
>>> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
>>> Assisted-by: LLM
>>> ---
>>> .../bindings/display/msm/qcom,dcp-dsi.yaml | 75
>>> +++++++++++++
>>> .../bindings/display/msm/qcom,dcp-virtio.yaml | 113
>>> +++++++++++++++++++
>>> .../devicetree/bindings/display/msm/qcom,dcp.yaml | 119
>>> +++++++++++++++++++++
>>> 3 files changed, 307 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..f7178c66c09e
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/display/msm/qcom,dcp-dsi.yaml
>>> @@ -0,0 +1,75 @@
>>> +# 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). DCP is a firmware protocol shared across multiple
>>> + Qualcomm SoCs, not specific to any one of them; Hawi is its first
>>> + platform. The DSI host and its connection to a panel are described
>>> + using the standard DSI controller and OF graph properties.
>>> +
>>> +allOf:
>>> + - $ref: /schemas/display/dsi-controller.yaml#
>>> +
>>> +properties:
>>> + reg:
>>> + maxItems: 1
>>> + description: Index of this DSI host among its siblings.
>>> +
>>> + status: true
>>> +
>>> + ports:
>>> + $ref: /schemas/graph.yaml#/properties/ports
>>> + description: |
>>> + port@0: Input from the DCP virtio device's DPU INTF output port.
>>> + port@1: Output to the attached panel.
>>> + properties:
>>> + port@0:
>>> + $ref: /schemas/graph.yaml#/properties/port
>>> + port@1:
>>> + $ref: /schemas/graph.yaml#/$defs/port-base
>>> + unevaluatedProperties: false
>>> + properties:
>>> + endpoint:
>>> + $ref: /schemas/media/video-interfaces.yaml#
>>> + unevaluatedProperties: false
>> Why do we need OF graph here? Would it be enough to link one of the
>> ports of the root node with the panel's input?
>>
>>> +
>>> +required:
>>> + - reg
>>> + - ports
>>> +
>>> +unevaluatedProperties: false
>>> +
>>> +examples:
>>> + - |
>>> + dcp-subsystem {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + dsi@0 {
>>> + reg = <0>;
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + ports {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + port@0 {
>>> + reg = <0>;
>>> + dcp_dsi0_in: endpoint {
>>> + remote-endpoint = <&dcp_intf1_out>;
>>> + };
>>> + };
>>> +
>>> + };
>>> + };
>>> + };
>>> diff --git
>>> a/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
>>> b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
>>> new file mode 100644
>>> index 000000000000..018694310dfb
>>> --- /dev/null
>>> +++
>>> b/Documentation/devicetree/bindings/display/msm/qcom,dcp-virtio.yaml
>>> @@ -0,0 +1,113 @@
>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/display/msm/qcom,dcp-virtio.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Qualcomm DCP VirtIO host
>>> +
>>> +maintainers:
>>> + - Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
>>> +
>>> +description: |
>>> + Qualcomm's DCP VirtIO host provides the generic VirtIO-MMIO
>>> transport and
>>> + the host-side IPCC channel used to wake the DCP firmware.
>>> +
>>> +properties:
>>> + compatible:
>>> + contains:
>>> + const: qcom,dcp-virtio
>>> +
>>> + reg:
>>> + maxItems: 1
>>> +
>>> + iommus:
>>> + maxItems: 1
>>> +
>>> + iommu-ranges:
>>> + $ref: /schemas/types.yaml#/definitions/uint32-array
>>> +
>>> + interrupts-extended:
>>> + minItems: 2
>>> +
>>> + interrupt-names:
>>> + items:
>>> + - const: virtio
>>> + - const: dcp_power_notify_irq
>>> +
>>> + dma-coherent: true
>>> +
>>> + mboxes:
>>> + maxItems: 1
>>> +
>>> + mbox-names:
>>> + const: dcp-power-tx
>>> +
>>> + dcp-subsystem:
>>> + $ref: qcom,dcp.yaml#
>>> +
>>> +required:
>>> + - compatible
>>> + - reg
>>> + - interrupts-extended
>>> + - interrupt-names
>>> + - iommus
>>> + - dcp-subsystem
>>> +
>>> +additionalProperties: false
>>> +
>>> +examples:
>>> + - |
>>> + #include <dt-bindings/interrupt-controller/arm-gic.h>
>>> + #include <dt-bindings/mailbox/qcom-ipcc.h>
>>> +
>>> + virtio@9be8000 {
>>> + compatible = "qcom,dcp-virtio", "virtio,mmio";
>> Is it really compatible with virtio,mmio? Can it be handled completely
>> by virtio,mmio?
>
>
> There are two possible approaches:
>
> Option 1 — Use the standard VirtIO-MMIO parent (preferred)
>
> Keep the parent as:
>
> compatible = "virtio,mmio";
> reg = <...>;
> interrupts = <...>;
> iommus = <...>;
> iommu-ranges = <...>;
> dma-coherent;
>
> The generic virtio/mmio.yaml binding would be extended only to
> document the generic iommu-ranges property.
>
> The DCP-specific resources would be placed on the virtio,deviceXX child:
>
> dcp-subsystem {
> compatible = "virtio,deviceXX";
> iommus = <...>;
> interrupts-extended = <&ipcc ...>;
> mboxes = <&ipcc ...>;
> mbox-names = "dcp-power-tx";
> };
>
> The parent uses the standard VirtIO-MMIO interrupts property. The
> child has one DCP-specific interrupt, so interrupt-names is
> unnecessary and the driver uses interrupt index 0.
>
> Option 2 — Add a Qualcomm-specific VirtIO-MMIO parent binding
>
> Keep a Qualcomm-specific compatible such as:
>
> compatible = "qcom,dcp-virtio", "virtio,mmio";
>
> and extend the parent binding to describe iommu-ranges, the additional
> IPCC interrupt, mailbox, and related DCP resources.
>
> However, this approach duplicates the generic VirtIO-MMIO binding and
> is only justified if the transport requires Qualcomm-specific
> handling. Since the DCP transport is handled completely by the
> standard VirtIO-MMIO driver.
>
> Which one do you prefer?
>
> I will be addressing other comments in v2.
>
>
> Thanks
>
> Jayant
>
>
I think Option 1 is not suitable because the IPCC power-notify sequence
is required before the first VirtIO-MMIO register access. The standard
VirtIO-MMIO driver would otherwise probe the DT node directly and access
the registers before Qualcomm-specific initialization is complete.
Would the following Qualcomm-specific transport approach be acceptable?
- qcom,dcp-virtio is the DT-compatible Qualcomm wrapper. It
initializes IPCC and sends the required power notification before
creating a synthetic virtio-mmio platform device.
- The unchanged generic VirtIO-MMIO driver probes the synthetic
child, accesses the MMIO registers, creates the VirtIO queues, and
registers the VirtIO device.
- The DCP VirtIO driver binds to the resulting VirtIO device through
its VirtIO device/vendor ID table and initializes HFI, firmware
capabilities, and DRM/KMS.
- The virtio,deviceXX DT child is discovered by the VirtIO core and
describes the DCP-specific IOMMU context, DSI hosts, display graph, and
panels.
The flow is:
qcom,dcp-virtio
→ IPCC power-up
→ synthetic virtio-mmio device
→ generic VirtIO-MMIO driver
→ virtio,deviceXX DT child
→ DCP HFI/DRM driver
Since the Qualcomm wrapper creates and owns the synthetic VirtIO-MMIO
device, the parent node uses only:
compatible = "qcom,dcp-virtio";
It should not also advertise "virtio,mmio", because the generic
driver must not probe the parent directly before IPCC initialization.
>>> + reg = <0x09be8000 0x1000>;
>>> + interrupts-extended = <&intc GIC_ESPI 956
>>> IRQ_TYPE_LEVEL_HIGH>,
>>> + <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
>>> + interrupt-names = "virtio", "dcp_power_notify_irq";
>> Why do you need names? Can they be dropped in favour of using indices?
>>
>>> + 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@1 {
>>> + reg = <1>;
>>> + dcp_intf1_out: endpoint {
>>> + remote-endpoint = <&dcp_dsi0_in>;
>> Can this point directly to the panel_in? What's the point of describing
>> internal paths in the DT?
>>
>>> + };
>>> + };
>>> + };
>>> +
>>> + dsi@0 {
>>> + reg = <0>;
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + ports {
>>> + #address-cells = <1>;
>>> + #size-cells = <0>;
>>> +
>>> + port@0 {
>>> + reg = <0>;
>>> + dcp_dsi0_in: endpoint {
>>> + remote-endpoint = <&dcp_intf1_out>;
>>> + };
>>> + };
>>> + };
>>> + };
>>> + };
>>> + };
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 02/13] drm/msm/dcp: Add HFI protocol definitions
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
2026-10-15 7:38 ` [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 7:38 ` [PATCH 03/13] drm/msm/dcp: Add HFI transport over VirtIO Jayant Shekhar
` (10 subsequent siblings)
12 siblings, 0 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
DCP firmware owns the display hardware and communicates with Linux through
the HFI protocol. The driver needs a single definition of the packet and
property formats shared by transport, capability, modeset, and event code.
Add the HFI packet formats, commands, properties, response structures, and
common pack, unpack, and utility helpers used by the DCP driver.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Kconfig | 25 ++
drivers/gpu/drm/msm/Makefile | 6 +
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 | 79 ++++
drivers/gpu/drm/msm/dcp/hfi/hfi_utils.h | 56 +++
22 files changed, 3047 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 0b8f2cafed19..00c995dde21a 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 \
@@ -68,6 +69,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_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_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_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_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_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_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_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_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..2f750182aa0b
--- /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_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__ */
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__ */
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__ */
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..1a06c3937bda
--- /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..87ea51132bb8
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/hfi/hfi_utils.c
@@ -0,0 +1,79 @@
+// 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"
+/**
+ * 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)
+{
+ 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;
+}
+
+/**
+ * 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)
+{
+ 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] 27+ messages in thread* [PATCH 03/13] drm/msm/dcp: Add HFI transport over VirtIO
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
2026-10-15 7:38 ` [PATCH 01/13] dt-bindings: display: msm: Add Qualcomm DCP bindings Jayant Shekhar
2026-10-15 7:38 ` [PATCH 02/13] drm/msm/dcp: Add HFI protocol definitions Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 12:36 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 04/13] drm/msm/dcp: Add IPCC power notification Jayant Shekhar
` (9 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
DCP firmware owns the display hardware, so the MSM DRM driver needs a
transport for HFI commands, responses, and asynchronous display events.
Add the VirtIO command and response queues used to communicate with DCP.
Allocate command, response, and capability buffers through the DMA API
using the firmware-visible IOVA window. Track synchronous commands and
asynchronous responses, dispatch HFI callbacks, and synchronize queue
teardown with in-flight transactions.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_drv.h | 30 +
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 214 ++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 1198 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h | 74 ++
5 files changed, 1517 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 00c995dde21a..bdc34babfc21 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -70,6 +70,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..bf6c34af43a0
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -0,0 +1,30 @@
+/* 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/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/virtio.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..726087e5c8a2
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -0,0 +1,214 @@
+/* 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 <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;
+struct dcp_hfi_caps_cache;
+
+/* 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_FEAT_DSC BIT(0)
+#define DCP_FEAT_HDR BIT(1)
+#define DCP_FEAT_SRC_SPLIT BIT(3)
+#define DCP_FEAT_CURSOR BIT(4)
+
+#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];
+
+ u32 num_crtcs;
+
+ u32 dsi_count;
+ u32 dsi_indices[DCP_MAX_BLOCKS];
+ u32 max_dsi_resolution;
+
+ u32 dp_count;
+ u32 dp_indices[DCP_MAX_BLOCKS];
+
+ u32 active_pipes_mask[DCP_MAX_DISPLAYS];
+
+ u32 max_width;
+ u32 max_height;
+ u32 max_planes;
+ u32 features;
+ bool valid;
+
+ bool dyn_clk_support;
+};
+
+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;
+};
+
+#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 {
+ 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. */
+ spinlock_t pending_lock;
+ atomic_t packet_id;
+ 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 caps_cache pointer and its lifetime. */
+ spinlock_t caps_lock;
+ /* Serializes capability transactions and cache detach/free. */
+ struct mutex caps_cache_lock;
+ struct dcp_hfi_caps_cache *caps_cache;
+
+ void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);
+ void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
+
+ 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,
+ struct virtqueue *cmd_vq, struct virtqueue *resp_vq,
+ struct device *dma_dev);
+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);
+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..bf27cbf9b8a4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -0,0 +1,1198 @@
+// 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/unaligned.h>
+#include <linux/slab.h>
+#include <linux/scatterlist.h>
+#include <linux/spinlock.h>
+#include <linux/virtio.h>
+#include <linux/workqueue.h>
+#include <uapi/linux/virtio_mmio.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_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)
+{
+ u32 id;
+
+ id = (u32)atomic_inc_return(&hfi->packet_id);
+ if (id == 0)
+ id = (u32)atomic_inc_return(&hfi->packet_id);
+ return id;
+}
+
+#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(struct dcp_hfi *hfi, struct dcp_hfi_buf *buf,
+ struct list_head *free_list, spinlock_t *lock)
+{
+ unsigned long flags;
+
+ buf->comp = NULL;
+ spin_lock_irqsave(lock, flags);
+ list_add_tail(&buf->node, free_list);
+ spin_unlock_irqrestore(lock, flags);
+}
+
+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)
+{
+ _dcp_hfi_buf_free(hfi, buf, &hfi->cmd_free_list, &hfi->cmd_lock);
+}
+
+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);
+}
+
+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: {
+ u32 intf_idx = pkt_info->id & 0xFFFF;
+ bool connected = false;
+
+ if (pkt_info->payload_ptr &&
+ pkt_info->payload_size >=
+ sizeof(struct hfi_display_hpd_status)) {
+ struct hfi_display_hpd_status *hpd =
+ pkt_info->payload_ptr;
+ connected = (hpd->dp_state ==
+ HFI_DP_STATE_CONNECTED);
+ }
+ if (hfi->hotplug_cb)
+ hfi->hotplug_cb(hfi->drv, intf_idx, connected);
+ return true;
+ }
+
+ case HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD:
+ dev_err(hfi->dev,
+ "DCP: panel dead display %u\n",
+ pkt_info->id & 0xFFFF);
+ if (hfi->error_cb)
+ hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 0);
+ return true;
+
+ case HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY:
+ dev_err(hfi->dev,
+ "DCP: HW recovery display %u\n",
+ pkt_info->id & 0xFFFF);
+ if (hfi->error_cb)
+ hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 1);
+ 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: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache)
+ dcp_hfi_store_caps_payload(hfi, &cache->device_done,
+ cache->device_buf, &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: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache)
+ dcp_hfi_store_caps_payload(hfi, &cache->display_done,
+ cache->display_buf, &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: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache) {
+ dcp_hfi_store_caps_payload(hfi, NULL, cache->vig_buf,
+ &cache->vig_len, pkt_info, "VIG_CAPS");
+ cache->pipe_caps_received |= BIT(0);
+ }
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_CAPS: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache) {
+ dcp_hfi_store_caps_payload(hfi, NULL, cache->dma_buf,
+ &cache->dma_len, pkt_info, "DMA_CAPS");
+ cache->pipe_caps_received |= BIT(1);
+ }
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_VIG_R1_CAPS: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache) {
+ dcp_hfi_store_caps_payload(hfi, NULL, cache->vig_r1_buf,
+ &cache->vig_r1_len, pkt_info, "VIG_R1_CAPS");
+ cache->pipe_caps_received |= BIT(2);
+ }
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_DMA_R1_CAPS: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache) {
+ dcp_hfi_store_caps_payload(hfi, NULL, cache->dma_r1_buf,
+ &cache->dma_r1_len, pkt_info, "DMA_R1_CAPS");
+ cache->pipe_caps_received |= BIT(3);
+ }
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ return true;
+ }
+
+ case HFI_COMMAND_DEVICE_INIT_COMMON_LAYER_CAPS: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache)
+ 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: {
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ if (cache)
+ 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;
+ 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 (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,
+ "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_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)
+{
+ 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->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);
+
+ INIT_LIST_HEAD(&hfi->cmd_free_list);
+
+ 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);
+ 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;
+
+ hfi->caps_cache = NULL;
+ 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_work_sync(&hfi->resource_vote_work);
+ 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_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;
+
+ if (!hfi)
+ return;
+
+ spin_lock_irqsave(&hfi->cmd_lock, flags);
+ 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);
+}
+
+void dcp_hfi_quiesce_queues(struct dcp_hfi *hfi)
+{
+ unsigned long flags;
+
+ if (!hfi)
+ return;
+
+ 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)
+{
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ if (!hfi)
+ return;
+
+ dcp_hfi_stop(hfi);
+
+ mutex_lock(&hfi->caps_cache_lock);
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ hfi->caps_cache = NULL;
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ kfree(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);
+ 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..4f7d0ff3d9f5
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_priv.h
@@ -0,0 +1,74 @@
+/* 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_CAPS_BUF_SIZE DCP_HFI_BUF_SIZE
+
+#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_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;
+};
+
+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] 27+ messages in thread* Re: [PATCH 03/13] drm/msm/dcp: Add HFI transport over VirtIO
2026-10-15 7:38 ` [PATCH 03/13] drm/msm/dcp: Add HFI transport over VirtIO Jayant Shekhar
@ 2026-10-15 12:36 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 12:36 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:23PM +0530, Jayant Shekhar wrote:
> DCP firmware owns the display hardware, so the MSM DRM driver needs a
> transport for HFI commands, responses, and asynchronous display events.
>
> Add the VirtIO command and response queues used to communicate with DCP.
> Allocate command, response, and capability buffers through the DMA API
> using the firmware-visible IOVA window. Track synchronous commands and
> asynchronous responses, dispatch HFI callbacks, and synchronize queue
> teardown with in-flight transactions.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
You haven't read Documentation/process/. Sad.
> 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..bf6c34af43a0
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> @@ -0,0 +1,30 @@
> +/* 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/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/notifier.h>
> +#include <linux/platform_device.h>
> +#include <linux/virtio.h>
Are all these headers required for this header?
> +
> +#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__ */
> +
> +#define DCP_MAX_DISPLAYS 4
> +
> +#define DCP_FEAT_DSC BIT(0)
> +#define DCP_FEAT_HDR BIT(1)
> +#define DCP_FEAT_SRC_SPLIT BIT(3)
> +#define DCP_FEAT_CURSOR BIT(4)
What kind of features are those?
> +
> +#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];
> +
> + u32 num_crtcs;
CRTCs? Or mixers?
> +
> + u32 dsi_count;
> + u32 dsi_indices[DCP_MAX_BLOCKS];
> + u32 max_dsi_resolution;
If these are parsed, why is it still u32 rather than two resolutions?
> +
> + u32 dp_count;
> + u32 dp_indices[DCP_MAX_BLOCKS];
Unused, drop
> +
> + u32 active_pipes_mask[DCP_MAX_DISPLAYS];
Unused. Drop all unused fields.
> +
> + u32 max_width;
> + u32 max_height;
max width/height of... what?
> + u32 max_planes;
> + u32 features;
> + bool valid;
How can it be invalid?
> +
> + bool dyn_clk_support;
??
> +};
> +
> +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;
> +};
> +
> +#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 {
> + 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. */
> + spinlock_t pending_lock;
> + atomic_t packet_id;
> + atomic_t shutdown_in_progress;
This all demands at least some description or kernel docs.
> +
> + 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 caps_cache pointer and its lifetime. */
> + spinlock_t caps_lock;
> + /* Serializes capability transactions and cache detach/free. */
> + struct mutex caps_cache_lock;
> + struct dcp_hfi_caps_cache *caps_cache;
What's the point of keeping it as a pointer? Just embed it here and drop
the locks. Read the caps while the devices is not visible so that there
are no concurrent users.
> +
> + void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);
Why do we need these callbacks? Can you call the functions directly?
> + void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
> +
> + 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,
> + struct virtqueue *cmd_vq, struct virtqueue *resp_vq,
> + struct device *dma_dev);
> +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);
> +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);
You haven't really followed the internal review feedback. You have been
asked to review the indentation. You didnt.
> +#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..bf27cbf9b8a4
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> @@ -0,0 +1,1198 @@
> +// 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/unaligned.h>
> +#include <linux/slab.h>
> +#include <linux/scatterlist.h>
> +#include <linux/spinlock.h>
> +#include <linux/virtio.h>
> +#include <linux/workqueue.h>
> +#include <uapi/linux/virtio_mmio.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_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)
> +{
> + u32 id;
> +
> + id = (u32)atomic_inc_return(&hfi->packet_id);
> + if (id == 0)
> + id = (u32)atomic_inc_return(&hfi->packet_id);
What if something looks at your counter inbetween? Two atomic operations
aren't atomic.
> + return id;
> +}
> +
> +#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)) {
This is called drm_dev_get().
> + 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(struct dcp_hfi *hfi, struct dcp_hfi_buf *buf,
> + struct list_head *free_list, spinlock_t *lock)
> +{
> + unsigned long flags;
> +
> + buf->comp = NULL;
> + spin_lock_irqsave(lock, flags);
> + list_add_tail(&buf->node, free_list);
> + spin_unlock_irqrestore(lock, flags);
Just call _dcp_hfi_buf_free_locked() under the lock.
> +}
> +
> +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);
> +}
> +
[..]
> +
> + case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS: {
> + u32 intf_idx = pkt_info->id & 0xFFFF;
> + bool connected = false;
> +
> + if (pkt_info->payload_ptr &&
> + pkt_info->payload_size >=
> + sizeof(struct hfi_display_hpd_status)) {
> + struct hfi_display_hpd_status *hpd =
> + pkt_info->payload_ptr;
> + connected = (hpd->dp_state ==
> + HFI_DP_STATE_CONNECTED);
> + }
> + if (hfi->hotplug_cb)
> + hfi->hotplug_cb(hfi->drv, intf_idx, connected);
> + return true;
> + }
> +
> + case HFI_COMMAND_DISPLAY_EVENT_PANEL_DEAD:
> + dev_err(hfi->dev,
> + "DCP: panel dead display %u\n",
> + pkt_info->id & 0xFFFF);
> + if (hfi->error_cb)
> + hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 0);
> + return true;
> +
> + case HFI_COMMAND_DISPLAY_EVENT_HW_RECOVERY:
> + dev_err(hfi->dev,
> + "DCP: HW recovery display %u\n",
> + pkt_info->id & 0xFFFF);
> + if (hfi->error_cb)
> + hfi->error_cb(hfi->drv, pkt_info->id & 0xFFFF, 1);
Yep... Drop the callbacks. I think it was also a part of the review.
> + return true;
> +
Can you split the patches? It really becomes troublesome to review.
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 04/13] drm/msm/dcp: Add IPCC power notification
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (2 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 03/13] drm/msm/dcp: Add HFI transport over VirtIO Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 12:42 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 05/13] drm/msm/dcp: Add HFI display command implementations Jayant Shekhar
` (8 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
DCP may be power-collapsed while the VirtIO transport remains available.
The host therefore needs the platform IPCC channel to wake the firmware and
to receive notification that it can service HFI queues.
Add the IPCC mailbox client and power-notify interrupt handling. Wake DCP
before VirtIO queue activity, wait for the firmware acknowledgment, and
notify the response queue when the IPCC receive path completes.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_drv.h | 1 +
drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 212 +++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_ipcc.h | 56 ++++++++++
4 files changed, 270 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index bdc34babfc21..4195bc0e1fc9 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -71,6 +71,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 bf6c34af43a0..af2db3cf9066 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -13,6 +13,7 @@
#include <linux/virtio.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..e60a3ecbe3d8
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
@@ -0,0 +1,212 @@
+// 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;
+ }
+
+ if (atomic_xchg(&ipcc->power_notify_pending, 0))
+ 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);
+}
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev)
+{
+ int irq, ret;
+
+ if (!ipcc || !dev)
+ return -EINVAL;
+
+ memset(ipcc, 0, sizeof(*ipcc));
+ init_completion(&ipcc->power_notify_done);
+ mutex_init(&ipcc->power_notify_lock);
+ spin_lock_init(&ipcc->state_lock);
+ atomic_set(&ipcc->power_notify_pending, 0);
+
+ 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_byname(&ipcc->client,
+ "dcp-power-tx");
+ if (IS_ERR(ipcc->power_chan)) {
+ ret = PTR_ERR(ipcc->power_chan);
+ ipcc->power_chan = NULL;
+ if (ret != -EPROBE_DEFER)
+ dev_err(dev, "IPCC: failed to get 'dcp-power-tx': %d\n", ret);
+ return ret;
+ }
+
+ irq = of_irq_get_byname(dev->of_node, "dcp_power_notify_irq");
+ if (irq <= 0) {
+ ret = irq ? irq : -EINVAL;
+ ret = dev_err_probe(dev, ret,
+ "failed to get required power notify IRQ\n");
+ goto err_free_channel;
+ }
+
+ ret = request_irq(irq, dcp_ipcc_power_notify_irq,
+ 0, "dcp-power-notify", ipcc);
+ if (ret) {
+ ret = dev_err_probe(dev, ret,
+ "failed to request power notify IRQ %d\n", irq);
+ goto err_free_channel;
+ }
+ ipcc->power_notify_irq = irq;
+
+ ret = enable_irq_wake(irq);
+ if (ret) {
+ dev_err(dev, "failed to enable power notify IRQ wake: %d\n", ret);
+ goto err_free_irq;
+ }
+ ipcc->power_notify_irq_wake = true;
+
+ return 0;
+
+err_free_irq:
+ free_irq(irq, ipcc);
+ ipcc->power_notify_irq = 0;
+err_free_channel:
+ mbox_free_channel(ipcc->power_chan);
+ ipcc->power_chan = NULL;
+ return ret;
+}
+
+void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq)
+{
+ unsigned long flags;
+
+ if (!ipcc)
+ return;
+
+ 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 mbox_chan *power_chan;
+ int irq;
+ bool irq_wake;
+ unsigned long flags;
+
+ if (!ipcc)
+ return;
+
+ mutex_lock(&ipcc->power_notify_lock);
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ ipcc->shutting_down = true;
+ ipcc->resp_vq = NULL;
+ irq = ipcc->power_notify_irq;
+ ipcc->power_notify_irq = 0;
+ irq_wake = ipcc->power_notify_irq_wake;
+ ipcc->power_notify_irq_wake = false;
+ power_chan = ipcc->power_chan;
+ ipcc->power_chan = NULL;
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+
+ if (irq > 0) {
+ disable_irq(irq);
+ if (irq_wake)
+ disable_irq_wake(irq);
+ free_irq(irq, ipcc);
+ }
+ if (power_chan)
+ mbox_free_channel(power_chan);
+ mutex_unlock(&ipcc->power_notify_lock);
+}
+
+int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
+{
+ unsigned long flags, timeout;
+ int ret;
+
+ if (!ipcc)
+ return -ENODEV;
+
+ /* Wait for the doorbell acknowledgment before returning to VirtIO. */
+ mutex_lock(&ipcc->power_notify_lock);
+ spin_lock_irqsave(&ipcc->state_lock, flags);
+ if (ipcc->shutting_down || !ipcc->power_chan) {
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+ ret = -ENODEV;
+ goto out_unlock;
+ }
+ spin_unlock_irqrestore(&ipcc->state_lock, flags);
+
+ reinit_completion(&ipcc->power_notify_done);
+ atomic_set(&ipcc->power_notify_pending, 1);
+
+ ret = mbox_send_message(ipcc->power_chan, NULL);
+ mbox_client_txdone(ipcc->power_chan, ret);
+ if (ret < 0) {
+ atomic_set(&ipcc->power_notify_pending, 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));
+ atomic_set(&ipcc->power_notify_pending, 0);
+ 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..a3430e5cbe72
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
@@ -0,0 +1,56 @@
+/* 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/atomic.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 - DCP IPCC power-notification state
+ * @client: Mailbox client used for APSS-to-DCP notifications.
+ * @power_chan: Mailbox channel carrying power notifications.
+ * @resp_vq: VirtIO response queue notified by the IPCC receive path.
+ * @power_notify_done: Completion for the firmware acknowledgment.
+ * @power_notify_lock: Serializes power notifications and teardown.
+ * @power_notify_pending: Indicates an outstanding acknowledgment.
+ * @state_lock: Protects shutdown state and response queue access.
+ * @shutting_down: Prevents callbacks from accessing teardown state.
+ * @power_notify_irq_wake: Whether IRQ wake was enabled.
+ * @power_notify_irq: Power-notify acknowledgment IRQ number.
+ */
+struct dcp_ipcc {
+ struct mbox_client client;
+ struct mbox_chan *power_chan;
+ struct virtqueue *resp_vq;
+ struct completion power_notify_done;
+ /* Serializes power notifications and teardown. */
+ struct mutex power_notify_lock;
+ atomic_t power_notify_pending;
+ /* Protects shutdown state and response queue access. */
+ spinlock_t state_lock;
+ bool shutting_down;
+ bool power_notify_irq_wake;
+ int power_notify_irq;
+};
+
+int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev);
+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);
+
+#endif /* __DCP_IPCC_H__ */
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 04/13] drm/msm/dcp: Add IPCC power notification
2026-10-15 7:38 ` [PATCH 04/13] drm/msm/dcp: Add IPCC power notification Jayant Shekhar
@ 2026-10-15 12:42 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 12:42 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:24PM +0530, Jayant Shekhar wrote:
> DCP may be power-collapsed while the VirtIO transport remains available.
> The host therefore needs the platform IPCC channel to wake the firmware and
> to receive notification that it can service HFI queues.
Is it allowed per the VirtIO spec?
>
> Add the IPCC mailbox client and power-notify interrupt handling. Wake DCP
> before VirtIO queue activity, wait for the firmware acknowledgment, and
> notify the response queue when the IPCC receive path completes.
Don't describe patch contents. Describe your design decisions. The
reasons for the flags, init/deinit functions, etc.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/dcp/dcp_drv.h | 1 +
> drivers/gpu/drm/msm/dcp/dcp_ipcc.c | 212 +++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_ipcc.h | 56 ++++++++++
> 4 files changed, 270 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index bdc34babfc21..4195bc0e1fc9 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -71,6 +71,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 bf6c34af43a0..af2db3cf9066 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> @@ -13,6 +13,7 @@
> #include <linux/virtio.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..e60a3ecbe3d8
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.c
> @@ -0,0 +1,212 @@
> +// 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;
> + }
> +
> + if (atomic_xchg(&ipcc->power_notify_pending, 0))
> + complete(&ipcc->power_notify_done);
Can there be more than one power notification pending? Can't we just
call complete() here?
> +
> + 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);
> +}
> +
> +int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev)
> +{
> + int irq, ret;
> +
> + if (!ipcc || !dev)
> + return -EINVAL;
> +
> + memset(ipcc, 0, sizeof(*ipcc));
> + init_completion(&ipcc->power_notify_done);
> + mutex_init(&ipcc->power_notify_lock);
> + spin_lock_init(&ipcc->state_lock);
> + atomic_set(&ipcc->power_notify_pending, 0);
> +
> + 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_byname(&ipcc->client,
> + "dcp-power-tx");
> + if (IS_ERR(ipcc->power_chan)) {
> + ret = PTR_ERR(ipcc->power_chan);
> + ipcc->power_chan = NULL;
> + if (ret != -EPROBE_DEFER)
> + dev_err(dev, "IPCC: failed to get 'dcp-power-tx': %d\n", ret);
> + return ret;
> + }
> +
> + irq = of_irq_get_byname(dev->of_node, "dcp_power_notify_irq");
> + if (irq <= 0) {
> + ret = irq ? irq : -EINVAL;
> + ret = dev_err_probe(dev, ret,
> + "failed to get required power notify IRQ\n");
> + goto err_free_channel;
> + }
> +
> + ret = request_irq(irq, dcp_ipcc_power_notify_irq,
> + 0, "dcp-power-notify", ipcc);
> + if (ret) {
> + ret = dev_err_probe(dev, ret,
> + "failed to request power notify IRQ %d\n", irq);
> + goto err_free_channel;
> + }
> + ipcc->power_notify_irq = irq;
> +
> + ret = enable_irq_wake(irq);
This is typically called from the suspend callback. Why are you calling
it in init path?
> + if (ret) {
> + dev_err(dev, "failed to enable power notify IRQ wake: %d\n", ret);
> + goto err_free_irq;
> + }
> + ipcc->power_notify_irq_wake = true;
> +
> + return 0;
> +
> +err_free_irq:
> + free_irq(irq, ipcc);
> + ipcc->power_notify_irq = 0;
> +err_free_channel:
> + mbox_free_channel(ipcc->power_chan);
> + ipcc->power_chan = NULL;
> + return ret;
> +}
> +
> +void dcp_ipcc_set_resp_vq(struct dcp_ipcc *ipcc, struct virtqueue *resp_vq)
> +{
> + unsigned long flags;
> +
> + if (!ipcc)
> + return;
> +
> + 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 mbox_chan *power_chan;
> + int irq;
> + bool irq_wake;
> + unsigned long flags;
> +
> + if (!ipcc)
> + return;
Oh, you again ignored the feedback. Can ipcc be NULL here? What would
that mean?
> +
> + mutex_lock(&ipcc->power_notify_lock);
> + spin_lock_irqsave(&ipcc->state_lock, flags);
> + ipcc->shutting_down = true;
> + ipcc->resp_vq = NULL;
> + irq = ipcc->power_notify_irq;
> + ipcc->power_notify_irq = 0;
> + irq_wake = ipcc->power_notify_irq_wake;
> + ipcc->power_notify_irq_wake = false;
> + power_chan = ipcc->power_chan;
> + ipcc->power_chan = NULL;
> + spin_unlock_irqrestore(&ipcc->state_lock, flags);
Ugh. No.
> +
> + if (irq > 0) {
Can it be 0 here?
> + disable_irq(irq);
What for?
> + if (irq_wake)
> + disable_irq_wake(irq);
It is enabled at this point. Drop the useless flags.
> + free_irq(irq, ipcc);
We have devm_foo() functions to simplify the teardown. Can we use them
here?
> + }
> + if (power_chan)
> + mbox_free_channel(power_chan);
> + mutex_unlock(&ipcc->power_notify_lock);
> +}
> +
> +int dcp_ipcc_send_power_notify(struct dcp_ipcc *ipcc)
> +{
> + unsigned long flags, timeout;
> + int ret;
> +
> + if (!ipcc)
> + return -ENODEV;
> +
> + /* Wait for the doorbell acknowledgment before returning to VirtIO. */
> + mutex_lock(&ipcc->power_notify_lock);
> + spin_lock_irqsave(&ipcc->state_lock, flags);
> + if (ipcc->shutting_down || !ipcc->power_chan) {
> + spin_unlock_irqrestore(&ipcc->state_lock, flags);
> + ret = -ENODEV;
> + goto out_unlock;
> + }
> + spin_unlock_irqrestore(&ipcc->state_lock, flags);
> +
> + reinit_completion(&ipcc->power_notify_done);
> + atomic_set(&ipcc->power_notify_pending, 1);
> +
> + ret = mbox_send_message(ipcc->power_chan, NULL);
> + mbox_client_txdone(ipcc->power_chan, ret);
> + if (ret < 0) {
> + atomic_set(&ipcc->power_notify_pending, 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));
> + atomic_set(&ipcc->power_notify_pending, 0);
> + 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..a3430e5cbe72
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_ipcc.h
> @@ -0,0 +1,56 @@
> +/* 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/atomic.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 - DCP IPCC power-notification state
> + * @client: Mailbox client used for APSS-to-DCP notifications.
> + * @power_chan: Mailbox channel carrying power notifications.
> + * @resp_vq: VirtIO response queue notified by the IPCC receive path.
> + * @power_notify_done: Completion for the firmware acknowledgment.
> + * @power_notify_lock: Serializes power notifications and teardown.
> + * @power_notify_pending: Indicates an outstanding acknowledgment.
> + * @state_lock: Protects shutdown state and response queue access.
> + * @shutting_down: Prevents callbacks from accessing teardown state.
What is going to access it during teardown? Can we shutdown in a logical
way, so that there are no accessors after starting the teardown?
> + * @power_notify_irq_wake: Whether IRQ wake was enabled.
> + * @power_notify_irq: Power-notify acknowledgment IRQ number.
> + */
> +struct dcp_ipcc {
> + struct mbox_client client;
> + struct mbox_chan *power_chan;
> + struct virtqueue *resp_vq;
> + struct completion power_notify_done;
> + /* Serializes power notifications and teardown. */
> + struct mutex power_notify_lock;
> + atomic_t power_notify_pending;
> + /* Protects shutdown state and response queue access. */
> + spinlock_t state_lock;
> + bool shutting_down;
> + bool power_notify_irq_wake;
> + int power_notify_irq;
> +};
> +
> +int dcp_ipcc_init(struct dcp_ipcc *ipcc, struct device *dev);
> +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);
> +
> +#endif /* __DCP_IPCC_H__ */
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 05/13] drm/msm/dcp: Add HFI display command implementations
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (3 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 04/13] drm/msm/dcp: Add IPCC power notification Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 7:38 ` [PATCH 06/13] drm/msm/dcp: Add planes, CRTCs, and HFI event routing Jayant Shekhar
` (7 subsequent siblings)
12 siblings, 0 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
Because DCP owns the display hardware, DRM state must be translated into
the firmware protocol instead of being written to display registers.
Add HFI command helpers for capabilities, display resources, CRTCs, planes,
encoders, DSI, and panel configuration. Translate modeset and plane state
into HFI properties for bandwidth, layers, mixers, frame sequencing, and
frame triggering, and add panel initialization and DCS transfer support.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 6 +
drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h | 77 ++++
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 57 +++
drivers/gpu/drm/msm/dcp/dcp_hfi_crtc.c | 191 +++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c | 253 ++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c | 601 ++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_encoder.c | 64 +++
drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c | 451 +++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c | 64 +++
9 files changed, 1764 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 4195bc0e1fc9..039c2e697258 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -71,6 +71,12 @@ 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 \
dcp/dcp_ipcc.o \
dcp/hfi/hfi_packer.o \
dcp/hfi/hfi_unpacker.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..a46cd9f4a7dc
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
@@ -0,0 +1,77 @@
+/* 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 - Firmware-only DSI panel properties.
+ */
+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;
+ 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 726087e5c8a2..664d44f0e48b 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -211,4 +211,61 @@ 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);
+
+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__ */
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..14a6db46db87
--- /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_dsi.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
new file mode 100644
index 000000000000..de34a6c6783b
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi.c
@@ -0,0 +1,253 @@
+// 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/completion.h>
+#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..48a87d52f697
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_dsi_panel.c
@@ -0,0 +1,601 @@
+// 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_format_bpp(enum mipi_dsi_pixel_format format)
+{
+ int bpp;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(format);
+ if (bpp < 0)
+ return 24;
+
+ return bpp;
+}
+
+static u32 dcp_dsi_traffic_mode(const struct mipi_dsi_device *dsi)
+{
+ if (!dsi || !(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 = dcp_dsi_format_bpp(dsi->format);
+ 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;
+}
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..c86046fbc730
--- /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..1246620fc6d5
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_kms.c
@@ -0,0 +1,451 @@
+// 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 int dcp_hfi_caps_cache_start(struct dcp_hfi *hfi)
+{
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ cache = kzalloc_obj(*cache, GFP_KERNEL);
+ if (!cache)
+ return -ENOMEM;
+
+ dcp_hfi_caps_cache_init(cache);
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ if (hfi->caps_cache) {
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+ kfree(cache);
+ return -EBUSY;
+ }
+ hfi->caps_cache = cache;
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+
+ return 0;
+}
+
+static struct dcp_hfi_caps_cache *dcp_hfi_caps_cache_detach(struct dcp_hfi *hfi)
+{
+ struct dcp_hfi_caps_cache *cache;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hfi->caps_lock, flags);
+ cache = hfi->caps_cache;
+ hfi->caps_cache = NULL;
+ spin_unlock_irqrestore(&hfi->caps_lock, flags);
+
+ return cache;
+}
+
+static void dcp_hfi_caps_cache_stop(struct dcp_hfi *hfi)
+{
+ kfree(dcp_hfi_caps_cache_detach(hfi));
+}
+
+int dcp_hfi_send_device_init(struct dcp_hfi *hfi)
+{
+ int ret;
+
+ mutex_lock(&hfi->caps_cache_lock);
+ ret = dcp_hfi_caps_cache_start(hfi);
+ if (ret) {
+ mutex_unlock(&hfi->caps_cache_lock);
+ return ret;
+ }
+
+ /* 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_crtcs = 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:
+ caps->max_dsi_resolution = payload[0];
+ {
+ 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;
+ case HFI_PROPERTY_DEVICE_INIT_DP_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->dp_count = count;
+ for (i = 0; i < count; i++)
+ caps->dp_indices[i] = payload[1 + i];
+ break;
+
+ case HFI_PROPERTY_DISPLAY_DYN_CLK_SUPPORT:
+
+ caps->dyn_clk_support = (payload[0] == HFI_TRUE);
+ break;
+ case HFI_PROPERTY_DEVICE_INIT_ACTIVE_LAYERS:
+ if (max_words < 1)
+ break;
+ {
+ u32 num_disp = payload[0];
+ u32 prop_sz = 2;
+
+ if (max_words < 1 + num_disp * prop_sz)
+ break;
+ for (i = 0; i < num_disp; i++) {
+ u32 disp_id = payload[1 + i * prop_sz];
+ u32 pipe_msk = payload[2 + i * prop_sz];
+
+ if (disp_id < DCP_MAX_DISPLAYS)
+ caps->active_pipes_mask[disp_id] = pipe_msk;
+ }
+ }
+ 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 (!cache) {
+ mutex_unlock(&hfi->caps_cache_lock);
+ return -ENODEV;
+ }
+
+ 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 — dyn_clk_support unavailable\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);
+ } else {
+ dev_dbg(hfi->dev,
+ "DISPLAY_CAPS: dyn_clk=%d\n",
+ caps->dyn_clk_support);
+ }
+ }
+ 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->valid = false;
+ caps->max_planes = caps->num_vig_pipes + caps->num_dma_pipes;
+ if (!caps->num_crtcs) {
+ 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;
+ }
+
+ caps->valid = true;
+
+ 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_crtcs, caps->max_width, caps->max_height,
+ caps->num_vig_pipes, caps->num_dma_pipes);
+
+out_free_cache:
+ kfree(dcp_hfi_caps_cache_detach(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..0573634025f4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_plane.c
@@ -0,0 +1,64 @@
+// 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 <linux/slab.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] 27+ messages in thread* [PATCH 06/13] drm/msm/dcp: Add planes, CRTCs, and HFI event routing
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (4 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 05/13] drm/msm/dcp: Add HFI display command implementations Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 13:18 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 07/13] drm/msm/dcp: Add DCP encoder Jayant Shekhar
` (6 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
The firmware-owned DCP pipeline still needs normal DRM atomic objects and
event handling so it can participate in the MSM DRM device.
Add DCP plane and CRTC objects, validate framebuffer and plane state,
allocate firmware display pipes, calculate bandwidth and clock votes, and
translate visible planes and CRTC state into HFI commands. Route VSYNC and
frame-scan completion events back to DRM and retain CRTC-local display
session state.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 3 +
drivers/gpu/drm/msm/dcp/dcp_crtc.c | 733 ++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_crtc.h | 104 ++++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 27 +
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 49 ++
drivers/gpu/drm/msm/dcp/dcp_hfi.h | 2 +
drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 21 +
drivers/gpu/drm/msm/dcp/dcp_plane.c | 1073 ++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_plane.h | 60 ++
9 files changed, 2072 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 039c2e697258..b7c1a2e9a19e 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -70,6 +70,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 \
@@ -78,6 +80,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_kms.o \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
+ dcp/dcp_plane.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_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
new file mode 100644
index 000000000000..ede81881d495
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
@@ -0,0 +1,733 @@
+// 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.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) {
+ 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;
+
+ if (!kms)
+ return MODE_ERROR;
+
+ 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_ERROR("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)) {
+ dev_dbg(crtc->dev->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) {
+ dev_err(crtc->dev->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)) {
+ dev_warn(crtc->dev->dev,
+ "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
+ dcp_crtc->display_id);
+ return;
+ }
+
+ dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
+ atomic_inc(&dcp_crtc->pending_kickoff_cnt);
+
+ ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
+ if (ret) {
+ dev_err(crtc->dev->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)) {
+ dev_err(crtc->dev->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;
+ if (!dcp_kms)
+ return NULL;
+ if (session_id >= DCP_MAX_DISPLAYS)
+ return NULL;
+
+ 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;
+}
+
+static struct drm_device *dcp_crtc_get_drm_dev(struct dcp_drv *drv)
+{
+ struct dcp_dpu_kms *dcp_kms;
+
+ if (!drv || !drv->kms)
+ return NULL;
+
+ dcp_kms = drv->kms;
+ if (!dcp_kms)
+ return NULL;
+
+ return dcp_kms->dev;
+}
+
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
+{
+ struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc)
+ dcp_crtc_vblank_callback(crtc);
+ else if (dcp_crtc_get_drm_dev(drv))
+ drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
+ "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_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc) {
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ if (sequence_id != dcp_crtc->pending_seq_id) {
+ dev_warn(crtc->dev->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 (dcp_crtc_get_drm_dev(drv))
+ drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
+ "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;
+ dev_dbg(crtc->dev->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) {
+ dev_err(crtc->dev->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) {
+ dev_err(crtc->dev->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) {
+ dev_err(crtc->dev->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) {
+ dev_err(dev->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);
+
+ dev_dbg(dev->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..d22c891750f4
--- /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..573376785be2
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -0,0 +1,27 @@
+// 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 *dpu_kms,
+ u32 display_id)
+{
+ struct drm_crtc *crtc;
+ u32 idx = 0;
+
+ if (!dpu_kms || !dpu_kms->dev)
+ return NULL;
+
+ drm_for_each_crtc(crtc, dpu_kms->dev) {
+ if (idx++ == display_id)
+ return 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..4601571a994f
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -0,0 +1,49 @@
+/* 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];
+};
+
+#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 *dpu_kms,
+ u32 display_id);
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dpu_kms,
+ struct drm_crtc *crtc);
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dpu_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_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 664d44f0e48b..25e822ce096a 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -169,6 +169,8 @@ struct dcp_hfi {
struct mutex caps_cache_lock;
struct dcp_hfi_caps_cache *caps_cache;
+ void (*vblank_cb)(struct dcp_drv *drv, u32 display_id);
+ void (*frame_done_cb)(struct dcp_drv *drv, u32 display_id, u32 sequence_id);
void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);
void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
index bf27cbf9b8a4..fea14a2d5095 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -20,6 +20,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"
@@ -264,18 +265,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: {
@@ -1019,6 +1037,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;
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..e804fb8cfa1b
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
@@ -0,0 +1,1073 @@
+// 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 || !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 (!state || !state->crtc)
+ return alpha << 8;
+
+ 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)
+{
+ int i;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_LAYER_SRC_ADDR, 0, 13);
+ *p++ = lid;
+ for (i = 0; i < HFI_MAX_PLANES; i++)
+ *p++ = lower_32_bits(iova[i]);
+ for (i = 0; i < HFI_MAX_PLANES; i++)
+ *p++ = upper_32_bits(iova[i]);
+ *p++ = 0; /* size */
+ *p++ = 0; /* version */
+ *p++ = 0; /* flags */
+ *p++ = hfi_fmt; /* format */
+ 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[128]; /* 128 dwords = 512 bytes, enough for all properties */
+ 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);
+
+ dev_dbg(state->plane->dev->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)
+{
+ int ret;
+
+ dev_dbg(plane->dev->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, false);
+ if (ret) {
+ DRM_ERROR("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)
+{
+ dev_dbg(plane->dev->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, false);
+}
+
+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 = NULL;
+ 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;
+
+ dev_dbg(plane->dev->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->valid = false;
+ 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);
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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);
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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) {
+ dev_err(plane->dev->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;
+
+ if (!state || !state->crtc || !state->crtc->state)
+ return;
+
+ 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_kms->hfi)
+ return 0;
+
+ if (!dcp_crtc_get_session(new_state->crtc, &session_id)) {
+ dev_dbg(plane->dev->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;
+
+ dev_dbg(plane->dev->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 || !dcp_kms->hfi)
+ return 0;
+
+ if (!dcp_crtc_get_session(old_state->crtc, &session_id)) {
+ dev_dbg(plane->dev->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;
+
+ dev_dbg(plane->dev->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->valid = false;
+ pstate->prop_pkt_size = 0;
+
+ if (!new_state->visible || !new_state->fb)
+ return;
+
+ ret = dcp_plane_submit_update(plane, new_state);
+ if (ret) {
+ dev_err(plane->dev->dev,
+ "plane%u: HFI update failed: %d\n", plane->base.id, ret);
+ dcp_plane_set_hfi_error(new_state, ret);
+ return;
+ }
+
+ pstate->valid = true;
+}
+
+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;
+
+ dev_dbg(plane->dev->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) {
+ dev_err(plane->dev->dev,
+ "plane%u: HFI detach failed: %d\n", plane->base.id, ret);
+ dcp_plane_set_hfi_error(old_state, ret);
+ }
+
+ pstate->valid = false;
+ 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->valid = false;
+ 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 void dcp_plane_reset(struct drm_plane *plane)
+{
+ struct dcp_plane_state *pstate;
+ u32 hfi_pipe_id = to_dcp_plane(plane)->hfi_pipe_id;
+
+ if (plane->state) {
+ dcp_plane_atomic_destroy_state(plane, plane->state);
+ plane->state = NULL;
+ }
+
+ pstate = kzalloc(sizeof(*pstate), GFP_KERNEL);
+ if (!pstate)
+ return;
+
+ __drm_atomic_helper_plane_reset(plane, &pstate->base);
+ pstate->hfi_pipe_id = hfi_pipe_id;
+}
+
+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, "\tvalid=%d\n", pstate->valid);
+ 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->valid) {
+ 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,
+ .reset = dcp_plane_reset,
+ .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;
+
+ dev_dbg(dev->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..685e78c547da
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.h
@@ -0,0 +1,60 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state and HFI property packet definitions.
+ */
+
+#ifndef __DCP_PLANE_H__
+#define __DCP_PLANE_H__
+
+#include <drm/drm_plane.h>
+
+struct dcp_dpu_kms;
+struct drm_display_mode;
+
+struct dcp_plane_perf {
+ u64 fetch_bw;
+ u64 clk;
+};
+
+#define DCP_PLANE_PROP_PKT_MAX_SIZE 512
+
+struct dcp_plane {
+ struct drm_plane base;
+ u32 hfi_pipe_id;
+};
+
+#define to_dcp_plane(x) container_of(x, struct dcp_plane, base)
+
+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 valid;
+
+ 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)
+
+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] 27+ messages in thread* Re: [PATCH 06/13] drm/msm/dcp: Add planes, CRTCs, and HFI event routing
2026-10-15 7:38 ` [PATCH 06/13] drm/msm/dcp: Add planes, CRTCs, and HFI event routing Jayant Shekhar
@ 2026-10-15 13:18 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 13:18 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:26PM +0530, Jayant Shekhar wrote:
> The firmware-owned DCP pipeline still needs normal DRM atomic objects and
> event handling so it can participate in the MSM DRM device.
>
> Add DCP plane and CRTC objects, validate framebuffer and plane state,
> allocate firmware display pipes, calculate bandwidth and clock votes, and
> translate visible planes and CRTC state into HFI commands. Route VSYNC and
> frame-scan completion events back to DRM and retain CRTC-local display
> session state.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> drivers/gpu/drm/msm/Makefile | 3 +
> drivers/gpu/drm/msm/dcp/dcp_crtc.c | 733 ++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_crtc.h | 104 ++++
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 27 +
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 49 ++
> drivers/gpu/drm/msm/dcp/dcp_hfi.h | 2 +
> drivers/gpu/drm/msm/dcp/dcp_hfi_core.c | 21 +
> drivers/gpu/drm/msm/dcp/dcp_plane.c | 1073 ++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_plane.h | 60 ++
> 9 files changed, 2072 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 039c2e697258..b7c1a2e9a19e 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -70,6 +70,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 \
> @@ -78,6 +80,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_kms.o \
> dcp/dcp_hfi_plane.o \
> dcp/dcp_ipcc.o \
> + dcp/dcp_plane.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_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> new file mode 100644
> index 000000000000..ede81881d495
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> @@ -0,0 +1,733 @@
> +// 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.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) {
> + const struct dcp_plane_state *dcp_pstate = to_dcp_plane_state(pstate);
Check if it's enabled.
> +
> + 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;
> +
> + if (!kms)
> + return MODE_ERROR;
> +
> + 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_ERROR("CRTC%u: invalid zero-sized LM%u ROI\n",
No deprecated macros in the new driver, thank you.
> + 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)) {
> + dev_dbg(crtc->dev->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) {
> + dev_err(crtc->dev->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)) {
> + dev_warn(crtc->dev->dev,
> + "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
> + dcp_crtc->display_id);
> + return;
> + }
> +
> + dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
> + atomic_inc(&dcp_crtc->pending_kickoff_cnt);
> +
> + ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
> + if (ret) {
> + dev_err(crtc->dev->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)) {
> + dev_err(crtc->dev->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;
> + if (!dcp_kms)
> + return NULL;
> + if (session_id >= DCP_MAX_DISPLAYS)
> + return NULL;
> +
> + 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;
> +}
> +
> +static struct drm_device *dcp_crtc_get_drm_dev(struct dcp_drv *drv)
> +{
> + struct dcp_dpu_kms *dcp_kms;
> +
> + if (!drv || !drv->kms)
> + return NULL;
> +
> + dcp_kms = drv->kms;
> + if (!dcp_kms)
> + return NULL;
> +
> + return dcp_kms->dev;
> +}
> +
> +void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
> +{
> + struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
> +
> + if (crtc)
> + dcp_crtc_vblank_callback(crtc);
> + else if (dcp_crtc_get_drm_dev(drv))
> + drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
> + "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_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
> +
> + if (crtc) {
> + struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
> +
> + if (sequence_id != dcp_crtc->pending_seq_id) {
> + dev_warn(crtc->dev->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 (dcp_crtc_get_drm_dev(drv))
> + drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
> + "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;
> + dev_dbg(crtc->dev->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) {
> + dev_err(crtc->dev->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) {
> + dev_err(crtc->dev->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) {
> + dev_err(crtc->dev->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) {
> + dev_err(dev->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);
> +
> + dev_dbg(dev->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..d22c891750f4
> --- /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..573376785be2
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> @@ -0,0 +1,27 @@
> +// 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 *dpu_kms,
> + u32 display_id)
Can we just use CRTC ids instead of display_id? Under which path do we
need to query the CRTC? Is it not accessible from the state pointers?
> +{
> + struct drm_crtc *crtc;
> + u32 idx = 0;
> +
> + if (!dpu_kms || !dpu_kms->dev)
> + return NULL;
> +
> + drm_for_each_crtc(crtc, dpu_kms->dev) {
> + if (idx++ == display_id)
> + return 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..4601571a994f
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
> @@ -0,0 +1,49 @@
> +/* 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];
> +};
> +
> +#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 *dpu_kms,
> + u32 display_id);
> +int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dpu_kms,
> + struct drm_crtc *crtc);
> +
> +int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dpu_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_hfi.h b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> index 664d44f0e48b..25e822ce096a 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
> @@ -169,6 +169,8 @@ struct dcp_hfi {
> struct mutex caps_cache_lock;
> struct dcp_hfi_caps_cache *caps_cache;
>
> + void (*vblank_cb)(struct dcp_drv *drv, u32 display_id);
> + void (*frame_done_cb)(struct dcp_drv *drv, u32 display_id, u32 sequence_id);
> void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);
> void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
>
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> index bf27cbf9b8a4..fea14a2d5095 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
> @@ -20,6 +20,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"
> @@ -264,18 +265,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: {
> @@ -1019,6 +1037,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;
> 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..e804fb8cfa1b
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
> @@ -0,0 +1,1073 @@
> +// 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 || !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 (!state || !state->crtc)
> + return alpha << 8;
> +
> + 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)
> +{
> + int i;
> +
> + *p++ = HFI_PACK_KEY(HFI_PROPERTY_LAYER_SRC_ADDR, 0, 13);
Magic 13, which encodes a particular value for HFI_MAX_PLANES. I've
asked you previously to write a macro which takes variable number of
args and programs all of them, preventing a need to specify number of
args.
> + *p++ = lid;
> + for (i = 0; i < HFI_MAX_PLANES; i++)
> + *p++ = lower_32_bits(iova[i]);
> + for (i = 0; i < HFI_MAX_PLANES; i++)
> + *p++ = upper_32_bits(iova[i]);
Who has invented this? Splitting addresses into two distinct arrays.
> + *p++ = 0; /* size */
> + *p++ = 0; /* version */
> + *p++ = 0; /* flags */
> + *p++ = hfi_fmt; /* format */
> + 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[128]; /* 128 dwords = 512 bytes, enough for all properties */
"640 KiB ought to be enough"
> + 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);
> +
> + dev_dbg(state->plane->dev->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)
> +{
> + int ret;
> +
> + dev_dbg(plane->dev->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, false);
> + if (ret) {
> + DRM_ERROR("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)
> +{
> + dev_dbg(plane->dev->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, false);
> +}
> +
> +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 = NULL;
> + 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;
> +
> + dev_dbg(plane->dev->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->valid = false;
> + 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);
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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);
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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) {
> + dev_err(plane->dev->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;
> +
> + if (!state || !state->crtc || !state->crtc->state)
> + return;
> +
> + 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_kms->hfi)
> + return 0;
> +
> + if (!dcp_crtc_get_session(new_state->crtc, &session_id)) {
> + dev_dbg(plane->dev->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;
> +
> + dev_dbg(plane->dev->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 || !dcp_kms->hfi)
> + return 0;
> +
> + if (!dcp_crtc_get_session(old_state->crtc, &session_id)) {
> + dev_dbg(plane->dev->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;
> +
> + dev_dbg(plane->dev->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->valid = false;
> + pstate->prop_pkt_size = 0;
> +
> + if (!new_state->visible || !new_state->fb)
> + return;
> +
> + ret = dcp_plane_submit_update(plane, new_state);
> + if (ret) {
> + dev_err(plane->dev->dev,
> + "plane%u: HFI update failed: %d\n", plane->base.id, ret);
> + dcp_plane_set_hfi_error(new_state, ret);
> + return;
> + }
> +
> + pstate->valid = true;
> +}
> +
> +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;
> +
> + dev_dbg(plane->dev->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) {
> + dev_err(plane->dev->dev,
> + "plane%u: HFI detach failed: %d\n", plane->base.id, ret);
> + dcp_plane_set_hfi_error(old_state, ret);
> + }
> +
> + pstate->valid = false;
> + 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->valid = false;
> + 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 void dcp_plane_reset(struct drm_plane *plane)
> +{
> + struct dcp_plane_state *pstate;
> + u32 hfi_pipe_id = to_dcp_plane(plane)->hfi_pipe_id;
> +
> + if (plane->state) {
> + dcp_plane_atomic_destroy_state(plane, plane->state);
> + plane->state = NULL;
> + }
> +
> + pstate = kzalloc(sizeof(*pstate), GFP_KERNEL);
> + if (!pstate)
> + return;
> +
> + __drm_atomic_helper_plane_reset(plane, &pstate->base);
> + pstate->hfi_pipe_id = hfi_pipe_id;
> +}
> +
> +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, "\tvalid=%d\n", pstate->valid);
> + 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->valid) {
> + 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,
> + .reset = dcp_plane_reset,
> + .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;
> +
> + dev_dbg(dev->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..685e78c547da
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + *
> + * DCP plane state and HFI property packet definitions.
> + */
> +
> +#ifndef __DCP_PLANE_H__
> +#define __DCP_PLANE_H__
> +
> +#include <drm/drm_plane.h>
> +
> +struct dcp_dpu_kms;
> +struct drm_display_mode;
> +
> +struct dcp_plane_perf {
> + u64 fetch_bw;
> + u64 clk;
> +};
> +
> +#define DCP_PLANE_PROP_PKT_MAX_SIZE 512
> +
> +struct dcp_plane {
> + struct drm_plane base;
> + u32 hfi_pipe_id;
> +};
> +
> +#define to_dcp_plane(x) container_of(x, struct dcp_plane, base)
> +
> +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 valid;
How can it be invalid?
> +
> + 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)
> +
> +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
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 07/13] drm/msm/dcp: Add DCP encoder
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (5 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 06/13] drm/msm/dcp: Add planes, CRTCs, and HFI event routing Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 12:44 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 08/13] drm/msm/dcp: Add DSI bridge Jayant Shekhar
` (5 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
The DCP display pipeline needs a DRM encoder to connect a CRTC to its
firmware-backed output bridge while reusing the existing MSM DRM object
model.
Add the DCP encoder wrapper and keep encoder allocation and DRM object
initialization independent from bridge and KMS registration.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_encoder.c | 31 +++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_encoder.h | 16 ++++++++++++++++
3 files changed, 48 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index b7c1a2e9a19e..e7d24be72113 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -81,6 +81,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_hfi_plane.o \
dcp/dcp_ipcc.o \
dcp/dcp_plane.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..bc173fffb325
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_encoder.c
@@ -0,0 +1,31 @@
+// 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) {
+ dev_err(dev->dev, "encoder init failed: %d\n", 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] 27+ messages in thread* Re: [PATCH 07/13] drm/msm/dcp: Add DCP encoder
2026-10-15 7:38 ` [PATCH 07/13] drm/msm/dcp: Add DCP encoder Jayant Shekhar
@ 2026-10-15 12:44 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 12:44 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:27PM +0530, Jayant Shekhar wrote:
> The DCP display pipeline needs a DRM encoder to connect a CRTC to its
> firmware-backed output bridge while reusing the existing MSM DRM object
> model.
>
> Add the DCP encoder wrapper and keep encoder allocation and DRM object
> initialization independent from bridge and KMS registration.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/dcp/dcp_encoder.c | 31 +++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_encoder.h | 16 ++++++++++++++++
> 3 files changed, 48 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index b7c1a2e9a19e..e7d24be72113 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -81,6 +81,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_hfi_plane.o \
> dcp/dcp_ipcc.o \
> dcp/dcp_plane.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..bc173fffb325
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_encoder.c
> @@ -0,0 +1,31 @@
> +// 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) {
> + dev_err(dev->dev, "encoder init failed: %d\n", ret);
Again, ignored feedback. Use drm_foo functions instead of dev_foo().
Also, there is little point in printing an error here.
> + 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
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 08/13] drm/msm/dcp: Add DSI bridge
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (6 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 07/13] drm/msm/dcp: Add DCP encoder Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 12:56 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
` (4 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
The firmware owns the DSI controller and display session, so the DRM bridge
must translate panel and connector operations into DCP HFI commands.
Add the DCP DRM bridge for the MIPI DSI pipeline. Translate panel, mode,
power, DCS, and display-session operations into HFI commands, together with
the KMS session preparation and CRTC integration required by the bridge.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 1 +
drivers/gpu/drm/msm/dcp/dcp_crtc.c | 14 +
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 45 ++
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 4 +-
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1140 ++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h | 69 ++
drivers/gpu/drm/msm/dcp/dcp_plane.c | 8 +-
drivers/gpu/drm/msm/dcp/dcp_plane.h | 1 +
8 files changed, 1279 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index e7d24be72113..9910bd4f306d 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -72,6 +72,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_crtc.o \
dcp/dcp_dpu_kms.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_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
index ede81881d495..fb8920d44f8a 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;
@@ -567,6 +573,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;
@@ -577,6 +584,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,
@@ -606,8 +615,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 573376785be2..d465164b158b 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -7,6 +7,30 @@
#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 *dpu_kms,
+ struct drm_crtc *crtc)
+{
+ u32 i;
+
+ if (!dpu_kms || !crtc)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(dpu_kms->dsi_bridge); i++) {
+ struct dcp_dsi_bridge *bridge = dpu_kms->dsi_bridge[i];
+
+ if (!bridge)
+ continue;
+ if (dcp_dpu_kms_get_crtc(dpu_kms, bridge->intf_idx) != 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 *dpu_kms,
u32 display_id)
@@ -25,3 +49,24 @@ struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dpu_kms,
return NULL;
}
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dpu_kms,
+ struct drm_crtc *crtc)
+{
+ u32 i;
+
+ if (!dpu_kms || !crtc)
+ return false;
+
+ for (i = 0; i < ARRAY_SIZE(dpu_kms->dsi_bridge); i++) {
+ struct dcp_dsi_bridge *bridge = dpu_kms->dsi_bridge[i];
+
+ if (!bridge ||
+ dcp_dpu_kms_get_crtc(dpu_kms, bridge->intf_idx) != 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 4601571a994f..650ed7d53a49 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -38,7 +38,9 @@ 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 *dpu_kms,
- u32 display_id);
+ u32 display_id);
+bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dpu_kms,
+ struct drm_crtc *crtc);
int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dpu_kms,
struct drm_crtc *crtc);
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..3a472373fb1e
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
@@ -0,0 +1,1140 @@
+// 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/async.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/of_graph.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_edid.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+#include <drm/drm_panel.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.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_hfi_panel_config hfi_panel_config;
+ 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->hfi_panel_config = dsi_bridge->hfi_panel_config;
+ 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->hfi_panel_config = old_state->hfi_panel_config;
+ state->prepare_error = old_state->prepare_error;
+ 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_host_dev_release(struct device *dev)
+{
+ of_node_put(dev->of_node);
+}
+
+static u32 dcp_dsi_bridge_bpp(const struct mipi_dsi_device *dsi)
+{
+ int bpp;
+
+ if (!dsi)
+ return 24;
+
+ bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+ if (bpp < 0)
+ return 24;
+
+ return bpp;
+}
+
+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;
+ u32 bpp_x16;
+
+ if (!dsi || !dsi->lanes)
+ return 0;
+
+ bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
+ cfg->dsc_bits_per_pixel : dcp_dsi_bridge_bpp(dsi) << 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->dev->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 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;
+
+ 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.parent,
+ "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);
+ }
+
+ dev_dbg(dsi->dev.parent,
+ "DSI%u attached: lanes=%u fmt=%u flags=0x%lx vc=%u hs=%lu %s\n",
+ dsi_bridge->intf_idx, dsi->lanes, dsi->format, dsi->mode_flags,
+ dsi->channel, dsi->hs_rate,
+ dcp_dsi_bridge_cmd_mode(dsi_bridge) ? "cmd" : "video");
+
+ 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);
+
+ 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 (!kms->hfi || !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(dsi_bridge->kms->dev->dev,
+ "DSI%u: live DCS command transfer failed: %zd\n",
+ dsi_bridge->intf_idx, ret);
+ else
+ dev_dbg(dsi_bridge->kms->dev->dev,
+ "DSI%u: live DCS command transferred\n",
+ dsi_bridge->intf_idx);
+
+ return ret;
+}
+
+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 device_node *np = bridge->of_node;
+ struct drm_bridge *panel_bridge;
+ int ret;
+
+ panel_bridge = devm_drm_of_get_bridge(bridge->dev->dev, np, 1, 0);
+ if (IS_ERR(panel_bridge)) {
+ ret = PTR_ERR(panel_bridge);
+ dev_err(dsi_bridge->kms->dev->dev,
+ "DSI%u: panel/bridge not ready: %d\n",
+ dsi_bridge->intf_idx, ret);
+ return ret;
+ }
+
+ ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
+ if (ret) {
+ dev_err(bridge->dev->dev,
+ "DSI%u: panel bridge attach failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ return ret;
+ }
+
+ dev_dbg(dsi_bridge->kms->dev->dev,
+ "DSI%u: panel bridge attached\n",
+ dsi_bridge->intf_idx);
+
+ if (dsi_bridge->kms->hfi)
+ 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->intf_idx);
+ 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->intf_idx));
+}
+
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+ struct dcp_dsi_bridge_state *state,
+ 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(state->hfi_panel_config.dsc_rc_min_qp); i++) {
+ state->hfi_panel_config.dsc_rc_min_qp[i] =
+ dsc->rc_range_params[i].range_min_qp;
+ state->hfi_panel_config.dsc_rc_max_qp[i] =
+ dsc->rc_range_params[i].range_max_qp;
+ state->hfi_panel_config.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 device *dev = kms->dev->dev;
+ int ret;
+ u32 opened_session_id;
+
+ if (!kms->hfi || !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) {
+ ret = -ENODEV;
+ return -ENODEV;
+ }
+
+ if (dsi_bridge->dsi_device->dsc) {
+ ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, state, mode);
+ if (ret) {
+ dev_err(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->session_id,
+ mode,
+ dsi_bridge->dsi_device,
+ &state->hfi_panel_config);
+ if (ret) {
+ dev_err(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,
+ &state->hfi_panel_config,
+ mode);
+ ret = dcp_hfi_display_mode_validate(kms->hfi,
+ dsi_bridge->session_id,
+ mode, link_clock);
+ if (ret) {
+ dev_err(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,
+ &state->hfi_panel_config,
+ mode));
+ if (ret) {
+ dev_err(dev, "DSI%u: HFI SET_MODE failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ } else {
+ dev_warn(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) {
+ dev_err(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) {
+ dev_err(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);
+
+ dev_dbg(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;
+
+ if (!bridge->encoder || !bridge->encoder->crtc)
+ ret = -ENODEV;
+ else
+ ret = dcp_dsi_bridge_prepare_session(dsi_bridge,
+ &bridge->encoder->crtc->state->adjusted_mode);
+ dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+ if (ret)
+ dev_err(bridge->dev->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;
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: bridge atomic_enable (session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+ if (WARN_ON(!kms->hfi))
+ return;
+ 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) {
+ dev_err(bridge->dev->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) {
+ dev_err(bridge->dev->dev,
+ "DSI%u: initial autorefresh setup failed: %d\n",
+ dsi_bridge->intf_idx, ret);
+ goto err_close;
+ }
+ autorefresh_enabled = true;
+ }
+
+ dev_dbg(bridge->dev->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);
+ dev_err(bridge->dev->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;
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
+ dsi_bridge->intf_idx, dsi_bridge->session_open,
+ dsi_bridge->session_id);
+
+ if (WARN_ON(!kms->hfi))
+ return;
+
+ 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)
+ dev_warn(bridge->dev->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)
+ dev_warn(bridge->dev->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;
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: atomic_post_disable\n",
+ dsi_bridge->intf_idx);
+
+ if (WARN_ON(!kms->hfi))
+ return;
+
+ 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)
+ dev_warn(bridge->dev->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)
+ dev_warn(bridge->dev->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, &state->hfi_panel_config, mode)))
+ return -EINVAL;
+
+ if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+ state->hfi_panel_config.topology_mixer_count =
+ dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+ &state->hfi_panel_config, mode);
+ state->hfi_panel_config.topology_enc_count =
+ state->hfi_panel_config.topology_mixer_count;
+ state->hfi_panel_config.topology_intf_count = 1;
+ }
+
+ mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+ &state->hfi_panel_config, mode);
+ bpc = state->hfi_panel_config.dsc_bits_per_component ?
+ state->hfi_panel_config.dsc_bits_per_component : 8;
+ dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
+
+ if (!crtc_state->mode_changed && !crtc_state->connectors_changed)
+ return 0;
+
+ 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;
+ }
+
+ if (kms->caps->max_dsi_resolution) {
+ u32 max_w = (kms->caps->max_dsi_resolution >> 16) & 0xFFFF;
+ u32 max_h = kms->caps->max_dsi_resolution & 0xFFFF;
+
+ dcp_mode_limits(mode, max_w, max_h, &max_w, &max_h);
+ if (max_w && max_h &&
+ (mode->hdisplay > max_w || mode->vdisplay > max_h)) {
+ drm_err(dsi_bridge->kms->dev,
+ "DSI%u: mode %dx%d exceeds DSI limit %dx%d\n",
+ dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
+ max_w, max_h);
+ 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;
+ dev_err(kms->dev->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,
+ u32 intf_idx, u32 fw_display_id)
+{
+ struct dcp_dsi_bridge *dsi_bridge;
+ struct dcp_dsi_hfi_panel_config *cfg;
+ struct device *dma_dev = kms->hfi ? kms->hfi->dma_dev : NULL;
+ int ret;
+
+ if (!dma_dev)
+ return ERR_PTR(-ENODEV);
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: bridge_init (of_node=%s)\n",
+ intf_idx, np ? np->name : "none");
+
+ dsi_bridge = devm_drm_bridge_alloc(kms->dev->dev, struct dcp_dsi_bridge,
+ bridge, &dcp_dsi_bridge_funcs);
+ if (IS_ERR(dsi_bridge))
+ return ERR_CAST(dsi_bridge);
+
+ 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);
+
+ /* The buffers are shared by firmware and the DPU SMMU 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;
+ }
+ 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) {
+ dev_err(kms->dev->dev, "DSI%u: failed to map DCS TX buffer in DPU domain\n",
+ intf_idx);
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+ dsi_bridge->dcs_tx_buf,
+ dsi_bridge->dcs_tx_iova);
+ dsi_bridge->dcs_tx_buf = NULL;
+ dsi_bridge->dcs_tx_iova = 0;
+ goto err_unwind_dma;
+ }
+ dev_dbg(kms->dev->dev, "DSI%u: dcs_tx_buf=%p iova=%pad\n",
+ intf_idx, dsi_bridge->dcs_tx_buf, &dsi_bridge->dcs_tx_iova);
+
+ 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;
+ }
+ 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) {
+ dev_err(kms->dev->dev, "DSI%u: failed to map DCS RX buffer in DPU domain\n",
+ intf_idx);
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+ dsi_bridge->dcs_rx_buf,
+ dsi_bridge->dcs_rx_iova);
+ dsi_bridge->dcs_rx_buf = NULL;
+ dsi_bridge->dcs_rx_iova = 0;
+ goto err_unwind_dma;
+ }
+ dev_dbg(kms->dev->dev, "DSI%u: dcs_rx_buf=%p iova=%pad\n",
+ intf_idx, dsi_bridge->dcs_rx_buf, &dsi_bridge->dcs_rx_iova);
+
+ device_initialize(&dsi_bridge->dsi_host_dev);
+ dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+ dsi_bridge->dsi_host_dev.parent = kms->dev->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(kms->dev->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;
+ }
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: registering MIPI DSI host (dev=%s of_node=%s)\n",
+ intf_idx,
+ dev_name(&dsi_bridge->dsi_host_dev),
+ np ? np->name : "none");
+
+ ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
+ if (ret) {
+ dev_err(kms->dev->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;
+ }
+ dev_dbg(kms->dev->dev,
+ "DSI%u: MIPI DSI host registered\n",
+ intf_idx);
+
+ dsi_bridge->bridge.of_node = of_node_get(np);
+ dsi_bridge->bridge.type = DRM_MODE_CONNECTOR_DSI;
+
+ drm_bridge_add(&dsi_bridge->bridge);
+
+ dev_dbg(kms->dev->dev,
+ "DSI%u: bridge initialized (of_node=%s dcs_tx=%s mode=%s)\n",
+ intf_idx, np ? np->name : "none",
+ dsi_bridge->dcs_tx_buf ? "ok" : "failed",
+ dcp_dsi_bridge_cmd_mode(dsi_bridge) ? "cmd" : "video");
+ return &dsi_bridge->bridge;
+
+err_unwind_dma:
+ if (dsi_bridge->dcs_rx_buf) {
+ dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
+ DCP_DSI_DCS_RX_BUF_SIZE);
+ 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) {
+ dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+ DCP_DSI_DCS_TX_BUF_SIZE);
+ 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);
+}
+
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+ struct device *dma_dev = dsi_bridge->kms->hfi ?
+ dsi_bridge->kms->hfi->dma_dev : NULL;
+
+ if (dsi_bridge->kms->hfi)
+ 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);
+
+ if (dsi_bridge->bridge.of_node) {
+ of_node_put(dsi_bridge->bridge.of_node);
+ dsi_bridge->bridge.of_node = NULL;
+ }
+
+ if (dsi_bridge->dsi_host.dev) {
+ mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+ device_del(&dsi_bridge->dsi_host_dev);
+ put_device(&dsi_bridge->dsi_host_dev);
+ dsi_bridge->dsi_host.dev = NULL;
+ }
+
+ if (dsi_bridge->dcs_tx_buf) {
+ dcp_dsi_dual_unmap_buf(dsi_bridge->kms, dsi_bridge->dcs_tx_iova,
+ DCP_DSI_DCS_TX_BUF_SIZE);
+ dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+ dsi_bridge->dcs_tx_buf,
+ dsi_bridge->dcs_tx_iova);
+ }
+
+ if (dsi_bridge->dcs_rx_buf) {
+ dcp_dsi_dual_unmap_buf(dsi_bridge->kms, dsi_bridge->dcs_rx_iova,
+ DCP_DSI_DCS_RX_BUF_SIZE);
+ 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->dcs_lock);
+}
+
+void dcp_dsi_bridge_hpd_notify(struct dcp_dsi_bridge *bridge,
+ enum drm_connector_status status)
+{
+ drm_bridge_hpd_notify(&bridge->bridge, status);
+}
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..0bf78e6a8c54
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
@@ -0,0 +1,69 @@
+/* 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 dcp_dpu_kms *kms;
+ 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;
+ 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,
+ u32 intf_idx, u32 fw_display_id);
+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);
+void dcp_dsi_bridge_hpd_notify(struct dcp_dsi_bridge *bridge,
+ enum drm_connector_status status);
+
+#endif /* __DCP_DSI_BRIDGE_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.c b/drivers/gpu/drm/msm/dcp/dcp_plane.c
index e804fb8cfa1b..d144bdd667e9 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_plane.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
@@ -583,6 +583,7 @@ static int dcp_plane_build_hfi_props_packet(struct dcp_plane_state *pstate,
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;
dev_dbg(plane->dev->dev,
@@ -597,7 +598,7 @@ static int dcp_plane_prepare_fb(struct drm_plane *plane,
if (ret)
return ret;
- ret = msm_framebuffer_prepare(new_state->fb, false);
+ ret = msm_framebuffer_prepare(new_state->fb, pstate->needs_dirtyfb);
if (ret) {
DRM_ERROR("plane%u: framebuffer prepare failed: %d\n",
plane->base.id, ret);
@@ -610,6 +611,8 @@ static int dcp_plane_prepare_fb(struct drm_plane *plane,
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);
+
dev_dbg(plane->dev->dev,
"plane%u: cleanup_fb fb=%u\n",
plane->base.id,
@@ -618,7 +621,7 @@ static void dcp_plane_cleanup_fb(struct drm_plane *plane,
if (!old_state->fb)
return;
- msm_framebuffer_cleanup(old_state->fb, false);
+ msm_framebuffer_cleanup(old_state->fb, pstate->needs_dirtyfb);
}
static int dcp_plane_atomic_check(struct drm_plane *plane,
@@ -944,6 +947,7 @@ dcp_plane_atomic_duplicate_state(struct drm_plane *plane)
pstate->fetch_bw = old_pstate->fetch_bw;
pstate->clk = old_pstate->clk;
pstate->valid = false;
+ pstate->needs_dirtyfb = old_pstate->needs_dirtyfb;
pstate->prop_pkt_size = 0;
pstate->prop_pkt_id = old_pstate->prop_pkt_id;
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.h b/drivers/gpu/drm/msm/dcp/dcp_plane.h
index 685e78c547da..d69f9b85cf26 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_plane.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.h
@@ -38,6 +38,7 @@ struct dcp_plane_state {
u32 prop_pkt_id;
bool valid;
+ bool needs_dirtyfb;
u32 src_img_w;
u32 src_img_h;
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 08/13] drm/msm/dcp: Add DSI bridge
2026-10-15 7:38 ` [PATCH 08/13] drm/msm/dcp: Add DSI bridge Jayant Shekhar
@ 2026-10-15 12:56 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 12:56 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:28PM +0530, Jayant Shekhar wrote:
> The firmware owns the DSI controller and display session, so the DRM bridge
> must translate panel and connector operations into DCP HFI commands.
>
> Add the DCP DRM bridge for the MIPI DSI pipeline. Translate panel, mode,
> power, DCS, and display-session operations into HFI commands, together with
> the KMS session preparation and CRTC integration required by the bridge.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> drivers/gpu/drm/msm/Makefile | 1 +
> drivers/gpu/drm/msm/dcp/dcp_crtc.c | 14 +
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 45 ++
> drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h | 4 +-
> drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1140 ++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h | 69 ++
> drivers/gpu/drm/msm/dcp/dcp_plane.c | 8 +-
> drivers/gpu/drm/msm/dcp/dcp_plane.h | 1 +
> 8 files changed, 1279 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index e7d24be72113..9910bd4f306d 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -72,6 +72,7 @@ msm-display-$(CONFIG_DRM_MSM_MDP5) += \
> msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_crtc.o \
> dcp/dcp_dpu_kms.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_crtc.c b/drivers/gpu/drm/msm/dcp/dcp_crtc.c
> index ede81881d495..fb8920d44f8a 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);
Do you have the session mechanism described somewhere?
> + if (ret) {
> + cstate->hfi_error = ret;
> + return;
> + }
> +
> if (!dcp_crtc_get_session(crtc, &session_id)) {
> cstate->hfi_error = -ENODEV;
> return;
> @@ -567,6 +573,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;
>
> @@ -577,6 +584,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,
> @@ -606,8 +615,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 573376785be2..d465164b158b 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
> @@ -7,6 +7,30 @@
> #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 *dpu_kms,
s/dpu_kms/dcp_kms/
> + struct drm_crtc *crtc)
> +{
> + u32 i;
> +
> + if (!dpu_kms || !crtc)
> + return -EINVAL;
As usual. No, neither of them can be NULL here.
> +
> + for (i = 0; i < ARRAY_SIZE(dpu_kms->dsi_bridge); i++) {
> + struct dcp_dsi_bridge *bridge = dpu_kms->dsi_bridge[i];
> +
> + if (!bridge)
> + continue;
The bridge can be NULL, Do you see the difference? This is an actual
guard, which makes sense. First two are pure noise. They make one
actually think that dpu_kms or crtc can be NULL here. They can't.
> + if (dcp_dpu_kms_get_crtc(dpu_kms, bridge->intf_idx) != 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 *dpu_kms,
> u32 display_id)
> @@ -25,3 +49,24 @@ struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dpu_kms,
> return NULL;
> }
>
> +bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dpu_kms,
> + struct drm_crtc *crtc)
> +{
> + u32 i;
> +
> + if (!dpu_kms || !crtc)
> + return false;
> +
> + for (i = 0; i < ARRAY_SIZE(dpu_kms->dsi_bridge); i++) {
> + struct dcp_dsi_bridge *bridge = dpu_kms->dsi_bridge[i];
> +
> + if (!bridge ||
> + dcp_dpu_kms_get_crtc(dpu_kms, bridge->intf_idx) != 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 4601571a994f..650ed7d53a49 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
> @@ -38,7 +38,9 @@ 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 *dpu_kms,
> - u32 display_id);
> + u32 display_id);
> +bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dpu_kms,
> + struct drm_crtc *crtc);
> int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dpu_kms,
> struct drm_crtc *crtc);
>
> 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..3a472373fb1e
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
> @@ -0,0 +1,1140 @@
> +// 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/async.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/of_graph.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_edid.h>
> +#include <drm/drm_encoder.h>
> +#include <drm/drm_mipi_dsi.h>
> +#include <drm/drm_modes.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_panel.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_vblank.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_hfi_panel_config hfi_panel_config;
> + 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->hfi_panel_config = dsi_bridge->hfi_panel_config;
What for?
> + 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->hfi_panel_config = old_state->hfi_panel_config;
> + state->prepare_error = old_state->prepare_error;
No, don't copy errors.
> + 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_host_dev_release(struct device *dev)
> +{
> + of_node_put(dev->of_node);
> +}
> +
> +static u32 dcp_dsi_bridge_bpp(const struct mipi_dsi_device *dsi)
> +{
> + int bpp;
> +
> + if (!dsi)
> + return 24;
> +
> + bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
> + if (bpp < 0)
> + return 24;
If it's less than 0, it's an error. Don't return random values.
> +
> + return bpp;
> +}
> +
> +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;
> + u32 bpp_x16;
> +
> + if (!dsi || !dsi->lanes)
> + return 0;
Reject these at the attach time.
> +
> + bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
> + cfg->dsc_bits_per_pixel : dcp_dsi_bridge_bpp(dsi) << 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);
Indentation :-( Checkpatch should have warned you here.
> +
> + 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)
Looks like a pattern.
> +{
> + 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->dev->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 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;
> +
> + 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.parent,
drm_dbg_driver().
> + "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);
> + }
> +
> + dev_dbg(dsi->dev.parent,
> + "DSI%u attached: lanes=%u fmt=%u flags=0x%lx vc=%u hs=%lu %s\n",
> + dsi_bridge->intf_idx, dsi->lanes, dsi->format, dsi->mode_flags,
> + dsi->channel, dsi->hs_rate,
> + dcp_dsi_bridge_cmd_mode(dsi_bridge) ? "cmd" : "video");
> +
> + 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);
> +
> + 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 (!kms->hfi || !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(dsi_bridge->kms->dev->dev,
> + "DSI%u: live DCS command transfer failed: %zd\n",
> + dsi_bridge->intf_idx, ret);
> + else
> + dev_dbg(dsi_bridge->kms->dev->dev,
> + "DSI%u: live DCS command transferred\n",
> + dsi_bridge->intf_idx);
> +
> + return ret;
> +}
> +
> +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 device_node *np = bridge->of_node;
> + struct drm_bridge *panel_bridge;
> + int ret;
> +
> + panel_bridge = devm_drm_of_get_bridge(bridge->dev->dev, np, 1, 0);
> + if (IS_ERR(panel_bridge)) {
> + ret = PTR_ERR(panel_bridge);
> + dev_err(dsi_bridge->kms->dev->dev,
> + "DSI%u: panel/bridge not ready: %d\n",
> + dsi_bridge->intf_idx, ret);
> + return ret;
> + }
> +
> + ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
> + if (ret) {
> + dev_err(bridge->dev->dev,
> + "DSI%u: panel bridge attach failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + return ret;
> + }
> +
> + dev_dbg(dsi_bridge->kms->dev->dev,
> + "DSI%u: panel bridge attached\n",
> + dsi_bridge->intf_idx);
> +
> + if (dsi_bridge->kms->hfi)
> + 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->intf_idx);
> + 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->intf_idx));
> +}
> +
> +static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
> + struct dcp_dsi_bridge_state *state,
> + 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(state->hfi_panel_config.dsc_rc_min_qp); i++) {
> + state->hfi_panel_config.dsc_rc_min_qp[i] =
> + dsc->rc_range_params[i].range_min_qp;
> + state->hfi_panel_config.dsc_rc_max_qp[i] =
> + dsc->rc_range_params[i].range_max_qp;
> + state->hfi_panel_config.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 device *dev = kms->dev->dev;
> + int ret;
> + u32 opened_session_id;
> +
> + if (!kms->hfi || !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) {
> + ret = -ENODEV;
> + return -ENODEV;
> + }
> +
> + if (dsi_bridge->dsi_device->dsc) {
> + ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, state, mode);
> + if (ret) {
> + dev_err(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->session_id,
> + mode,
> + dsi_bridge->dsi_device,
> + &state->hfi_panel_config);
> + if (ret) {
> + dev_err(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,
> + &state->hfi_panel_config,
> + mode);
> + ret = dcp_hfi_display_mode_validate(kms->hfi,
> + dsi_bridge->session_id,
> + mode, link_clock);
> + if (ret) {
> + dev_err(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,
> + &state->hfi_panel_config,
> + mode));
> + if (ret) {
> + dev_err(dev, "DSI%u: HFI SET_MODE failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + } else {
> + dev_warn(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) {
> + dev_err(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) {
> + dev_err(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);
> +
> + dev_dbg(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;
> +
> + if (!bridge->encoder || !bridge->encoder->crtc)
> + ret = -ENODEV;
> + else
> + ret = dcp_dsi_bridge_prepare_session(dsi_bridge,
> + &bridge->encoder->crtc->state->adjusted_mode);
> + dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
> + if (ret)
> + dev_err(bridge->dev->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;
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: bridge atomic_enable (session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_id);
> +
> + if (WARN_ON(!kms->hfi))
> + return;
> + 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) {
> + dev_err(bridge->dev->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) {
> + dev_err(bridge->dev->dev,
> + "DSI%u: initial autorefresh setup failed: %d\n",
> + dsi_bridge->intf_idx, ret);
> + goto err_close;
> + }
> + autorefresh_enabled = true;
> + }
> +
> + dev_dbg(bridge->dev->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);
> + dev_err(bridge->dev->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;
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: atomic_disable (session_open=%d session_id=%u)\n",
> + dsi_bridge->intf_idx, dsi_bridge->session_open,
> + dsi_bridge->session_id);
> +
> + if (WARN_ON(!kms->hfi))
> + return;
> +
> + 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)
> + dev_warn(bridge->dev->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)
> + dev_warn(bridge->dev->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;
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: atomic_post_disable\n",
> + dsi_bridge->intf_idx);
> +
> + if (WARN_ON(!kms->hfi))
> + return;
> +
> + 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)
> + dev_warn(bridge->dev->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)
> + dev_warn(bridge->dev->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, &state->hfi_panel_config, mode)))
> + return -EINVAL;
> +
> + if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
> + state->hfi_panel_config.topology_mixer_count =
Split the static config and changing state.
> + dcp_dsi_bridge_get_mixer_count(dsi_bridge,
> + &state->hfi_panel_config, mode);
> + state->hfi_panel_config.topology_enc_count =
> + state->hfi_panel_config.topology_mixer_count;
> + state->hfi_panel_config.topology_intf_count = 1;
No 2:2:2 support? Can we just drop DSC support for now and add it later?
> + }
> +
> + mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
> + &state->hfi_panel_config, mode);
> + bpc = state->hfi_panel_config.dsc_bits_per_component ?
> + state->hfi_panel_config.dsc_bits_per_component : 8;
> + dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
> +
> + if (!crtc_state->mode_changed && !crtc_state->connectors_changed)
> + return 0;
Why?
> +
> + 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;
> + }
> +
> + if (kms->caps->max_dsi_resolution) {
> + u32 max_w = (kms->caps->max_dsi_resolution >> 16) & 0xFFFF;
> + u32 max_h = kms->caps->max_dsi_resolution & 0xFFFF;
> +
> + dcp_mode_limits(mode, max_w, max_h, &max_w, &max_h);
> + if (max_w && max_h &&
> + (mode->hdisplay > max_w || mode->vdisplay > max_h)) {
> + drm_err(dsi_bridge->kms->dev,
> + "DSI%u: mode %dx%d exceeds DSI limit %dx%d\n",
> + dsi_bridge->intf_idx, mode->hdisplay, mode->vdisplay,
> + max_w, max_h);
> + 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;
> + dev_err(kms->dev->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,
> + u32 intf_idx, u32 fw_display_id)
> +{
> + struct dcp_dsi_bridge *dsi_bridge;
> + struct dcp_dsi_hfi_panel_config *cfg;
> + struct device *dma_dev = kms->hfi ? kms->hfi->dma_dev : NULL;
> + int ret;
> +
> + if (!dma_dev)
> + return ERR_PTR(-ENODEV);
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: bridge_init (of_node=%s)\n",
> + intf_idx, np ? np->name : "none");
> +
> + dsi_bridge = devm_drm_bridge_alloc(kms->dev->dev, struct dcp_dsi_bridge,
> + bridge, &dcp_dsi_bridge_funcs);
> + if (IS_ERR(dsi_bridge))
> + return ERR_CAST(dsi_bridge);
> +
> + 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);
> +
> + /* The buffers are shared by firmware and the DPU SMMU 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;
> + }
> + 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) {
> + dev_err(kms->dev->dev, "DSI%u: failed to map DCS TX buffer in DPU domain\n",
> + intf_idx);
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf,
> + dsi_bridge->dcs_tx_iova);
> + dsi_bridge->dcs_tx_buf = NULL;
> + dsi_bridge->dcs_tx_iova = 0;
> + goto err_unwind_dma;
> + }
> + dev_dbg(kms->dev->dev, "DSI%u: dcs_tx_buf=%p iova=%pad\n",
> + intf_idx, dsi_bridge->dcs_tx_buf, &dsi_bridge->dcs_tx_iova);
> +
> + 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;
> + }
> + 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) {
> + dev_err(kms->dev->dev, "DSI%u: failed to map DCS RX buffer in DPU domain\n",
> + intf_idx);
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
> + dsi_bridge->dcs_rx_buf,
> + dsi_bridge->dcs_rx_iova);
> + dsi_bridge->dcs_rx_buf = NULL;
> + dsi_bridge->dcs_rx_iova = 0;
> + goto err_unwind_dma;
> + }
> + dev_dbg(kms->dev->dev, "DSI%u: dcs_rx_buf=%p iova=%pad\n",
> + intf_idx, dsi_bridge->dcs_rx_buf, &dsi_bridge->dcs_rx_iova);
> +
> + device_initialize(&dsi_bridge->dsi_host_dev);
> + dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
> + dsi_bridge->dsi_host_dev.parent = kms->dev->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(kms->dev->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;
> + }
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: registering MIPI DSI host (dev=%s of_node=%s)\n",
> + intf_idx,
> + dev_name(&dsi_bridge->dsi_host_dev),
> + np ? np->name : "none");
> +
> + ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
> + if (ret) {
> + dev_err(kms->dev->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;
> + }
> + dev_dbg(kms->dev->dev,
> + "DSI%u: MIPI DSI host registered\n",
> + intf_idx);
> +
> + dsi_bridge->bridge.of_node = of_node_get(np);
> + dsi_bridge->bridge.type = DRM_MODE_CONNECTOR_DSI;
> +
> + drm_bridge_add(&dsi_bridge->bridge);
> +
> + dev_dbg(kms->dev->dev,
> + "DSI%u: bridge initialized (of_node=%s dcs_tx=%s mode=%s)\n",
> + intf_idx, np ? np->name : "none",
> + dsi_bridge->dcs_tx_buf ? "ok" : "failed",
> + dcp_dsi_bridge_cmd_mode(dsi_bridge) ? "cmd" : "video");
> + return &dsi_bridge->bridge;
> +
> +err_unwind_dma:
> + if (dsi_bridge->dcs_rx_buf) {
> + dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
> + DCP_DSI_DCS_RX_BUF_SIZE);
> + 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) {
> + dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
> + DCP_DSI_DCS_TX_BUF_SIZE);
> + 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);
> +}
> +
> +void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
> +{
> + struct device *dma_dev = dsi_bridge->kms->hfi ?
> + dsi_bridge->kms->hfi->dma_dev : NULL;
> +
> + if (dsi_bridge->kms->hfi)
> + 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);
> +
> + if (dsi_bridge->bridge.of_node) {
Check which ifs are necessary here and which are not. Drop useless ones.
> + of_node_put(dsi_bridge->bridge.of_node);
> + dsi_bridge->bridge.of_node = NULL;
> + }
> +
> + if (dsi_bridge->dsi_host.dev) {
> + mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
> + device_del(&dsi_bridge->dsi_host_dev);
> + put_device(&dsi_bridge->dsi_host_dev);
> + dsi_bridge->dsi_host.dev = NULL;
> + }
> +
> + if (dsi_bridge->dcs_tx_buf) {
> + dcp_dsi_dual_unmap_buf(dsi_bridge->kms, dsi_bridge->dcs_tx_iova,
> + DCP_DSI_DCS_TX_BUF_SIZE);
> + dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
> + dsi_bridge->dcs_tx_buf,
> + dsi_bridge->dcs_tx_iova);
> + }
> +
> + if (dsi_bridge->dcs_rx_buf) {
> + dcp_dsi_dual_unmap_buf(dsi_bridge->kms, dsi_bridge->dcs_rx_iova,
> + DCP_DSI_DCS_RX_BUF_SIZE);
> + 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->dcs_lock);
> +}
> +
> +void dcp_dsi_bridge_hpd_notify(struct dcp_dsi_bridge *bridge,
> + enum drm_connector_status status)
> +{
> + drm_bridge_hpd_notify(&bridge->bridge, status);
> +}
> 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..0bf78e6a8c54
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
> @@ -0,0 +1,69 @@
> +/* 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 dcp_dpu_kms *kms;
> + 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;
> + 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,
> + u32 intf_idx, u32 fw_display_id);
> +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);
> +void dcp_dsi_bridge_hpd_notify(struct dcp_dsi_bridge *bridge,
> + enum drm_connector_status status);
> +
> +#endif /* __DCP_DSI_BRIDGE_H__ */
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.c b/drivers/gpu/drm/msm/dcp/dcp_plane.c
> index e804fb8cfa1b..d144bdd667e9 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_plane.c
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
> @@ -583,6 +583,7 @@ static int dcp_plane_build_hfi_props_packet(struct dcp_plane_state *pstate,
> 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;
>
> dev_dbg(plane->dev->dev,
> @@ -597,7 +598,7 @@ static int dcp_plane_prepare_fb(struct drm_plane *plane,
> if (ret)
> return ret;
>
> - ret = msm_framebuffer_prepare(new_state->fb, false);
> + ret = msm_framebuffer_prepare(new_state->fb, pstate->needs_dirtyfb);
> if (ret) {
> DRM_ERROR("plane%u: framebuffer prepare failed: %d\n",
> plane->base.id, ret);
> @@ -610,6 +611,8 @@ static int dcp_plane_prepare_fb(struct drm_plane *plane,
> 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);
> +
> dev_dbg(plane->dev->dev,
> "plane%u: cleanup_fb fb=%u\n",
> plane->base.id,
> @@ -618,7 +621,7 @@ static void dcp_plane_cleanup_fb(struct drm_plane *plane,
> if (!old_state->fb)
> return;
>
> - msm_framebuffer_cleanup(old_state->fb, false);
> + msm_framebuffer_cleanup(old_state->fb, pstate->needs_dirtyfb);
> }
>
> static int dcp_plane_atomic_check(struct drm_plane *plane,
> @@ -944,6 +947,7 @@ dcp_plane_atomic_duplicate_state(struct drm_plane *plane)
> pstate->fetch_bw = old_pstate->fetch_bw;
> pstate->clk = old_pstate->clk;
> pstate->valid = false;
> + pstate->needs_dirtyfb = old_pstate->needs_dirtyfb;
> pstate->prop_pkt_size = 0;
> pstate->prop_pkt_id = old_pstate->prop_pkt_id;
>
> diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.h b/drivers/gpu/drm/msm/dcp/dcp_plane.h
> index 685e78c547da..d69f9b85cf26 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_plane.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_plane.h
> @@ -38,6 +38,7 @@ struct dcp_plane_state {
> u32 prop_pkt_id;
>
> bool valid;
> + bool needs_dirtyfb;
>
> u32 src_img_w;
> u32 src_img_h;
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (7 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 08/13] drm/msm/dcp: Add DSI bridge Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-21 12:05 ` Krzysztof Kozlowski
2026-10-21 12:05 ` Krzysztof Kozlowski
2026-10-15 7:38 ` [PATCH 10/13] drm/msm/dcp: Add VirtIO transport and platform driver Jayant Shekhar
` (3 subsequent siblings)
12 siblings, 2 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
The DCP firmware exposes display resources through capabilities rather than
through host-visible display registers. The MSM DRM backend therefore needs
to build its KMS objects from the firmware capabilities and device-tree
display graph.
Complete DCP KMS registration, including power and IOMMU integration,
capability handling, display-graph parsing, and creation of DRM planes,
CRTCs, encoders, bridges, and connectors.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c | 589 +++++++++++++++++++++++++++++++++-
1 file changed, 588 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
index d465164b158b..e87c24d44417 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -4,10 +4,30 @@
* 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_drv.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_vblank.h>
-#include "dcp_dpu_kms.h"
+#include "msm_drv.h"
+#include "msm_gem.h"
+#include "msm_kms.h"
+
+#include "dcp_crtc.h"
#include "dcp_dsi_bridge.h"
+#include "dcp_dpu_kms.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 *dpu_kms,
struct drm_crtc *crtc)
@@ -70,3 +90,570 @@ bool dcp_dpu_kms_needs_dirtyfb(struct dcp_dpu_kms *dpu_kms,
return false;
}
+
+static void dcp_dpu_kms_cleanup_bridges(struct dcp_dpu_kms *dpu_kms)
+{
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(dpu_kms->dsi_bridge); i++) {
+ if (!dpu_kms->dsi_bridge[i])
+ continue;
+
+ dcp_dsi_bridge_cleanup(dpu_kms->dsi_bridge[i]);
+ dpu_kms->dsi_bridge[i] = NULL;
+ }
+}
+
+static void dcp_dpu_kms_dump_caps(struct dcp_dpu_kms *dpu_kms)
+{
+ struct device *dev = dpu_kms->dev->dev;
+ const struct dcp_caps *caps = dpu_kms->caps;
+ u32 i;
+
+ dev_dbg(dev, "DCP caps: hw=%u.%u fw=%u valid=%u dyn_clk=%u crtcs=%u\n",
+ caps->dcp_hw_rev, caps->hw_rev, caps->fw_rev, caps->valid,
+ caps->dyn_clk_support, caps->num_crtcs);
+ dev_dbg(dev, "DCP caps: max_planes=%u max=%ux%u features=0x%x\n",
+ caps->max_planes, caps->max_width, caps->max_height,
+ caps->features);
+
+ dev_dbg(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++)
+ dev_dbg(dev, "DCP caps: VIG[%u] pipe=%u r1=%u\n", i,
+ caps->vig_pipe_ids[i], caps->vig_r1_indices[i]);
+
+ dev_dbg(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++)
+ dev_dbg(dev, "DCP caps: DMA[%u] pipe=%u r1=%u\n", i,
+ caps->dma_pipe_ids[i], caps->dma_r1_indices[i]);
+
+ dev_dbg(dev, "DCP caps: DSI count=%u max_resolution=0x%x\n",
+ caps->dsi_count, caps->max_dsi_resolution);
+ for (i = 0; i < min_t(u32, caps->dsi_count, DCP_MAX_BLOCKS); i++)
+ dev_dbg(dev, "DCP caps: DSI[%u] index=%u\n", i,
+ caps->dsi_indices[i]);
+
+ dev_dbg(dev, "DCP caps: DP count=%u\n", caps->dp_count);
+ for (i = 0; i < min_t(u32, caps->dp_count, DCP_MAX_BLOCKS); i++)
+ dev_dbg(dev, "DCP caps: DP[%u] index=%u\n", i,
+ caps->dp_indices[i]);
+
+ for (i = 0; i < DCP_MAX_DISPLAYS; i++)
+ dev_dbg(dev, "DCP caps: active_pipes_mask[%u]=0x%x\n", i,
+ caps->active_pipes_mask[i]);
+}
+
+static int dcp_dpu_kms_hw_init(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dpu_kms = to_dcp_dpu_kms(kms);
+ struct drm_device *dev = dpu_kms->dev;
+ struct drm_gpuvm *vm;
+ int ret;
+
+ kms->irq = IRQ_NOTCONNECTED;
+ dev->mode_config.cursor_width = 512;
+ dev->mode_config.cursor_height = 512;
+
+ ret = pm_runtime_resume_and_get(dpu_kms->pm_dev);
+ if (ret < 0) {
+ dev_err(dev->dev, "failed to enable power: %d\n", ret);
+ return ret;
+ }
+
+ vm = msm_kms_init_vm(dev, NULL);
+ if (IS_ERR(vm)) {
+ dev_err(dev->dev, "SMMU/IOMMU init failed: %pe\n", vm);
+ ret = PTR_ERR(vm);
+ goto err_cleanup;
+ }
+ kms->vm = vm;
+
+ if (dpu_kms->drv && !dpu_kms->hfi)
+ dpu_kms->hfi = dpu_kms->drv->hfi;
+ if (!dpu_kms->hfi) {
+ dev_err(dev->dev, "HFI unavailable, cannot initialize DCP KMS\n");
+ ret = -ENODEV;
+ goto err_cleanup;
+ }
+
+ if (!dpu_kms->caps || !dpu_kms->caps->valid) {
+ dev_err(dev->dev, "DCP caps unavailable, cannot initialize DCP KMS\n");
+ ret = -ENODEV;
+ goto err_cleanup;
+ }
+
+ dcp_dpu_kms_dump_caps(dpu_kms);
+
+ dev->mode_config.min_width = 0;
+ dev->mode_config.min_height = 0;
+ dev->mode_config.max_width = max(dpu_kms->caps->max_width,
+ dpu_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(dpu_kms);
+ if (ret) {
+ dev_err(dev->dev, "DRM object init failed: %d\n", ret);
+ goto err_cleanup;
+ }
+
+ pm_runtime_put_autosuspend(dpu_kms->pm_dev);
+ dev_dbg(dev->dev, "DCP hw_init complete\n");
+ return 0;
+
+err_cleanup:
+ 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(dpu_kms->pm_dev);
+ return ret;
+}
+
+static void dcp_dpu_kms_enable_commit(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dpu_kms = to_dcp_dpu_kms(kms);
+ int ret;
+
+ ret = pm_runtime_resume_and_get(dpu_kms->pm_dev);
+ if (ret < 0) {
+ dpu_kms->commit_pm_active = false;
+ dev_err_ratelimited(dpu_kms->dev->dev,
+ "pm_runtime_resume_and_get failed: %d\n",
+ ret);
+ return;
+ }
+
+ dpu_kms->commit_pm_active = true;
+}
+
+static void dcp_dpu_kms_disable_commit(struct msm_kms *kms)
+{
+ struct dcp_dpu_kms *dpu_kms = to_dcp_dpu_kms(kms);
+
+ if (dpu_kms->commit_pm_active) {
+ pm_runtime_put_autosuspend(dpu_kms->pm_dev);
+ dpu_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 *dpu_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dpu_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 *dpu_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dpu_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 *dpu_kms = to_dcp_dpu_kms(kms);
+ struct drm_crtc *crtc;
+
+ for_each_crtc_mask(dpu_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 *dpu_kms = to_dcp_dpu_kms(kms);
+
+ dcp_dpu_kms_cleanup_bridges(dpu_kms);
+
+ if (kms->vm) {
+ struct msm_gem_vm *msm_vm = to_msm_vm(kms->vm);
+
+ if (msm_vm->mmu && msm_vm->mmu->funcs->detach) {
+ dev_dbg(dpu_kms->dev ? dpu_kms->dev->dev : NULL,
+ "%s: detaching IOMMU domain from dcp-dpus\n", __func__);
+ msm_vm->mmu->funcs->detach(msm_vm->mmu);
+ }
+
+ drm_gpuvm_put(kms->vm);
+ kms->vm = NULL;
+ }
+
+ if (dpu_kms->rpm_enabled) {
+ pm_runtime_disable(dpu_kms->pm_dev);
+ dpu_kms->rpm_enabled = false;
+ }
+
+ msm_kms_destroy(&dpu_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 *dpu_kms = to_dcp_dpu_kms(kms);
+
+ dev_dbg(dpu_kms->dev->dev,
+ "DCP snapshot: max %ux%u, features=0x%x\n",
+ dpu_kms->caps->max_width,
+ dpu_kms->caps->max_height,
+ dpu_kms->caps->features);
+}
+
+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 *dpu_kms,
+ bool is_dp, u32 intf_idx)
+{
+ struct dcp_caps *caps = dpu_kms->caps;
+ u32 count = 0;
+ const u32 *ids = NULL;
+ u32 id;
+
+ if (caps) {
+ count = is_dp ? caps->dp_count : caps->dsi_count;
+ ids = is_dp ? caps->dp_indices : caps->dsi_indices;
+ }
+
+ if (!ids || intf_idx >= count) {
+ dev_err(dpu_kms->dev->dev,
+ "%s%u: no catalog entry (%u)\n",
+ is_dp ? "DP" : "DSI", intf_idx, count);
+ return U32_MAX;
+ }
+
+ id = ids[intf_idx];
+
+ if (id > 0xFF) {
+ dev_err(dpu_kms->dev->dev,
+ "%s%u: catalog id 0x%x exceeds 8-bit range\n",
+ is_dp ? "DP" : "DSI", intf_idx, id);
+ return U32_MAX;
+ }
+
+ return id;
+}
+
+static int dcp_dpu_kms_setup_bridges(struct dcp_dpu_kms *dpu_kms,
+ struct device_node *dpu_np)
+{
+ struct drm_device *dev = dpu_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;
+
+ if (!dpu_np) {
+ dev_err(dev->dev, "no DCP function DT node\n");
+ return -ENODEV;
+ }
+
+ for_each_of_graph_port(dpu_np, port_np) {
+ u32 fw_display_id;
+ int enc_type;
+
+ 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;
+ }
+
+ if (of_node_name_prefix(remote_np, "dsi")) {
+ if (dsi_idx >= ARRAY_SIZE(dpu_kms->dsi_bridge)) {
+ dev_err(dev->dev, "too many DSI outputs\n");
+ ret = -E2BIG;
+ goto out;
+ }
+
+ enc_type = DRM_MODE_ENCODER_DSI;
+ dev_dbg(dev->dev, "port@%u: DSI bridge intf_idx=%u\n",
+ ep.port, dsi_idx);
+ fw_display_id =
+ dcp_dpu_kms_resolve_fw_display_id(dpu_kms,
+ false, dsi_idx);
+ if (fw_display_id == U32_MAX) {
+ ret = -EINVAL;
+ dev_err(dev->dev,
+ "port@%u: invalid DSI firmware display ID\n",
+ ep.port);
+ of_node_put(remote_np);
+ goto out;
+ }
+ bridge = dcp_dsi_bridge_init(dpu_kms, remote_np,
+ dsi_idx, fw_display_id);
+ if (!IS_ERR(bridge) && dsi_idx < 2) {
+ dpu_kms->dsi_bridge[dsi_idx] =
+ to_dcp_dsi_bridge(bridge);
+ dsi_idx++;
+ }
+ } else {
+ of_node_put(remote_np);
+ continue;
+ }
+
+ of_node_put(remote_np);
+
+ if (IS_ERR(bridge)) {
+ ret = PTR_ERR(bridge);
+ dev_err(dev->dev, "bridge init failed: %d\n", ret);
+ goto out;
+ }
+
+ encoder = dcp_encoder_init(dev, enc_type);
+ if (IS_ERR(encoder)) {
+ ret = PTR_ERR(encoder);
+ dev_err(dev->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) {
+ dev_dbg(dev->dev,
+ "port@%u bridge deferred; panel not ready\n", ep.port);
+ goto out;
+ }
+ dev_err(dev->dev, "bridge attach failed: %d\n", ret);
+ goto out;
+ }
+
+ connector = drm_bridge_connector_init(dev, encoder);
+ if (IS_ERR(connector)) {
+ ret = PTR_ERR(connector);
+ dev_err(dev->dev, "bridge connector init failed: %d\n", ret);
+ goto out;
+ }
+ }
+
+out:
+ if (ret)
+ dcp_dpu_kms_cleanup_bridges(dpu_kms);
+ return ret;
+}
+
+static int dcp_dpu_setup_pipeline(struct dcp_dpu_kms *dpu_kms,
+ struct device_node *dpu_np)
+{
+ struct drm_device *dev = dpu_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(dpu_kms, dpu_np);
+ if (ret) {
+ dev_err(dev->dev, "bridge setup failed: %d\n", ret);
+ return ret;
+ }
+
+ max_crtc_count = 0;
+ drm_for_each_encoder(encoder, dev)
+ max_crtc_count++;
+
+ if (!dpu_kms->caps || !dpu_kms->caps->valid) {
+ dev_err(dev->dev, "firmware capabilities unavailable\n");
+ return -EINVAL;
+ }
+
+ max_crtc_count = min(max_crtc_count, DCP_DPU_MAX_CRTCS);
+ max_crtc_count = min_t(int, max_crtc_count,
+ dpu_kms->caps->num_crtcs);
+
+ total_planes = min_t(int, dpu_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);
+ dev_err(dev->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);
+
+ for (i = 0; i < max_crtc_count; i++) {
+ crtc = dcp_crtc_init(dpu_kms, primary_planes[i],
+ dev->mode_config.num_crtc);
+ if (IS_ERR(crtc)) {
+ ret = PTR_ERR(crtc);
+ dev_err(dev->dev, "CRTC init failed: %d\n", ret);
+ goto err_cleanup_bridges;
+ }
+ }
+
+ return 0;
+
+err_cleanup_bridges:
+ dcp_dpu_kms_cleanup_bridges(dpu_kms);
+ return ret;
+}
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dpu_kms)
+{
+ struct drm_device *dev = dpu_kms->dev;
+ struct device_node *dpus_np = dpu_kms->dpus_np;
+ struct drm_encoder *encoder;
+ int enc_idx = 0;
+ int ret;
+
+ if (!dpus_np) {
+ dev_err(dev->dev, "no DCP function node — cannot create DRM objects\n");
+ return -ENODEV;
+ }
+
+ ret = dcp_dpu_setup_pipeline(dpu_kms, dpus_np);
+ if (ret) {
+ dev_err(dev->dev, "pipeline setup failed: %d\n", ret);
+ return ret;
+ }
+
+ /* 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++;
+ }
+
+ dev_dbg(dev->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 *dpu_kms = to_dcp_dpu_kms(priv->kms);
+ struct dcp_drv *drv = dpu_kms->drv;
+
+ dpu_kms->dev = ddev;
+ if (drv)
+ drv->kms = dpu_kms;
+
+ if (drv) {
+ dpu_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
+ if (!dpu_kms->hfi)
+ dpu_kms->hfi = drv->hfi;
+ if (!dpu_kms->dpus_np)
+ dpu_kms->dpus_np = drv->dcp_subsystem_node;
+ if (!dpu_kms->iommu_dev)
+ dpu_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+ }
+
+ if (!dpu_kms->hfi)
+ return -ENODEV;
+
+ if (!dpu_kms->caps || !dpu_kms->caps->valid)
+ return -ENODEV;
+
+ return 0;
+}
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline
2026-10-15 7:38 ` [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
@ 2026-10-21 12:05 ` Krzysztof Kozlowski
2026-10-21 12:05 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-21 12:05 UTC (permalink / raw)
To: Jayant Shekhar, Rob Clark, Dmitry Baryshkov, Abhinav Kumar,
Jessica Zhang, Sean Paul, Marijn Suijten, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu
On 15/10/2026 09:38, Jayant Shekhar wrote:
> The DCP firmware exposes display resources through capabilities rather than
> through host-visible display registers. The MSM DRM backend therefore needs
> to build its KMS objects from the firmware capabilities and device-tree
> display graph.
...
> +
> + 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;
> + }
> +
> + if (of_node_name_prefix(remote_np, "dsi")) {
Where did you document the ABI?
> + if (dsi_idx >= ARRAY_SIZE(dpu_kms->dsi_bridge)) {
> + dev_err(dev->dev, "too many DSI outputs\n");
> + ret = -E2BIG;
> + goto out;
> + }
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline
2026-10-15 7:38 ` [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
2026-10-21 12:05 ` Krzysztof Kozlowski
@ 2026-10-21 12:05 ` Krzysztof Kozlowski
1 sibling, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-21 12:05 UTC (permalink / raw)
To: Jayant Shekhar, Rob Clark, Dmitry Baryshkov, Abhinav Kumar,
Jessica Zhang, Sean Paul, Marijn Suijten, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu
On 15/10/2026 09:38, Jayant Shekhar wrote:
> The DCP firmware exposes display resources through capabilities rather than
> through host-visible display registers. The MSM DRM backend therefore needs
> to build its KMS objects from the firmware capabilities and device-tree
> display graph.
>
> Complete DCP KMS registration, including power and IOMMU integration,
> capability handling, display-graph parsing, and creation of DRM planes,
> CRTCs, encoders, bridges, and connectors.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
Your tag is the last one.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 10/13] drm/msm/dcp: Add VirtIO transport and platform driver
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (8 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 09/13] drm/msm/dcp: Register DCP display pipeline Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 13:06 ` Dmitry Baryshkov
2026-10-15 7:38 ` [PATCH 11/13] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
` (2 subsequent siblings)
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
DCP display hardware is controlled by firmware and exposed to Linux as a
VirtIO function. The MSM DRM driver needs a platform driver to establish
the transport, DMA/IOMMU address spaces, power-notify path, and display
component.
Add the VirtIO-MMIO transport and DCP platform-function driver. Set up the
DMA/IOMMU identity, IPCC client, VirtIO queues, HFI transport, firmware
capability handling, and MSM DRM registration. Synchronize suspend, resume,
remove, and shutdown with in-flight HFI activity.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
drivers/gpu/drm/msm/Makefile | 2 +
drivers/gpu/drm/msm/dcp/dcp_drv.c | 245 ++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_drv.h | 19 +++
drivers/gpu/drm/msm/dcp/dcp_virtio.c | 302 +++++++++++++++++++++++++++++++++++
drivers/gpu/drm/msm/dcp/dcp_virtio.h | 22 +++
5 files changed, 590 insertions(+)
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 9910bd4f306d..e57aee483137 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -83,6 +83,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
dcp/dcp_ipcc.o \
dcp/dcp_plane.o \
dcp/dcp_encoder.o \
+ dcp/dcp_drv.o \
+ dcp/dcp_virtio.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.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c
new file mode 100644
index 000000000000..e7016937a028
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c
@@ -0,0 +1,245 @@
+// 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_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];
+ int ret;
+
+ dcp_subsystem_node = of_get_child_by_name(vdev->dev.parent->of_node,
+ "dcp-subsystem");
+ 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;
+ }
+
+ if (!device_iommu_mapped(dev)) {
+ 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",
+ device_iommu_mapped(dev),
+ 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 ? ((struct virtio_device *)
+ 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 ? ((struct virtio_device *)
+ dev_to_virtio(vdev_dev))->priv : NULL;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return;
+
+ msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
+}
+
+static int dcp_pm_ipcc_notify(struct dcp_drv *drv)
+{
+ int ret;
+
+ if (!drv->ipcc_valid)
+ return 0;
+
+ ret = dcp_ipcc_send_power_notify(&drv->ipcc);
+ if (ret)
+ dev_warn(&drv->vdev->dev,
+ "IPCC power notification failed during PM: %d\n", ret);
+
+ return ret;
+}
+
+int dcp_pm_suspend(struct device *vdev_dev)
+{
+ struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *)
+ dev_to_virtio(vdev_dev))->priv : NULL;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return 0;
+
+ return dcp_pm_ipcc_notify(drv);
+}
+
+int dcp_pm_resume(struct device *vdev_dev)
+{
+ struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *)
+ dev_to_virtio(vdev_dev))->priv : NULL;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return 0;
+
+ return dcp_pm_ipcc_notify(drv);
+}
+
+static int dcp_self_bind(struct device *master, struct device *dev, void *data)
+{
+ return 0;
+}
+
+static void dcp_self_unbind(struct device *master, struct device *dev,
+ void *data)
+{
+}
+
+static const struct component_ops dcp_self_ops = {
+ .bind = dcp_self_bind,
+ .unbind = dcp_self_unbind,
+};
+
+int dcp_component_register(struct dcp_drv *drv)
+{
+ struct device *dev = &drv->dcp_subsystem_dev->dev;
+ struct component_match *match = NULL;
+ int ret;
+
+ /*
+ * DCP has no host-visible display subcomponents: firmware owns the
+ * underlying display blocks. The self component is only the minimal shim
+ * needed to enter the existing MSM DRM component-master init path without
+ * adding a DCP-specific entry point to common MSM DRM code.
+ */
+ ret = component_add(dev, &dcp_self_ops);
+ if (ret) {
+ dev_err(dev, "component_add failed: %d\n", ret);
+ return ret;
+ }
+
+ component_match_add(dev, &match, component_compare_dev, dev);
+
+ 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);
+ component_del(dev, &dcp_self_ops);
+ 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);
+ component_del(dev, &dcp_self_ops);
+}
+
+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 af2db3cf9066..ca8e5bd284e0 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
@@ -26,6 +26,25 @@ struct dcp_drv {
struct dcp_dpu_kms *kms;
struct dcp_caps caps;
struct notifier_block panic_notifier;
+ struct dcp_ipcc ipcc;
+ bool ipcc_valid;
};
+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);
+int dcp_pm_suspend(struct device *vdev_dev);
+int dcp_pm_resume(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..b2aeb03688c9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
@@ -0,0 +1,302 @@
+// 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/scatterlist.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 *dpu_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;
+ }
+ /* 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 = dcp_ipcc_init(&drv->ipcc, vdev->dev.parent);
+ if (ret) {
+ if (ret == -EPROBE_DEFER)
+ dev_err_probe(&vdev->dev, ret,
+ "IPCC not ready, deferring probe\n");
+ else
+ dev_err(&vdev->dev, "IPCC init failed: %d\n", ret);
+ goto err_fn_dev;
+ }
+ drv->ipcc_valid = !!drv->ipcc.power_chan;
+
+ 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_ipcc;
+ }
+
+ 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;
+ }
+
+ if (drv->ipcc_valid)
+ 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, features=0x%x\n",
+ drv->caps.num_crtcs, drv->caps.max_width, drv->caps.max_height,
+ drv->caps.max_planes, drv->caps.features);
+
+ dpu_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dpu_kms), GFP_KERNEL);
+ if (!dpu_kms) {
+ ret = -ENOMEM;
+ goto err_hfi_deinit;
+ }
+
+ dpu_kms->drv = drv;
+ dpu_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
+ dpu_kms->hfi = drv->hfi;
+ dpu_kms->caps = &drv->caps;
+ dpu_kms->dpus_np = drv->dcp_subsystem_node;
+ dpu_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
+
+ ret = msm_kms_init(&dpu_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 = &dpu_kms->base;
+ priv->kms_init = dcp_kms_init;
+ dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
+ dpu_kms->rpm_enabled = true;
+
+ /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
+ ret = dcp_component_register(drv);
+ if (ret) {
+ dev_err(&vdev->dev, "component register failed: %d\n", ret);
+ 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_ipcc;
+err_del_vqs:
+ virtio_reset_device(vdev);
+ vdev->config->del_vqs(vdev);
+err_ipcc:
+ if (drv->ipcc_valid) {
+ dcp_ipcc_deinit(&drv->ipcc);
+ drv->ipcc_valid = false;
+ }
+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_hfi_stop(drv->hfi);
+ virtio_reset_device(vdev);
+ virtio_synchronize_cbs(vdev);
+ vdev->priv = NULL;
+ dcp_hfi_quiesce_queues(drv->hfi);
+ vdev->config->del_vqs(vdev);
+ dcp_hfi_deinit(drv->hfi);
+ drv->hfi = NULL;
+}
+
+/*
+ * 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");
+
+ if (!drv)
+ return;
+
+ dcp_component_unregister(drv);
+ dcp_panic_notifier_unregister(drv);
+
+ dcp_virtio_hfi_cleanup(drv);
+
+ if (drv->ipcc_valid) {
+ dcp_ipcc_deinit(&drv->ipcc);
+ drv->ipcc_valid = false;
+ }
+
+ 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;
+
+ if (!drv || !drv->dcp_subsystem_dev)
+ return;
+
+ 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);
+}
+
+/*
+ * Firmware capabilities may change independently of the VirtIO transport;
+ * refresh them without rebuilding the DRM device.
+ */
+static void dcp_virtio_config_changed(struct virtio_device *vdev)
+{
+ struct dcp_drv *drv = vdev->priv;
+ int ret;
+
+ dev_dbg(&vdev->dev, "VirtIO config changed — re-querying DCP capabilities\n");
+
+ if (!drv || !drv->hfi)
+ return;
+
+ ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
+ if (ret)
+ dev_err(&vdev->dev,
+ "config_changed: capability re-query failed: %d\n", ret);
+ else
+ dev_dbg(&vdev->dev,
+ "config_changed: DCP caps updated: %u displays, features=0x%x\n",
+ drv->caps.num_crtcs, drv->caps.features);
+}
+
+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,
+ .suspend = dcp_pm_suspend,
+ .resume = dcp_pm_resume,
+};
+
+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,
+ .config_changed = dcp_virtio_config_changed,
+};
+
+int dcp_virtio_register(void)
+{
+ return register_virtio_driver(&dcp_virtio_driver);
+}
+
+void dcp_virtio_unregister(void)
+{
+ unregister_virtio_driver(&dcp_virtio_driver);
+}
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..fe244bc5b51a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP VirtIO driver interface.
+ */
+
+#ifndef __DCP_VIRTIO_H__
+#define __DCP_VIRTIO_H__
+
+#include <linux/device.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ids.h>
+
+struct dcp_drv;
+
+#define VIRTIO_ID_QPSFI 52
+#define VIRTIO_VENDOR_ID_DCP_HFI 1
+
+int dcp_virtio_register(void);
+void dcp_virtio_unregister(void);
+
+#endif /* __DCP_VIRTIO_H__ */
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 10/13] drm/msm/dcp: Add VirtIO transport and platform driver
2026-10-15 7:38 ` [PATCH 10/13] drm/msm/dcp: Add VirtIO transport and platform driver Jayant Shekhar
@ 2026-10-15 13:06 ` Dmitry Baryshkov
0 siblings, 0 replies; 27+ messages in thread
From: Dmitry Baryshkov @ 2026-10-15 13:06 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:30PM +0530, Jayant Shekhar wrote:
> DCP display hardware is controlled by firmware and exposed to Linux as a
> VirtIO function. The MSM DRM driver needs a platform driver to establish
> the transport, DMA/IOMMU address spaces, power-notify path, and display
> component.
>
> Add the VirtIO-MMIO transport and DCP platform-function driver. Set up the
> DMA/IOMMU identity, IPCC client, VirtIO queues, HFI transport, firmware
> capability handling, and MSM DRM registration. Synchronize suspend, resume,
> remove, and shutdown with in-flight HFI activity.
>
> Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
> Assisted-by: LLM
> ---
> drivers/gpu/drm/msm/Makefile | 2 +
> drivers/gpu/drm/msm/dcp/dcp_drv.c | 245 ++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_drv.h | 19 +++
> drivers/gpu/drm/msm/dcp/dcp_virtio.c | 302 +++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/msm/dcp/dcp_virtio.h | 22 +++
> 5 files changed, 590 insertions(+)
>
> diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
> index 9910bd4f306d..e57aee483137 100644
> --- a/drivers/gpu/drm/msm/Makefile
> +++ b/drivers/gpu/drm/msm/Makefile
> @@ -83,6 +83,8 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
> dcp/dcp_ipcc.o \
> dcp/dcp_plane.o \
> dcp/dcp_encoder.o \
> + dcp/dcp_drv.o \
> + dcp/dcp_virtio.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.c b/drivers/gpu/drm/msm/dcp/dcp_drv.c
> new file mode 100644
> index 000000000000..e7016937a028
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.c
> @@ -0,0 +1,245 @@
> +// 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_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];
> + int ret;
> +
> + dcp_subsystem_node = of_get_child_by_name(vdev->dev.parent->of_node,
> + "dcp-subsystem");
> + 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;
> + }
> +
> + if (!device_iommu_mapped(dev)) {
> + 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",
> + device_iommu_mapped(dev),
> + 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 ? ((struct virtio_device *)
> + 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 ? ((struct virtio_device *)
> + dev_to_virtio(vdev_dev))->priv : NULL;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return;
> +
> + msm_kms_pm_complete(&drv->dcp_subsystem_dev->dev);
> +}
> +
> +static int dcp_pm_ipcc_notify(struct dcp_drv *drv)
> +{
> + int ret;
> +
> + if (!drv->ipcc_valid)
> + return 0;
> +
> + ret = dcp_ipcc_send_power_notify(&drv->ipcc);
An indirection just for the sake of indirection? Can we drop one-line
wrappers?
> + if (ret)
> + dev_warn(&drv->vdev->dev,
> + "IPCC power notification failed during PM: %d\n", ret);
> +
> + return ret;
> +}
> +
> +int dcp_pm_suspend(struct device *vdev_dev)
> +{
> + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *)
> + dev_to_virtio(vdev_dev))->priv : NULL;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return 0;
> +
> + return dcp_pm_ipcc_notify(drv);
> +}
> +
> +int dcp_pm_resume(struct device *vdev_dev)
> +{
> + struct dcp_drv *drv = vdev_dev ? ((struct virtio_device *)
> + dev_to_virtio(vdev_dev))->priv : NULL;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return 0;
> +
> + return dcp_pm_ipcc_notify(drv);
> +}
> +
> +static int dcp_self_bind(struct device *master, struct device *dev, void *data)
> +{
> + return 0;
> +}
> +
> +static void dcp_self_unbind(struct device *master, struct device *dev,
> + void *data)
> +{
> +}
> +
> +static const struct component_ops dcp_self_ops = {
> + .bind = dcp_self_bind,
> + .unbind = dcp_self_unbind,
> +};
> +
> +int dcp_component_register(struct dcp_drv *drv)
> +{
> + struct device *dev = &drv->dcp_subsystem_dev->dev;
> + struct component_match *match = NULL;
> + int ret;
> +
> + /*
> + * DCP has no host-visible display subcomponents: firmware owns the
> + * underlying display blocks. The self component is only the minimal shim
> + * needed to enter the existing MSM DRM component-master init path without
> + * adding a DCP-specific entry point to common MSM DRM code.
> + */
And how does that work with the DSI probing sequences? The panel driver
might be not (yet) available when you probe the DCP. The driver should
register the DSI host, create DSI devices, but the DRM device
registration is only possible when the panel / bridge has attached to
the DSI host. See how it's handled by the mdp4/mdp5/dpu drivers.
> + ret = component_add(dev, &dcp_self_ops);
> + if (ret) {
> + dev_err(dev, "component_add failed: %d\n", ret);
> + return ret;
> + }
> +
> + component_match_add(dev, &match, component_compare_dev, dev);
Missing optional GPU match. I think I wrote that before...
> +
> + 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);
> + component_del(dev, &dcp_self_ops);
> + 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);
> + component_del(dev, &dcp_self_ops);
> +}
> +
> +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 af2db3cf9066..ca8e5bd284e0 100644
> --- a/drivers/gpu/drm/msm/dcp/dcp_drv.h
> +++ b/drivers/gpu/drm/msm/dcp/dcp_drv.h
> @@ -26,6 +26,25 @@ struct dcp_drv {
> struct dcp_dpu_kms *kms;
> struct dcp_caps caps;
> struct notifier_block panic_notifier;
> + struct dcp_ipcc ipcc;
> + bool ipcc_valid;
> };
>
> +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);
> +int dcp_pm_suspend(struct device *vdev_dev);
> +int dcp_pm_resume(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..b2aeb03688c9
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.c
> @@ -0,0 +1,302 @@
> +// 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/scatterlist.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 *dpu_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;
> + }
> + /* 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 = dcp_ipcc_init(&drv->ipcc, vdev->dev.parent);
> + if (ret) {
> + if (ret == -EPROBE_DEFER)
> + dev_err_probe(&vdev->dev, ret,
> + "IPCC not ready, deferring probe\n");
> + else
> + dev_err(&vdev->dev, "IPCC init failed: %d\n", ret);
> + goto err_fn_dev;
> + }
> + drv->ipcc_valid = !!drv->ipcc.power_chan;
> +
> + 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_ipcc;
> + }
> +
> + 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;
> + }
> +
> + if (drv->ipcc_valid)
> + 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, features=0x%x\n",
> + drv->caps.num_crtcs, drv->caps.max_width, drv->caps.max_height,
> + drv->caps.max_planes, drv->caps.features);
> +
> + dpu_kms = devm_kzalloc(&drv->dcp_subsystem_dev->dev, sizeof(*dpu_kms), GFP_KERNEL);
> + if (!dpu_kms) {
> + ret = -ENOMEM;
> + goto err_hfi_deinit;
> + }
> +
> + dpu_kms->drv = drv;
> + dpu_kms->pm_dev = &drv->dcp_subsystem_dev->dev;
> + dpu_kms->hfi = drv->hfi;
> + dpu_kms->caps = &drv->caps;
> + dpu_kms->dpus_np = drv->dcp_subsystem_node;
> + dpu_kms->iommu_dev = &drv->dcp_subsystem_dev->dev;
> +
> + ret = msm_kms_init(&dpu_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 = &dpu_kms->base;
> + priv->kms_init = dcp_kms_init;
> + dev_set_drvdata(&drv->dcp_subsystem_dev->dev, priv);
> + dpu_kms->rpm_enabled = true;
> +
> + /* Component binding now sees initialized HFI, capabilities, and IOMMU state. */
> + ret = dcp_component_register(drv);
> + if (ret) {
> + dev_err(&vdev->dev, "component register failed: %d\n", ret);
> + 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_ipcc;
> +err_del_vqs:
> + virtio_reset_device(vdev);
> + vdev->config->del_vqs(vdev);
> +err_ipcc:
> + if (drv->ipcc_valid) {
> + dcp_ipcc_deinit(&drv->ipcc);
> + drv->ipcc_valid = false;
> + }
> +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_hfi_stop(drv->hfi);
> + virtio_reset_device(vdev);
> + virtio_synchronize_cbs(vdev);
> + vdev->priv = NULL;
> + dcp_hfi_quiesce_queues(drv->hfi);
> + vdev->config->del_vqs(vdev);
> + dcp_hfi_deinit(drv->hfi);
> + drv->hfi = NULL;
What's the point of setting pointers to NULL? Won't this memory be
kfree()'d soon?
> +}
> +
> +/*
> + * 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");
> +
> + if (!drv)
> + return;
> +
> + dcp_component_unregister(drv);
> + dcp_panic_notifier_unregister(drv);
> +
> + dcp_virtio_hfi_cleanup(drv);
> +
> + if (drv->ipcc_valid) {
> + dcp_ipcc_deinit(&drv->ipcc);
> + drv->ipcc_valid = false;
> + }
> +
> + 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;
> +
> + if (!drv || !drv->dcp_subsystem_dev)
> + return;
> +
> + 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);
> +}
> +
> +/*
> + * Firmware capabilities may change independently of the VirtIO transport;
> + * refresh them without rebuilding the DRM device.
And what does that mean? How can they change?
> + */
> +static void dcp_virtio_config_changed(struct virtio_device *vdev)
> +{
> + struct dcp_drv *drv = vdev->priv;
> + int ret;
> +
> + dev_dbg(&vdev->dev, "VirtIO config changed — re-querying DCP capabilities\n");
> +
> + if (!drv || !drv->hfi)
> + return;
> +
> + ret = dcp_hfi_get_capabilities(drv->hfi, &drv->caps);
> + if (ret)
> + dev_err(&vdev->dev,
> + "config_changed: capability re-query failed: %d\n", ret);
> + else
> + dev_dbg(&vdev->dev,
> + "config_changed: DCP caps updated: %u displays, features=0x%x\n",
> + drv->caps.num_crtcs, drv->caps.features);
> +}
> +
[...]
> 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..fe244bc5b51a
> --- /dev/null
> +++ b/drivers/gpu/drm/msm/dcp/dcp_virtio.h
> @@ -0,0 +1,22 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
> + * DCP VirtIO driver interface.
> + */
> +
> +#ifndef __DCP_VIRTIO_H__
> +#define __DCP_VIRTIO_H__
> +
> +#include <linux/device.h>
> +#include <linux/virtio.h>
> +#include <linux/virtio_ids.h>
> +
> +struct dcp_drv;
> +
> +#define VIRTIO_ID_QPSFI 52
> +#define VIRTIO_VENDOR_ID_DCP_HFI 1
I think this was questioned by Bjorn. Is it described somewhere? Is it a
part of the VirtIO spec? If so, add these ids to the VirtIO headers. If
not... then it's squatting. We can't just randomly settle on the ID.
> +
> +int dcp_virtio_register(void);
> +void dcp_virtio_unregister(void);
> +
> +#endif /* __DCP_VIRTIO_H__ */
>
> --
> 2.43.0
>
--
With best wishes
Dmitry
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH 11/13] drm/msm: Support KMS backends without a display IRQ
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (9 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 10/13] drm/msm/dcp: Add VirtIO transport and platform driver Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 7:38 ` [PATCH 12/13] drm/msm: Register the DCP backend Jayant Shekhar
2026-10-15 7:38 ` [PATCH 13/13] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
12 siblings, 0 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
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.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
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] 27+ messages in thread* [PATCH 12/13] drm/msm: Register the DCP backend
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (10 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 11/13] drm/msm: Support KMS backends without a display IRQ Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 7:38 ` [PATCH 13/13] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
12 siblings, 0 replies; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
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.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
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 f3d2eaa04f14..04c31596d6d4 100644
--- a/drivers/gpu/drm/msm/msm_drv.c
+++ b/drivers/gpu/drm/msm/msm_drv.c
@@ -1135,6 +1135,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();
@@ -1156,6 +1157,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 eb4bbae8557b..39954932b91b 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -415,6 +415,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] 27+ messages in thread* [PATCH 13/13] arm64: dts: qcom: hawi: Add DCP support
2026-10-15 7:38 [PATCH 00/13] drm/msm/dcp: Add Qualcomm DCP display support Jayant Shekhar
` (11 preceding siblings ...)
2026-10-15 7:38 ` [PATCH 12/13] drm/msm: Register the DCP backend Jayant Shekhar
@ 2026-10-15 7:38 ` Jayant Shekhar
2026-10-15 10:52 ` Krzysztof Kozlowski
12 siblings, 1 reply; 27+ messages in thread
From: Jayant Shekhar @ 2026-10-15 7:38 UTC (permalink / raw)
To: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy
Cc: linux-arm-msm, dri-devel, freedreno, devicetree, linux-kernel,
linux-media, linaro-mm-sig, iommu, Jayant Shekhar
Hawi display hardware is owned by DCP firmware, so the platform device tree
must describe the VirtIO transport, firmware display function, power-notify
channel, IOMMU contexts, and display graph used by the MSM DRM driver.
Describe the DCP VirtIO transport and function, IPCC channel, IOMMU
assignments, firmware-visible IOVA range, and DSI display endpoints on
Hawi. Enable the DCP display device on the Hawi MTP platform.
Signed-off-by: Jayant Shekhar <jayant.shekhar@oss.qualcomm.com>
Assisted-by: LLM
---
arch/arm64/boot/dts/qcom/hawi-mtp.dts | 131 +++++++++++++++++++++++++++++++++-
arch/arm64/boot/dts/qcom/hawi.dtsi | 59 +++++++++++++++
2 files changed, 189 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/hawi-mtp.dts b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
index 9c86e0477053..03e4cc117109 100644
--- a/arch/arm64/boot/dts/qcom/hawi-mtp.dts
+++ b/arch/arm64/boot/dts/qcom/hawi-mtp.dts
@@ -36,6 +36,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 {
xo_board: xo_board {
compatible = "fixed-clock";
@@ -261,6 +273,7 @@ sdcard_vdd_3p0: sdcard-vdd-3p0 {
vin-supply = <&vreg_bob1>;
};
+
};
&lpass_vamacro {
@@ -271,7 +284,6 @@ &lpass_vamacro {
qcom,dmic-sample-rate = <4800000>;
};
-
&q6apmbedai {
dai@40 {
reg = <SECONDARY_TDM_RX_0>;
@@ -1277,3 +1289,120 @@ &usb_1_dwc3_hs {
&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_dsi0_out>;
+ };
+ };
+ };
+};
+
+&dcp_dsi0_out {
+ remote-endpoint = <&panel_dcp_in>;
+ data-lanes = <0 1 2 3>;
+};
+
+&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 0926d880cb0c..58fcf42cbaba 100644
--- a/arch/arm64/boot/dts/qcom/hawi.dtsi
+++ b/arch/arm64/boot/dts/qcom/hawi.dtsi
@@ -8235,4 +8235,63 @@ timer {
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
};
+
+ dcp: virtio@9be8000 {
+ compatible = "qcom,dcp-virtio", "virtio,mmio";
+ reg = <0x0 0x09be8000 0x0 0x1000>;
+ interrupts-extended = <&intc GIC_ESPI 956 IRQ_TYPE_LEVEL_HIGH>,
+ <&ipcc 13 5 IRQ_TYPE_EDGE_RISING>;
+ interrupt-names = "virtio", "dcp_power_notify_irq";
+ 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@1 {
+ reg = <1>;
+ dcp_intf1_out: endpoint {
+
+ remote-endpoint = <&dcp_dsi0_in>;
+ };
+ };
+
+ };
+
+ dcp_dsi0: dsi@0 {
+ reg = <0>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ dcp_dsi0_in: endpoint {
+ remote-endpoint = <&dcp_intf1_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ dcp_dsi0_out: endpoint { };
+ };
+ };
+ };
+ };
+ };
};
--
2.43.0
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH 13/13] arm64: dts: qcom: hawi: Add DCP support
2026-10-15 7:38 ` [PATCH 13/13] arm64: dts: qcom: hawi: Add DCP support Jayant Shekhar
@ 2026-10-15 10:52 ` Krzysztof Kozlowski
0 siblings, 0 replies; 27+ messages in thread
From: Krzysztof Kozlowski @ 2026-10-15 10:52 UTC (permalink / raw)
To: Jayant Shekhar
Cc: Rob Clark, Dmitry Baryshkov, Abhinav Kumar, Jessica Zhang,
Sean Paul, Marijn Suijten, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, David Airlie, Simona Vetter, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Sumit Semwal,
Christian König, Philipp Stanner, Bjorn Andersson,
Konrad Dybcio, Abel Vesa, Joerg Roedel (AMD),
Will Deacon, Robin Murphy, linux-arm-msm, dri-devel, freedreno,
devicetree, linux-kernel, linux-media, linaro-mm-sig, iommu
On Thu, Oct 15, 2026 at 01:08:33PM +0530, Jayant Shekhar wrote:
> diff --git a/arch/arm64/boot/dts/qcom/hawi.dtsi b/arch/arm64/boot/dts/qcom/hawi.dtsi
> index 0926d880cb0c..58fcf42cbaba 100644
> --- a/arch/arm64/boot/dts/qcom/hawi.dtsi
> +++ b/arch/arm64/boot/dts/qcom/hawi.dtsi
> @@ -8235,4 +8235,63 @@ timer {
> <GIC_PPI 11 IRQ_TYPE_LEVEL_LOW>,
> <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>;
> };
> +
> + dcp: virtio@9be8000 {
> + compatible = "qcom,dcp-virtio", "virtio,mmio";
More slop.
At least you could ask your agent to TEST it. See internal guidelines
how to do it.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 27+ messages in thread