[PATCH 09/13] drm/msm/dcp: Register DCP display pipeline
From: Jayant Shekhar
Date: Thu Oct 15 2026 - 03:45:52 EST
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@xxxxxxxxxxxxxxxx>
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