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 <[email protected]> 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
