Expose the DCP DSI host and panel as DRM-facing objects. The DCP
display path needs a host, panel configuration, bridge state, DSC
setup, DCS transfer buffers, and panel lifetime management before it
can drive a physical display.

Implement the MIPI DSI host, panel-device support, bridge state,
DSC configuration, DCS transfers, and DSI buffer management.

Assisted-by: LLM
Signed-off-by: Jayant Shekhar <[email protected]>
---
 drivers/gpu/drm/msm/Makefile             |    1 +
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c | 1110 ++++++++++++++++++++++++++++++
 drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h |   76 ++
 drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h  |   13 +
 4 files changed, 1200 insertions(+)

diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 4024f6b1e3cf..9f0a9bfeb2a7 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -75,6 +75,7 @@ msm-display-$(CONFIG_DRM_MSM_DCP) += \
        dcp/dcp_crtc.o \
        dcp/dcp_dpu_kms.o \
        dcp/dcp_plane.o \
+       dcp/dcp_dsi_bridge.o \
        dcp/dcp_hfi_core.o \
        dcp/dcp_hfi_crtc.o \
        dcp/dcp_hfi_dsi.o \
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c 
b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
new file mode 100644
index 000000000000..b3e782b8f047
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.c
@@ -0,0 +1,1110 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge driver.
+ *
+ * Implements a DRM bridge for DSI displays in the DCP firmware-delegated
+ * architecture. DCP firmware owns the DSI PHY and controller; this driver
+ * uses HFI commands to configure DSI parameters rather than direct register
+ * writes.
+ */
+
+#include <linux/component.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/jiffies.h>
+#include <linux/math64.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <drm/display/drm_dsc.h>
+#include <drm/display/drm_dsc_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+#include <drm/drm_of.h>
+
+#include <video/mipi_display.h>
+
+#include "dcp_crtc.h"
+#include "dcp_dsi_bridge.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_hfi.h"
+#include "hfi/hfi_defs_panel.h"
+
+struct dcp_dsi_bridge_state {
+       struct drm_bridge_state base;
+       struct dcp_dsi_panel_state panel_state;
+       int prepare_error;
+};
+
+#define to_dcp_dsi_bridge_state(s) \
+       container_of(s, struct dcp_dsi_bridge_state, base)
+
+static struct dcp_dsi_bridge_state *
+dcp_dsi_bridge_get_state(struct dcp_dsi_bridge *bridge)
+{
+       return 
to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->bridge.base.state));
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_create_state(struct drm_bridge *bridge)
+{
+       struct dcp_dsi_bridge_state *state;
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+
+       state = kzalloc_obj(*state);
+       if (!state)
+               return ERR_PTR(-ENOMEM);
+
+       __drm_atomic_helper_bridge_state_init(&state->base, bridge);
+       state->panel_state.topology_mixer_count =
+               dsi_bridge->hfi_panel_config.topology_mixer_count;
+       state->panel_state.topology_enc_count =
+               dsi_bridge->hfi_panel_config.topology_enc_count;
+       state->panel_state.topology_intf_count =
+               dsi_bridge->hfi_panel_config.topology_intf_count;
+       return &state->base;
+}
+
+static struct drm_bridge_state *
+dcp_dsi_bridge_atomic_duplicate_state(struct drm_bridge *bridge)
+{
+       struct dcp_dsi_bridge_state *state, *old_state;
+
+       if (WARN_ON(!bridge->base.state))
+               return NULL;
+
+       state = kzalloc_obj(*state);
+       if (!state)
+               return NULL;
+
+       old_state = 
to_dcp_dsi_bridge_state(drm_priv_to_bridge_state(bridge->base.state));
+       __drm_atomic_helper_bridge_duplicate_state(bridge, &state->base);
+       state->panel_state = old_state->panel_state;
+       return &state->base;
+}
+
+static void dcp_dsi_bridge_atomic_destroy_state(struct drm_bridge *bridge,
+                                               struct drm_bridge_state 
*bridge_state)
+{
+       kfree(to_dcp_dsi_bridge_state(bridge_state));
+}
+
+static void
+dcp_dsi_bridge_build_hfi_config(struct dcp_dsi_bridge *dsi_bridge,
+                               const struct dcp_dsi_bridge_state *state,
+                               struct dcp_dsi_hfi_panel_config *cfg)
+{
+       *cfg = dsi_bridge->hfi_panel_config;
+       cfg->topology_mixer_count = state->panel_state.topology_mixer_count;
+       cfg->topology_enc_count = state->panel_state.topology_enc_count;
+       cfg->topology_intf_count = state->panel_state.topology_intf_count;
+}
+
+static void dcp_dsi_host_dev_release(struct device *dev)
+{
+       of_node_put(dev->of_node);
+}
+
+static bool dcp_dsi_bridge_cmd_mode(const struct dcp_dsi_bridge *dsi_bridge)
+{
+       return dsi_bridge->dsi_device &&
+               !(dsi_bridge->dsi_device->mode_flags & MIPI_DSI_MODE_VIDEO);
+}
+
+static u64 dcp_dsi_link_clock(const struct dcp_dsi_bridge *dsi_bridge,
+                             const struct dcp_dsi_hfi_panel_config *cfg,
+                             const struct drm_display_mode *mode)
+{
+       const struct mipi_dsi_device *dsi = dsi_bridge->dsi_device;
+       int bpp;
+       u32 bpp_x16;
+
+       bpp = mipi_dsi_pixel_format_to_bpp(dsi->format);
+       if (bpp < 0)
+               return 0;
+
+       bpp_x16 = cfg->dsc_enabled && cfg->dsc_bits_per_pixel ?
+               cfg->dsc_bits_per_pixel : bpp << 4;
+
+       if (dsi->hs_rate)
+               return dsi->hs_rate;
+
+       return div_u64((u64)mode->clock * 1000 * bpp_x16, dsi->lanes * 16);
+}
+
+static void dcp_dsi_power_control_work(struct work_struct *work)
+{
+       struct dcp_dsi_bridge *dsi_bridge =
+               container_of(work, struct dcp_dsi_bridge, power_control_work);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+       dcp_hfi_display_power_control_ack(kms->hfi,
+                                         dsi_bridge->session_id,
+                                         dsi_bridge->power_packet_id);
+
+       complete(&dsi_bridge->power_done);
+}
+
+static void dcp_dsi_power_control_cb(void *priv, u32 display_id,
+                                    u32 power_mask, bool enable,
+                                    u32 packet_id)
+{
+       struct dcp_dpu_kms *kms = priv;
+       struct dcp_dsi_bridge *dsi_bridge = NULL;
+       int idx;
+
+       for (idx = 0; idx < ARRAY_SIZE(kms->dsi_bridge); idx++) {
+               struct dcp_dsi_bridge *b = kms->dsi_bridge[idx];
+
+               if (b && b->session_id == display_id) {
+                       dsi_bridge = b;
+                       break;
+               }
+       }
+
+       if (!dsi_bridge) {
+               dev_warn(kms->pm_dev,
+                        "DSI: power_control for unknown display_id=%u\n", 
display_id);
+               return;
+       }
+
+       dsi_bridge->power_packet_id = packet_id;
+       schedule_work(&dsi_bridge->power_control_work);
+}
+
+static const struct component_ops dcp_dsi_component_ops;
+
+static int dcp_dsi_host_attach(struct mipi_dsi_host *host,
+                              struct mipi_dsi_device *dsi)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+       struct dcp_dsi_hfi_panel_config *cfg = &dsi_bridge->hfi_panel_config;
+       int ret;
+
+       if (!dsi->lanes || dsi->lanes > 4)
+               return -EINVAL;
+
+       cfg->dsc_enabled = false;
+       cfg->dsc_version_major = 0;
+       cfg->dsc_version_minor = 0;
+       cfg->dsc_scr_version = 0;
+       cfg->dsc_slice_height = 0;
+       cfg->dsc_slice_width = 0;
+       cfg->dsc_slices_per_pkt = 0;
+       cfg->dsc_bits_per_component = 0;
+       cfg->dsc_pps_delay_ms = 0;
+       cfg->dsc_bits_per_pixel = 0;
+       cfg->dsc_chroma_format = 0;
+       cfg->dsc_color_space = 0;
+       cfg->dsc_block_prediction_enable = 0;
+       cfg->dsc_rc_override = false;
+       memset(cfg->dsc_rc_min_qp, 0, sizeof(cfg->dsc_rc_min_qp));
+       memset(cfg->dsc_rc_max_qp, 0, sizeof(cfg->dsc_rc_max_qp));
+       memset(cfg->dsc_rc_bpg_offsets, 0, sizeof(cfg->dsc_rc_bpg_offsets));
+
+       switch (dsi->format) {
+       case MIPI_DSI_FMT_RGB888:
+       case MIPI_DSI_FMT_RGB666:
+       case MIPI_DSI_FMT_RGB666_PACKED:
+       case MIPI_DSI_FMT_RGB565:
+       case MIPI_DSI_FMT_RGB101010:
+               break;
+       default:
+               dev_err(&dsi->dev, "unsupported pixel format %u\n", 
dsi->format);
+               return -EINVAL;
+       }
+
+       dsi_bridge->dsi_device = dsi;
+
+       if (dsi->dsc) {
+               struct drm_dsc_config *dsc = dsi->dsc;
+               u32 chroma_format = 0;
+               u32 color_space = 0;
+
+               if (dsc->native_422)
+                       chroma_format = 1;
+               else if (dsc->native_420)
+                       chroma_format = 2;
+               if (dsc->native_422 || dsc->native_420)
+                       color_space = 1;
+
+               cfg->dsc_enabled                 = true;
+               cfg->dsc_version_major           = dsc->dsc_version_major;
+               cfg->dsc_version_minor           = dsc->dsc_version_minor;
+               cfg->dsc_scr_version              = 0;
+               cfg->dsc_slice_height             = dsc->slice_height;
+               cfg->dsc_slice_width              = dsc->slice_width;
+               cfg->dsc_slices_per_pkt           = 1;
+               cfg->dsc_bits_per_component       = dsc->bits_per_component;
+               cfg->dsc_pps_delay_ms             = 0;
+               cfg->dsc_bits_per_pixel           = dsc->bits_per_pixel;
+               cfg->dsc_chroma_format            = chroma_format;
+               cfg->dsc_color_space              = color_space;
+               cfg->dsc_block_prediction_enable  = dsc->block_pred_enable;
+
+               dev_dbg(&dsi->dev,
+                       "DSI%u: DSC enabled: version=%u.%u slice=%ux%u bpc=%u 
bpp=%u.%u\n",
+                       dsi_bridge->intf_idx, dsc->dsc_version_major,
+                       dsc->dsc_version_minor, dsc->slice_width,
+                       dsc->slice_height, dsc->bits_per_component,
+                       dsc->bits_per_pixel >> 4, (dsc->bits_per_pixel & 0xf) * 
100 / 16);
+       }
+
+       ret = component_add(&dsi_bridge->dsi_host_dev,
+                           &dcp_dsi_component_ops);
+       if (ret)
+               return ret;
+       dsi_bridge->component_added = true;
+
+       return 0;
+}
+
+static int dcp_dsi_host_detach(struct mipi_dsi_host *host,
+                              struct mipi_dsi_device *dsi)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+
+       if (dsi_bridge->component_added) {
+               component_del(&dsi_bridge->dsi_host_dev,
+                             &dcp_dsi_component_ops);
+               dsi_bridge->component_added = false;
+       }
+       dsi_bridge->panel_bridge = NULL;
+       dsi_bridge->dsi_device = NULL;
+       dsi_bridge->hfi_panel_config.dsc_enabled = false;
+       return 0;
+}
+
+static ssize_t dcp_dsi_host_transfer(struct mipi_dsi_host *host,
+                                    const struct mipi_dsi_msg *msg)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_host(host);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       u32 session_id;
+       bool temporary_session = false;
+       ssize_t ret;
+
+       guard(mutex)(&dsi_bridge->session_lock);
+       if (!dsi_bridge->dcs_tx_buf)
+               return -ENODEV;
+
+       /* Serialize DCS transfers */
+       guard(mutex)(&dsi_bridge->dcs_lock);
+
+       if (dsi_bridge->session_open) {
+               session_id = dsi_bridge->session_id;
+       } else {
+               /*
+                * mipi_dsi_host_ops::transfer() may be called while the host is
+                * disabled. Open a transaction-only DCP session in that case;
+                * atomic_pre_enable() will create the full display session 
later.
+                */
+               ret = dcp_hfi_display_enable(kms->hfi, dsi_bridge->intf_idx,
+                                            dsi_bridge->fw_display_id,
+                                            &session_id);
+               if (ret)
+                       return ret;
+
+               temporary_session = true;
+       }
+
+       ret = dcp_hfi_dcs_transfer(kms->hfi, session_id, msg,
+                                  dsi_bridge->dcs_tx_buf, 
dsi_bridge->dcs_tx_iova,
+                                  dsi_bridge->dcs_rx_buf, 
dsi_bridge->dcs_rx_iova, 0,
+                                  
dsi_bridge->hfi_panel_config.ctrl_indices[0]);
+       if (temporary_session) {
+               int close_ret = dcp_hfi_display_close(kms->hfi, session_id);
+
+               if (ret >= 0 && close_ret)
+                       ret = close_ret;
+       }
+       if (ret < 0)
+               dev_warn(kms->pm_dev,
+                        "DSI%u: live DCS command transfer failed: %zd\n",
+                        dsi_bridge->intf_idx, ret);
+
+       return ret;
+}
+
+static int dcp_dsi_component_bind(struct device *dev,
+                                 struct device *master, void *data)
+{
+       struct dcp_dsi_bridge *dsi_bridge =
+               container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+       dsi_bridge->panel_bridge =
+               devm_drm_of_get_bridge(dev, dsi_bridge->dcp_np,
+                                      dsi_bridge->dcp_port, 0);
+       if (IS_ERR(dsi_bridge->panel_bridge)) {
+               int ret = PTR_ERR(dsi_bridge->panel_bridge);
+
+               dsi_bridge->panel_bridge = NULL;
+               return ret;
+       }
+
+       return 0;
+}
+
+static void dcp_dsi_component_unbind(struct device *dev,
+                                    struct device *master, void *data)
+{
+       struct dcp_dsi_bridge *dsi_bridge =
+               container_of(dev, struct dcp_dsi_bridge, dsi_host_dev);
+
+       dsi_bridge->panel_bridge = NULL;
+}
+
+static const struct component_ops dcp_dsi_component_ops = {
+       .bind = dcp_dsi_component_bind,
+       .unbind = dcp_dsi_component_unbind,
+};
+
+static const struct mipi_dsi_host_ops dcp_dsi_host_ops = {
+       .attach   = dcp_dsi_host_attach,
+       .detach   = dcp_dsi_host_detach,
+       .transfer = dcp_dsi_host_transfer,
+};
+
+static int dcp_dsi_bridge_attach(struct drm_bridge *bridge,
+                                struct drm_encoder *encoder,
+                                enum drm_bridge_attach_flags flags)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       struct drm_bridge *panel_bridge;
+       int ret;
+
+       panel_bridge = dsi_bridge->panel_bridge;
+       if (!panel_bridge)
+               return -EPROBE_DEFER;
+
+       ret = drm_bridge_attach(encoder, panel_bridge, bridge, flags);
+       if (ret) {
+               drm_err(bridge->dev,
+                       "DSI%u: panel bridge attach failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+               return ret;
+       }
+
+       drm_dbg_driver(dsi_bridge->kms->dev,
+                      "DSI%u: panel bridge attached\n",
+                      dsi_bridge->intf_idx);
+
+       dcp_hfi_set_power_control_cb(dsi_bridge->kms->hfi,
+                                    dsi_bridge->kms, dcp_dsi_power_control_cb);
+
+       return 0;
+}
+
+static u32 dcp_dsi_bridge_get_mixer_count(struct dcp_dsi_bridge *dsi_bridge,
+                                         const struct dcp_dsi_hfi_panel_config 
*cfg,
+                                         const struct drm_display_mode *mode)
+{
+       u32 mixer_count = cfg->topology_mixer_count;
+
+       if (!mixer_count)
+               mixer_count = 1;
+
+       if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+               const struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+
+               if (dsc->slice_count)
+                       mixer_count = dsc->slice_count;
+               else if (dsc->slice_width && mode->hdisplay)
+                       mixer_count = DIV_ROUND_UP(mode->hdisplay,
+                                                  dsc->slice_width);
+       }
+
+       return mixer_count;
+}
+
+static void dcp_dsi_bridge_publish_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       struct drm_crtc *crtc;
+
+       crtc = dcp_dpu_kms_get_crtc(kms, dsi_bridge->fw_display_id);
+       if (crtc)
+               dcp_crtc_set_session(crtc, dsi_bridge->session_id);
+       else
+               drm_err(kms->dev, "DSI%u: no CRTC for session %u\n",
+                       dsi_bridge->intf_idx, dsi_bridge->session_id);
+}
+
+static void dcp_dsi_bridge_clear_session(struct dcp_dsi_bridge *dsi_bridge)
+{
+       dcp_crtc_clear_session(dcp_dpu_kms_get_crtc(dsi_bridge->kms,
+                                                   dsi_bridge->fw_display_id));
+}
+
+static int dcp_dsi_bridge_setup_dsc(struct dcp_dsi_bridge *dsi_bridge,
+                                   struct dcp_dsi_hfi_panel_config *cfg,
+                                   const struct drm_display_mode *mode)
+{
+       struct drm_dsc_config *dsc = dsi_bridge->dsi_device->dsc;
+       enum drm_dsc_params_type params_type;
+       unsigned int i;
+       int ret;
+
+       dsc->pic_width = mode->hdisplay;
+       dsc->pic_height = mode->vdisplay;
+       dsc->convert_rgb = !dsc->native_422 && !dsc->native_420;
+       dsc->slice_chunk_size = DIV_ROUND_UP(dsc->slice_width *
+                                       dsc->bits_per_pixel, 8 * 16);
+       dsc->simple_422 = 0;
+       dsc->vbr_enable = 0;
+
+       if (dsc->dsc_version_major != 1)
+               return -EOPNOTSUPP;
+
+       switch (dsc->dsc_version_minor) {
+       case 1:
+               if (dsc->native_422 || dsc->native_420)
+                       return -EOPNOTSUPP;
+               params_type = DRM_DSC_1_1_PRE_SCR;
+               break;
+       case 2:
+               if (dsc->native_422)
+                       params_type = DRM_DSC_1_2_422;
+               else if (dsc->native_420)
+                       params_type = DRM_DSC_1_2_420;
+               else
+                       params_type = DRM_DSC_1_2_444;
+               break;
+       default:
+               return -EOPNOTSUPP;
+       }
+
+       drm_dsc_set_const_params(dsc);
+       drm_dsc_set_rc_buf_thresh(dsc);
+       ret = drm_dsc_setup_rc_params(dsc, params_type);
+       if (ret)
+               return ret;
+
+       dsc->initial_scale_value = drm_dsc_initial_scale_value(dsc);
+       dsc->line_buf_depth = dsc->bits_per_component + 1;
+       ret = drm_dsc_compute_rc_parameters(dsc);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < ARRAY_SIZE(cfg->dsc_rc_min_qp); i++) {
+               cfg->dsc_rc_min_qp[i] = dsc->rc_range_params[i].range_min_qp;
+               cfg->dsc_rc_max_qp[i] = dsc->rc_range_params[i].range_max_qp;
+               cfg->dsc_rc_bpg_offsets[i] = 
dsc->rc_range_params[i].range_bpg_offset;
+       }
+
+       return 0;
+}
+
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+                                  const struct drm_display_mode *mode)
+{
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       struct dcp_dsi_bridge_state *state = 
dcp_dsi_bridge_get_state(dsi_bridge);
+       struct dcp_dsi_hfi_panel_config cfg;
+       int ret;
+       u32 opened_session_id;
+
+       if (!mode)
+               return -ENODEV;
+
+       guard(mutex)(&dsi_bridge->session_lock);
+       if (dsi_bridge->session_open)
+               return 0;
+
+       dsi_bridge->session_id = dsi_bridge->fw_display_id;
+
+       if (!dsi_bridge->dsi_device)
+               return -ENODEV;
+
+       dcp_dsi_bridge_build_hfi_config(dsi_bridge, state, &cfg);
+
+       if (dsi_bridge->dsi_device->dsc) {
+               ret = dcp_dsi_bridge_setup_dsc(dsi_bridge, &cfg, mode);
+               if (ret) {
+                       drm_err(kms->dev, "DSI%u: DSC setup failed: %d\n",
+                               dsi_bridge->intf_idx, ret);
+                       goto err_close;
+               }
+       }
+
+       ret = dcp_hfi_panel_init(kms->hfi, dsi_bridge->intf_idx,
+                                mode,
+                                dsi_bridge->dsi_device,
+                                &cfg);
+       if (ret) {
+               drm_err(kms->dev, "DSI%u: HFI PANEL_INIT failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+               goto err_close;
+       }
+
+       if (mode->clock) {
+               u64 link_clock;
+
+               link_clock = dcp_dsi_link_clock(dsi_bridge, &cfg,
+                                               mode);
+               ret = dcp_hfi_display_mode_validate(kms->hfi,
+                                                   dsi_bridge->session_id,
+                                                   mode, link_clock);
+               if (ret) {
+                       drm_err(kms->dev, "DSI%u: HFI MODE_VALIDATE failed: 
%d\n",
+                               dsi_bridge->intf_idx, ret);
+                       goto err_close;
+               }
+
+               ret = dcp_hfi_display_set_mode(kms->hfi,
+                                              dsi_bridge->session_id,
+                                              mode,
+                                              dcp_dsi_link_clock(dsi_bridge, 
&cfg,
+                                                                 mode));
+               if (ret) {
+                       drm_err(kms->dev, "DSI%u: HFI SET_MODE failed: %d\n",
+                               dsi_bridge->intf_idx, ret);
+                       goto err_close;
+               }
+       } else {
+               drm_warn(kms->dev, "DSI%u: no valid mode, skipping SET_MODE\n",
+                        dsi_bridge->intf_idx);
+       }
+
+       dcp_hfi_set_power_control_cb(kms->hfi, kms,
+                                    dcp_dsi_power_control_cb);
+
+       reinit_completion(&dsi_bridge->power_done);
+
+       ret = dcp_hfi_display_power_register(kms->hfi, dsi_bridge->session_id);
+       if (ret) {
+               drm_err(kms->dev, "DSI%u: HFI POWER_REGISTER failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+               goto err_close;
+       }
+
+       ret = dcp_hfi_display_enable(kms->hfi,
+                                    dsi_bridge->intf_idx,
+                                    dsi_bridge->fw_display_id,
+                                    &opened_session_id);
+       if (ret) {
+               drm_err(kms->dev, "DSI%u: HFI DISPLAY_ENABLE failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+               goto err_close;
+       }
+       dsi_bridge->session_id   = opened_session_id;
+       dsi_bridge->session_open = true;
+       dcp_dsi_bridge_publish_session(dsi_bridge);
+
+       drm_dbg_driver(kms->dev, "DSI%u: session prepared (session_id=%u)\n",
+                      dsi_bridge->intf_idx, dsi_bridge->session_id);
+       return 0;
+
+err_close:
+       dcp_hfi_clear_power_control_cb(kms->hfi, kms);
+       dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+       dsi_bridge->session_id   = 0;
+       dsi_bridge->session_open = false;
+       dcp_dsi_bridge_clear_session(dsi_bridge);
+
+       return ret;
+}
+
+static void dcp_dsi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
+                                            struct drm_atomic_commit *state)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       int ret;
+
+       ret = dsi_bridge->session_open ? 0 : -ENODEV;
+       dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error = ret;
+       if (ret)
+               drm_err(bridge->dev, "DSI%u: session prepare failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_enable(struct drm_bridge *bridge,
+                                        struct drm_atomic_commit *state)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       bool scan_start_registered = false;
+       bool scan_complete_registered = false;
+       bool autorefresh_enabled = false;
+       int ret;
+
+       drm_dbg_driver(kms->dev,
+                      "DSI%u: bridge atomic_enable (session_id=%u)\n",
+                      dsi_bridge->intf_idx, dsi_bridge->session_id);
+
+       if (dcp_dsi_bridge_get_state(dsi_bridge)->prepare_error)
+               return;
+       if (!dsi_bridge->session_open)
+               return;
+
+       ret = dcp_hfi_display_post_enable(kms->hfi, dsi_bridge->session_id);
+       if (ret) {
+               drm_err(bridge->dev,
+                       "DSI%u: HFI DISPLAY_POST_ENABLE failed: %d\n",
+                       dsi_bridge->intf_idx, ret);
+               goto err_close;
+       }
+
+       dcp_dsi_bridge_publish_session(dsi_bridge);
+
+       ret = dcp_hfi_display_event_register(kms->hfi,
+                                            dsi_bridge->session_id, 
HFI_EVENT_FRAME_SCAN_START);
+       if (ret)
+               goto err_close;
+       scan_start_registered = true;
+
+       ret = dcp_hfi_display_event_register(kms->hfi,
+                                            dsi_bridge->session_id, 
HFI_EVENT_FRAME_SCAN_COMPLETE);
+       if (ret)
+               goto err_close;
+       scan_complete_registered = true;
+
+       if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+               ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+                                                  dsi_bridge->session_id, 
true, 1);
+               if (ret) {
+                       drm_err(bridge->dev,
+                               "DSI%u: initial autorefresh setup failed: %d\n",
+                               dsi_bridge->intf_idx, ret);
+                       goto err_close;
+               }
+               autorefresh_enabled = true;
+       }
+
+       drm_dbg_driver(bridge->dev,
+                      "DSI%u: enable complete (session_id=%u)\n",
+                      dsi_bridge->intf_idx, dsi_bridge->session_id);
+       return;
+
+err_close:
+       if (autorefresh_enabled)
+               dcp_hfi_crtc_set_autorefresh(kms->hfi, dsi_bridge->session_id,
+                                            false, 0);
+       if (scan_complete_registered)
+               dcp_hfi_display_event_deregister(kms->hfi, 
dsi_bridge->session_id,
+                                                HFI_EVENT_FRAME_SCAN_COMPLETE);
+       if (scan_start_registered)
+               dcp_hfi_display_event_deregister(kms->hfi, 
dsi_bridge->session_id,
+                                                HFI_EVENT_FRAME_SCAN_START);
+       drm_err(bridge->dev,
+               "DSI%u: bridge enable failed; session closed\n",
+               dsi_bridge->intf_idx);
+       dcp_hfi_display_close(kms->hfi, dsi_bridge->session_id);
+       {
+               guard(mutex)(&dsi_bridge->session_lock);
+               dsi_bridge->session_id   = 0;
+               dsi_bridge->session_open = false;
+       }
+       dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static void dcp_dsi_bridge_atomic_disable(struct drm_bridge *bridge,
+                                         struct drm_atomic_commit *state)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       int ret;
+
+       drm_dbg_driver(kms->dev,
+                      "DSI%u: atomic_disable (session_open=%d 
session_id=%u)\n",
+                      dsi_bridge->intf_idx, dsi_bridge->session_open,
+                      dsi_bridge->session_id);
+
+       guard(mutex)(&dsi_bridge->session_lock);
+       if (!dsi_bridge->session_open)
+               return;
+
+       if (dcp_dsi_bridge_cmd_mode(dsi_bridge)) {
+               ret = dcp_hfi_crtc_set_autorefresh(kms->hfi,
+                                                  dsi_bridge->session_id, 
false, 0);
+               if (ret)
+                       drm_warn(bridge->dev,
+                                "DSI%u: autorefresh disable failed: %d\n",
+                                dsi_bridge->intf_idx, ret);
+       }
+
+       dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+                                        HFI_EVENT_FRAME_SCAN_COMPLETE);
+       dcp_hfi_display_event_deregister(kms->hfi, dsi_bridge->session_id,
+                                        HFI_EVENT_FRAME_SCAN_START);
+
+       ret = dcp_hfi_display_disable(kms->hfi, dsi_bridge->session_id);
+       if (ret)
+               drm_warn(bridge->dev,
+                        "DSI%u: HFI DISPLAY_DISABLE failed: %d\n",
+                        dsi_bridge->intf_idx, ret);
+}
+
+static void dcp_dsi_bridge_atomic_post_disable(struct drm_bridge *bridge,
+                                              struct drm_atomic_commit *state)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       int ret;
+
+       drm_dbg_driver(kms->dev,
+                      "DSI%u: atomic_post_disable\n",
+                      dsi_bridge->intf_idx);
+
+       guard(mutex)(&dsi_bridge->session_lock);
+       if (!dsi_bridge->session_open)
+               return;
+
+       ret = dcp_hfi_display_post_disable(kms->hfi, dsi_bridge->session_id);
+       if (ret)
+               drm_warn(bridge->dev,
+                        "DSI%u: HFI DISPLAY_POST_DISABLE failed: %d\n",
+                        dsi_bridge->intf_idx, ret);
+
+       ret = dcp_hfi_send_sync(kms->hfi,
+                               HFI_COMMAND_DISPLAY_CLOSE,
+                               HFI_CMD_BUFF_DISPLAY, dsi_bridge->session_id,
+                               HFI_PAYLOAD_NONE, NULL, 0,
+                               HFI_TX_FLAGS_RESPONSE_REQUIRED |
+                               HFI_TX_FLAGS_NON_DISCARDABLE,
+                               NULL, 0,
+                               2000 /* ms */);
+       if (ret)
+               drm_warn(bridge->dev,
+                        "DSI%u: HFI DISPLAY_CLOSE failed: %d\n",
+                        dsi_bridge->intf_idx, ret);
+
+       dsi_bridge->session_id   = 0;
+       dsi_bridge->session_open = false;
+
+       dcp_dsi_bridge_clear_session(dsi_bridge);
+}
+
+static int dcp_dsi_bridge_atomic_check(struct drm_bridge *bridge,
+                                      struct drm_bridge_state *bridge_state,
+                                      struct drm_crtc_state *crtc_state,
+                                      struct drm_connector_state *conn_state)
+{
+       struct dcp_dsi_bridge *dsi_bridge = to_dcp_dsi_bridge(bridge);
+       struct dcp_dsi_bridge_state *state =
+               to_dcp_dsi_bridge_state(bridge_state);
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+       u32 max_width, max_height;
+       u32 mixer_count, bpc;
+
+       state->prepare_error = 0;
+
+       if (!mode->clock || !drm_mode_vrefresh(mode))
+               return -EINVAL;
+
+       if (dsi_bridge->dsi_device &&
+           (!dsi_bridge->dsi_device->lanes ||
+            !dcp_dsi_link_clock(dsi_bridge, &dsi_bridge->hfi_panel_config, 
mode)))
+               return -EINVAL;
+
+       if (dsi_bridge->dsi_device && dsi_bridge->dsi_device->dsc) {
+               state->panel_state.topology_mixer_count =
+                       dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+                                                      
&dsi_bridge->hfi_panel_config, mode);
+               state->panel_state.topology_enc_count =
+                       state->panel_state.topology_mixer_count;
+               state->panel_state.topology_intf_count = 1;
+       }
+
+       mixer_count = dcp_dsi_bridge_get_mixer_count(dsi_bridge,
+                                                    
&dsi_bridge->hfi_panel_config, mode);
+       bpc = dsi_bridge->hfi_panel_config.dsc_bits_per_component ?
+               dsi_bridge->hfi_panel_config.dsc_bits_per_component : 8;
+       dcp_crtc_set_topology_state(crtc_state, mixer_count, bpc);
+
+       dcp_mode_limits(mode, kms->caps->max_width, kms->caps->max_height,
+                       &max_width, &max_height);
+
+       if (mode->hdisplay > max_width || mode->vdisplay > max_height) {
+               drm_err(dsi_bridge->kms->dev,
+                       "DSI%u: mode %dx%d exceeds DCP caps %dx%d\n",
+                                dsi_bridge->intf_idx, mode->hdisplay, 
mode->vdisplay,
+                                kms->caps->max_width, kms->caps->max_height);
+               return -EINVAL;
+       }
+       return 0;
+}
+
+static const struct drm_bridge_funcs dcp_dsi_bridge_funcs = {
+       .attach                  = dcp_dsi_bridge_attach,
+       .atomic_pre_enable       = dcp_dsi_bridge_atomic_pre_enable,
+       .atomic_enable           = dcp_dsi_bridge_atomic_enable,
+       .atomic_disable          = dcp_dsi_bridge_atomic_disable,
+       .atomic_post_disable     = dcp_dsi_bridge_atomic_post_disable,
+       .atomic_check            = dcp_dsi_bridge_atomic_check,
+       .atomic_create_state     = dcp_dsi_bridge_atomic_create_state,
+       .atomic_duplicate_state  = dcp_dsi_bridge_atomic_duplicate_state,
+       .atomic_destroy_state    = dcp_dsi_bridge_atomic_destroy_state,
+};
+
+#define DCP_DSI_DUAL_MAP_ALIGN SZ_1K
+
+static int dcp_dsi_dual_map_buf(struct dcp_dpu_kms *kms,
+                               struct device *dma_dev, void *cpu_addr,
+                               dma_addr_t iova, size_t size, int prot)
+{
+       struct sg_table sgt = { 0 };
+       struct iommu_domain *domain;
+       ssize_t mapped;
+       int ret;
+
+       if (!kms->iommu_dev || !cpu_addr || !iova ||
+           !IS_ALIGNED(iova, DCP_DSI_DUAL_MAP_ALIGN))
+               return -EINVAL;
+
+       domain = iommu_get_domain_for_dev(kms->iommu_dev);
+       if (!domain)
+               return -ENODEV;
+
+       ret = dma_get_sgtable(dma_dev, &sgt, cpu_addr, iova, size);
+       if (ret)
+               return ret;
+
+       mapped = iommu_map_sgtable(domain, iova, &sgt, prot);
+       if (mapped != PAGE_ALIGN(size)) {
+               if (mapped > 0)
+                       iommu_unmap(domain, iova, mapped);
+               ret = mapped < 0 ? mapped : -EIO;
+               drm_err(kms->dev,
+                       "failed to dual-map iova=%pad into iommu_dev domain: 
%zd\n",
+                       &iova, mapped);
+               sg_free_table(&sgt);
+               return ret;
+       }
+
+       sg_free_table(&sgt);
+       return 0;
+}
+
+static void dcp_dsi_dual_unmap_buf(struct dcp_dpu_kms *kms, dma_addr_t iova,
+                                  size_t size)
+{
+       struct iommu_domain *domain;
+
+       if (!kms->iommu_dev || !iova)
+               return;
+
+       domain = iommu_get_domain_for_dev(kms->iommu_dev);
+       if (!domain)
+               return;
+
+       iommu_unmap(domain, iova, PAGE_ALIGN(size));
+}
+
+static void dcp_dsi_init_hfi_panel_defaults(struct dcp_dsi_hfi_panel_config 
*cfg,
+                                           u32 intf_idx)
+{
+       memset(cfg, 0, sizeof(*cfg));
+
+       cfg->display_type = HFI_PANEL_DISPLAY_TYPE_BUILT_IN_0;
+       cfg->num_ctrls = 1;
+       cfg->ctrl_indices[0] = intf_idx;
+       cfg->num_phys = 1;
+       cfg->phy_indices[0] = intf_idx;
+
+       cfg->dma_trigger = HFI_PANEL_TRIGGER_SW;
+       cfg->stream_trigger = HFI_PANEL_TRIGGER_NONE;
+       cfg->te_dcs_command = 1;
+       cfg->wr_mem_start = 0x2C;
+       cfg->wr_mem_continue = 0x3C;
+
+       cfg->phy_type = HFI_PANEL_PHY_OLED;
+       cfg->bllp_eof_power_mode = true;
+       cfg->bllp_power_mode = true;
+       cfg->lane_map = 1;
+       cfg->color_order = 1;
+
+       cfg->jitter_numer = 4;
+       cfg->jitter_denom = 1;
+       cfg->dcs_tx_buf_size = DCP_DSI_DCS_TX_BUF_SIZE;
+
+       cfg->topology_mixer_count = 1;
+       cfg->topology_intf_count = 1;
+}
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+                                      struct device_node *np,
+                                      struct device_node *dcp_np,
+                                      u32 dcp_port, u32 intf_idx,
+                                      u32 fw_display_id)
+{
+       struct dcp_dsi_bridge *dsi_bridge;
+       struct dcp_dsi_hfi_panel_config *cfg;
+       struct device *dev = kms->pm_dev;
+       struct device *dma_dev = kms->hfi->dma_dev;
+       int ret;
+
+       dsi_bridge = devm_drm_bridge_alloc(dev, struct dcp_dsi_bridge,
+                                          bridge, &dcp_dsi_bridge_funcs);
+       if (IS_ERR(dsi_bridge))
+               return ERR_CAST(dsi_bridge);
+
+       dsi_bridge->dcp_np        = of_node_get(dcp_np);
+       dsi_bridge->dcp_port      = dcp_port;
+       dsi_bridge->kms           = kms;
+       dsi_bridge->intf_idx      = intf_idx;
+       dsi_bridge->fw_display_id = fw_display_id;
+
+       INIT_WORK(&dsi_bridge->power_control_work, dcp_dsi_power_control_work);
+       init_completion(&dsi_bridge->power_done);
+       mutex_init(&dsi_bridge->session_lock);
+       mutex_init(&dsi_bridge->dcs_lock);
+
+       cfg = &dsi_bridge->hfi_panel_config;
+       dcp_dsi_init_hfi_panel_defaults(cfg, intf_idx);
+
+       /*
+        * Allocate the buffers for the firmware DMA domain here. The second
+        * mapping into the display domain is deferred until msm_kms_init_vm()
+        * has installed the final display IOMMU domain.
+        */
+       dsi_bridge->dcs_tx_buf = dma_alloc_coherent(dma_dev, 
DCP_DSI_DCS_TX_BUF_SIZE,
+                                                   &dsi_bridge->dcs_tx_iova, 
GFP_KERNEL);
+       if (!dsi_bridge->dcs_tx_buf) {
+               ret = -ENOMEM;
+               goto err_unwind_dma;
+       }
+
+       dsi_bridge->hfi_panel_config.dcs_tx_dva = dsi_bridge->dcs_tx_iova;
+       dsi_bridge->hfi_panel_config.dcs_tx_iova = dsi_bridge->dcs_tx_iova;
+
+       dsi_bridge->dcs_rx_buf = dma_alloc_coherent(dma_dev, 
DCP_DSI_DCS_RX_BUF_SIZE,
+                                                   &dsi_bridge->dcs_rx_iova, 
GFP_KERNEL);
+       if (!dsi_bridge->dcs_rx_buf) {
+               ret = -ENOMEM;
+               goto err_unwind_dma;
+       }
+
+       device_initialize(&dsi_bridge->dsi_host_dev);
+       dsi_bridge->dsi_host_dev.release = dcp_dsi_host_dev_release;
+       dsi_bridge->dsi_host_dev.parent  = dev;
+       dsi_bridge->dsi_host_dev.of_node = of_node_get(np);
+       dev_set_name(&dsi_bridge->dsi_host_dev, "%pOFn", np);
+       dsi_bridge->dsi_host.ops = &dcp_dsi_host_ops;
+       dsi_bridge->dsi_host.dev = &dsi_bridge->dsi_host_dev;
+
+       ret = device_add(&dsi_bridge->dsi_host_dev);
+       if (ret) {
+               dev_err(dev,
+                       "DSI%u: failed to add dsi_host_dev: %d\n", intf_idx, 
ret);
+               put_device(&dsi_bridge->dsi_host_dev);
+               goto err_unwind_dma;
+       }
+
+       ret = mipi_dsi_host_register(&dsi_bridge->dsi_host);
+       if (ret) {
+               dev_err(dev,
+                       "DSI%u: failed to register MIPI DSI host: %d\n",
+                       intf_idx, ret);
+               device_del(&dsi_bridge->dsi_host_dev);
+               put_device(&dsi_bridge->dsi_host_dev);
+               goto err_unwind_dma;
+       }
+
+       dsi_bridge->bridge.of_node = of_node_get(np);
+       dsi_bridge->bridge.type    = DRM_MODE_CONNECTOR_DSI;
+
+       drm_bridge_add(&dsi_bridge->bridge);
+
+       return &dsi_bridge->bridge;
+
+err_unwind_dma:
+       if (dsi_bridge->dcs_rx_buf) {
+               dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+                                 dsi_bridge->dcs_rx_buf,
+                                 dsi_bridge->dcs_rx_iova);
+       }
+       if (dsi_bridge->dcs_tx_buf) {
+               dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+                                 dsi_bridge->dcs_tx_buf,
+                                 dsi_bridge->dcs_tx_iova);
+       }
+       cancel_work_sync(&dsi_bridge->power_control_work);
+       mutex_destroy(&dsi_bridge->session_lock);
+       mutex_destroy(&dsi_bridge->dcs_lock);
+       return ERR_PTR(ret);
+}
+
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+       struct device *dma_dev = kms->hfi->dma_dev;
+       int ret;
+
+       if (dsi_bridge->dcs_mapped)
+               return 0;
+
+       ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_tx_buf,
+                                  dsi_bridge->dcs_tx_iova,
+                                  DCP_DSI_DCS_TX_BUF_SIZE, IOMMU_READ);
+       if (ret)
+               return ret;
+
+       ret = dcp_dsi_dual_map_buf(kms, dma_dev, dsi_bridge->dcs_rx_buf,
+                                  dsi_bridge->dcs_rx_iova,
+                                  DCP_DSI_DCS_RX_BUF_SIZE, IOMMU_WRITE);
+       if (ret) {
+               dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+                                      DCP_DSI_DCS_TX_BUF_SIZE);
+               return ret;
+       }
+
+       dsi_bridge->dcs_mapped = true;
+       drm_dbg_driver(kms->dev,
+                      "DSI%u: mapped DCS buffers tx=%pad rx=%pad in display 
domain\n",
+                      dsi_bridge->intf_idx, &dsi_bridge->dcs_tx_iova,
+                      &dsi_bridge->dcs_rx_iova);
+
+       return 0;
+}
+
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge)
+{
+       struct dcp_dpu_kms *kms = dsi_bridge->kms;
+
+       if (!dsi_bridge->dcs_mapped)
+               return;
+
+       dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_tx_iova,
+                              DCP_DSI_DCS_TX_BUF_SIZE);
+       dcp_dsi_dual_unmap_buf(kms, dsi_bridge->dcs_rx_iova,
+                              DCP_DSI_DCS_RX_BUF_SIZE);
+       dsi_bridge->dcs_mapped = false;
+}
+
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge)
+{
+       struct device *dma_dev = dsi_bridge->kms->hfi->dma_dev;
+
+       dcp_hfi_clear_power_control_cb(dsi_bridge->kms->hfi, dsi_bridge->kms);
+       cancel_work_sync(&dsi_bridge->power_control_work);
+
+       drm_bridge_remove(&dsi_bridge->bridge);
+       of_node_put(dsi_bridge->bridge.of_node);
+       of_node_put(dsi_bridge->dcp_np);
+
+       mipi_dsi_host_unregister(&dsi_bridge->dsi_host);
+       device_del(&dsi_bridge->dsi_host_dev);
+       put_device(&dsi_bridge->dsi_host_dev);
+
+       dcp_dsi_bridge_unmap_buffers(dsi_bridge);
+       dma_free_coherent(dma_dev, DCP_DSI_DCS_TX_BUF_SIZE,
+                         dsi_bridge->dcs_tx_buf, dsi_bridge->dcs_tx_iova);
+
+       dma_free_coherent(dma_dev, DCP_DSI_DCS_RX_BUF_SIZE,
+                         dsi_bridge->dcs_rx_buf, dsi_bridge->dcs_rx_iova);
+
+       mutex_destroy(&dsi_bridge->session_lock);
+       mutex_destroy(&dsi_bridge->dcs_lock);
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h 
b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
new file mode 100644
index 000000000000..f2c7abf23ac9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_bridge.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP DSI bridge state and panel configuration.
+ * DCP firmware owns the DSI PHY and controller; the bridge submits HFI 
commands.
+ */
+
+#ifndef __DCP_DSI_BRIDGE_H__
+#define __DCP_DSI_BRIDGE_H__
+
+#include <linux/completion.h>
+#include <linux/dma-mapping.h>
+#include <linux/ktime.h>
+#include <linux/workqueue.h>
+
+#include <drm/drm_bridge.h>
+#include <drm/drm_encoder.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_modes.h>
+
+#include "dcp_dsi_panel.h"
+
+struct dcp_dpu_kms;
+struct device_node;
+
+struct dcp_dsi_bridge {
+       struct drm_bridge    bridge;
+       struct device        dsi_host_dev;
+       struct mipi_dsi_host  dsi_host;
+       struct mipi_dsi_device *dsi_device;
+       struct drm_bridge *panel_bridge;
+       bool component_added;
+
+       struct dcp_dpu_kms  *kms;
+       struct device_node  *dcp_np;
+       u32                  dcp_port;
+       u32                  intf_idx;
+       u32                  fw_display_id;
+       u32                  session_id;
+       bool                 session_open;
+       /* Protects session_id and session_open. */
+       struct mutex         session_lock;
+
+       struct dcp_dsi_hfi_panel_config hfi_panel_config;
+       /* Serializes DCS transfers. */
+       struct mutex dcs_lock;
+
+       void       *dcs_tx_buf;
+       dma_addr_t  dcs_tx_iova;
+       void       *dcs_rx_buf;
+       dma_addr_t  dcs_rx_iova;
+       bool         dcs_mapped;
+       struct work_struct power_control_work;
+       struct completion  power_done;
+       u32                power_packet_id;
+};
+
+#define to_dcp_dsi_host(h) \
+       container_of(h, struct dcp_dsi_bridge, dsi_host)
+
+#define to_dcp_dsi_bridge(b) \
+       container_of(b, struct dcp_dsi_bridge, bridge)
+
+struct drm_bridge *dcp_dsi_bridge_init(struct dcp_dpu_kms *kms,
+                                      struct device_node *np,
+                                      struct device_node *dcp_np,
+                                      u32 dcp_port, u32 intf_idx,
+                                      u32 fw_display_id);
+int dcp_dsi_bridge_map_buffers(struct dcp_dsi_bridge *dsi_bridge);
+void dcp_dsi_bridge_unmap_buffers(struct dcp_dsi_bridge *dsi_bridge);
+int dcp_dsi_bridge_prepare_session(struct dcp_dsi_bridge *dsi_bridge,
+                                  const struct drm_display_mode *mode);
+void dcp_dsi_bridge_cleanup(struct dcp_dsi_bridge *dsi_bridge);
+
+#endif /* __DCP_DSI_BRIDGE_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h 
b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
index a8898e469b44..cc4e9f4c6fc4 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_dsi_panel.h
@@ -74,6 +74,19 @@ struct dcp_dsi_hfi_panel_config {
        u32 topology_intf_count;
 };
 
+/**
+ * struct dcp_dsi_panel_state - Mode-dependent DSI panel state.
+ *
+ * This state is owned by a DRM atomic bridge state. It is combined with
+ * the static panel configuration when constructing the HFI PANEL_INIT
+ * payload.
+ */
+struct dcp_dsi_panel_state {
+       u32 topology_mixer_count;
+       u32 topology_enc_count;
+       u32 topology_intf_count;
+};
+
 /* DCS buffers use IOVAs directly in the firmware command descriptor. */
 #define DCP_DSI_DCS_TX_BUF_SIZE        SZ_4K
 #define DCP_DSI_DCS_RX_BUF_SIZE        SZ_4K

-- 
2.43.0


Reply via email to