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

Reply via email to