+ dcp_crtc->display_id, i);
+ cstate->hfi_error = -EINVAL;
+ return;
+ }
+
+ ret = dcp_hfi_crtc_set_resource(dcp_kms->hfi, session_id,
+ i, roi->w, roi->h, roi->x,
roi->y);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+ }
+
+ ret = dcp_hfi_crtc_set_dest_roi(dcp_kms->hfi, session_id,
+ cstate->update_roi.x,
cstate->update_roi.y,
+ cstate->update_roi.w,
cstate->update_roi.h);
+ if (ret) {
+ cstate->hfi_error = ret;
+ return;
+ }
+}
+
+static void dcp_crtc_atomic_flush(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ unsigned long flags;
+ u32 session_id;
+ int ret;
+
+ if (!crtc->state->enable)
+ return;
+ drm_dbg_driver(crtc->dev, "DCP_FLUSH CRTC%u mask=0x%x err=%d\n",
+ dcp_crtc->display_id, crtc->state->plane_mask,
+ cstate->hfi_error);
+
+ if (!dcp_crtc_get_session(crtc, &session_id)) {
+ dev_dbg(crtc->dev->dev,
+ "CRTC%u: atomic flush without HFI session\n",
+ dcp_crtc->display_id);
+ goto flush_done;
+ }
+
+ if (cstate->hfi_error)
+ goto flush_done;
+
+ ret = dcp_hfi_crtc_set_scan_sequence_id(dcp_kms->hfi, session_id,
+ ++dcp_crtc->commit_seq_id);
+ if (ret)
+ goto flush_error;
+
+ ret = dcp_hfi_crtc_set_autorefresh(dcp_kms->hfi,
+ session_id, true, 1);
+ if (ret)
+ goto flush_error;
+
+ goto flush_done;
+
+flush_error:
+ cstate->hfi_error = ret;
+
+flush_done:
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ dcp_crtc->event = crtc->state->event;
+ crtc->state->event = NULL;
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+
+ if (!crtc)
+ return;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ dcp_crtc->session_id = session_id;
+ dcp_crtc->session_valid = true;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+}
+
+void dcp_crtc_clear_session(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+
+ if (!crtc)
+ return;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ dcp_crtc->session_valid = false;
+ dcp_crtc->session_id = 0;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+}
+
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id)
+{
+ struct dcp_crtc *dcp_crtc;
+ unsigned long flags;
+ bool valid;
+
+ if (!crtc)
+ return false;
+
+ dcp_crtc = to_dcp_crtc(crtc);
+ spin_lock_irqsave(&dcp_crtc->spin_lock, flags);
+ valid = dcp_crtc->session_valid;
+ if (valid && session_id)
+ *session_id = dcp_crtc->session_id;
+ spin_unlock_irqrestore(&dcp_crtc->spin_lock, flags);
+
+ return valid;
+}
+
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+ u32 mixer_count,
+ u32 bits_per_component)
+{
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(state);
+
+ cstate->topology_mixer_count = mixer_count;
+ cstate->topology_bits_per_component = bits_per_component;
+}
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (cstate->hfi_error) {
+ dev_err(crtc->dev->dev,
+ "DCP_TRIGGER CRTC%u suppressed after HFI error %d\n",
+ dcp_crtc->display_id, cstate->hfi_error);
+ return;
+ }
+
+ if (!dcp_crtc_get_session(crtc, &session_id)) {
+ dev_warn(crtc->dev->dev,
+ "CRTC%u: no active session, skipping FRAME_TRIGGER\n",
+ dcp_crtc->display_id);
+ return;
+ }
+
+ dcp_crtc->pending_seq_id = dcp_crtc->commit_seq_id;
+ atomic_inc(&dcp_crtc->pending_kickoff_cnt);
+
+ ret = dcp_hfi_display_commit(dcp_kms->hfi, session_id);
+ if (ret) {
+ dev_err(crtc->dev->dev,
+ "HFI commit failed for CRTC%u (session=%u): %d\n",
+ dcp_crtc->display_id, session_id, ret);
+ atomic_dec(&dcp_crtc->pending_kickoff_cnt);
+ wake_up(&dcp_crtc->frame_done_wq);
+ }
+}
+
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ unsigned long timeout = msecs_to_jiffies(DCP_FRAME_DONE_TIMEOUT_MS);
+
+ if (!atomic_read(&dcp_crtc->pending_kickoff_cnt))
+ return;
+
+ if (!wait_event_timeout(dcp_crtc->frame_done_wq,
+ atomic_read(&dcp_crtc->pending_kickoff_cnt) ==
0,
+ timeout)) {
+ dev_err(crtc->dev->dev,
+ "frame done timeout for CRTC%u\n",
+ dcp_crtc->display_id);
+ atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+ }
+}
+
+void dcp_crtc_complete_commit(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ unsigned long flags;
+
+ if (!crtc->state->active)
+ dcp_crtc_aggregate_bw(crtc->state, false);
+
+ spin_lock_irqsave(&crtc->dev->event_lock, flags);
+ if (dcp_crtc->event) {
+ drm_crtc_send_vblank_event(crtc, dcp_crtc->event);
+ dcp_crtc->event = NULL;
+ }
+ spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+}
+
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ u32 session_id;
+ int ret;
+
+ if (!dcp_kms->hfi || !dcp_crtc_get_session(crtc, &session_id))
+ return -ENODEV;
+
+ if (enable)
+ ret = dcp_hfi_display_event_register_async(dcp_kms->hfi,
+ session_id,
HFI_EVENT_VSYNC);
+ else
+ ret = dcp_hfi_display_event_deregister_async(dcp_kms->hfi,
+ session_id,
HFI_EVENT_VSYNC);
+ if (ret)
+ return ret;
+
+ WRITE_ONCE(dcp_crtc->vblank_enabled, enable);
+ return 0;
+}
+
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ if (!READ_ONCE(dcp_crtc->vblank_enabled))
+ return;
+
+ if (ktime_compare(dcp_crtc->vblank_cb_time, ktime_set(0, 0)) == 0)
+ dcp_crtc->vblank_cb_time = ktime_get();
+ else
+ dcp_crtc->vblank_cb_count++;
+ drm_crtc_handle_vblank(crtc);
+}
+
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc)
+{
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ atomic_add_unless(&dcp_crtc->pending_kickoff_cnt, -1, 0);
+ wake_up(&dcp_crtc->frame_done_wq);
+}
+
+static struct drm_crtc *dcp_crtc_find_by_session_id(struct dcp_drv *drv,
+ u32 session_id)
+{
+ struct dcp_dpu_kms *dcp_kms;
+ struct drm_crtc *crtc;
+ u32 active_session_id;
+
+ if (!drv || !drv->kms)
+ return NULL;
+
+ dcp_kms = drv->kms;
+ if (!dcp_kms)
+ return NULL;
+ if (session_id >= DCP_MAX_DISPLAYS)
+ return NULL;
+
+ crtc = dcp_dpu_kms_get_crtc(dcp_kms, session_id);
+ if (!crtc || !dcp_crtc_get_session(crtc, &active_session_id) ||
+ active_session_id != session_id)
+ return NULL;
+
+ return crtc;
+}
+
+static struct drm_device *dcp_crtc_get_drm_dev(struct dcp_drv *drv)
+{
+ struct dcp_dpu_kms *dcp_kms;
+
+ if (!drv || !drv->kms)
+ return NULL;
+
+ dcp_kms = drv->kms;
+ if (!dcp_kms)
+ return NULL;
+
+ return dcp_kms->dev;
+}
+
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id)
+{
+ struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc)
+ dcp_crtc_vblank_callback(crtc);
+ else if (dcp_crtc_get_drm_dev(drv))
+ drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
+ "unknown CRTC for HFI VSYNC session %u\n",
+ session_id);
+}
+
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32
sequence_id)
+{
+ struct drm_crtc *crtc = dcp_crtc_find_by_session_id(drv, session_id);
+
+ if (crtc) {
+ struct dcp_crtc *dcp_crtc = to_dcp_crtc(crtc);
+
+ if (sequence_id != dcp_crtc->pending_seq_id) {
+ dev_warn(crtc->dev->dev,
+ "CRTC%u: ignoring stale FRAME_SCAN_COMPLETE seq=%u
expected=%u\n",
+ dcp_crtc->display_id, sequence_id,
+ dcp_crtc->pending_seq_id);
+ return;
+ }
+ dcp_crtc_frame_done_callback(crtc);
+ } else if (dcp_crtc_get_drm_dev(drv))
+ drm_err_ratelimited(dcp_crtc_get_drm_dev(drv),
+ "unknown CRTC for HFI frame-done session %u
seq=%u\n",
+ session_id, sequence_id);
+}
+
+static int dcp_crtc_setup_mixer_rois(struct drm_crtc *crtc,
+ struct drm_crtc_state *crtc_state,
+ struct dcp_crtc_state *cstate)
+{
+ const struct drm_plane_state *pstate;
+ struct drm_plane *plane;
+ const struct drm_display_mode *mode = &crtc_state->adjusted_mode;
+ u32 mixer_count = cstate->topology_mixer_count;
+ u32 mixer_width = mode->hdisplay / mixer_count;
+ u32 i;
+
+ if (!mixer_width)
+ return -EINVAL;
+
+ cstate->update_roi.x = 0;
+ cstate->update_roi.y = 0;
+ cstate->update_roi.w = mode->hdisplay;
+ cstate->update_roi.h = mode->vdisplay;
+ for (i = 0; i < mixer_count; i++) {
+ cstate->mixer_roi[i].x = (mode->hdisplay * i) / mixer_count;
+ cstate->mixer_roi[i].y = 0;
+ cstate->mixer_roi[i].w = (mode->hdisplay * (i + 1)) /
mixer_count -
+ cstate->mixer_roi[i].x;
+ cstate->mixer_roi[i].h = mode->vdisplay;
+ dev_dbg(crtc->dev->dev,
+ "DCP_CHECK CRTC%u LM%u roi=(%u,%u %ux%u)\n",
+ to_dcp_crtc(crtc)->display_id, i,
+ cstate->mixer_roi[i].x, cstate->mixer_roi[i].y,
+ cstate->mixer_roi[i].w, cstate->mixer_roi[i].h);
+ }
+
+ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ bool fullscreen;
+ bool assigned = false;
+ u32 mixer_idx;
+
+ if (!pstate || !pstate->visible || !pstate->fb)
+ continue;
+
+ for (mixer_idx = 0; mixer_idx < mixer_count; mixer_idx++) {
+ struct dcp_mixer_roi *mixer =
&cstate->mixer_roi[mixer_idx];
+
+ if (pstate->crtc_x >= mixer->x &&
+ pstate->crtc_x + pstate->crtc_w <= mixer->x +
mixer->w) {
+ assigned = true;
+ break;
+ }
+ }
+
+ /* Any plane may cover the complete split display. Plane type is
+ * not a resource-selection mechanism in the universal-plane
API.
+ */
+ fullscreen = pstate->crtc_x == 0 && pstate->crtc_y == 0 &&
+ pstate->crtc_w == mode->hdisplay &&
+ pstate->crtc_h == mode->vdisplay;
+
+ if (!assigned && !fullscreen && mixer_count > 1) {
+ /*
+ * Split-pipe/source-split and multirect allocation are
+ * deferred until the atomic pipe resource manager is
added.
+ * Reject planes needing multiple mixer rectangles for
now.
+ */
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int dcp_crtc_atomic_check(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct drm_crtc_state *crtc_state =
+ drm_atomic_get_new_crtc_state(state, crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc_state);
+ struct drm_plane *plane;
+ const struct drm_plane_state *pstate;
+ u32 max_width, max_height;
+ u32 mixer_count;
+ int num_planes = 0;
+ int ret;
+
+ drm_dbg_atomic(crtc->dev, "CRTC%u: atomic_check enable=%d active=%d
mode=%s\n",
+ crtc->base.id, crtc_state->enable, crtc_state->active,
+ crtc_state->mode.name[0] ? crtc_state->mode.name : "none");
+
+ if (!crtc_state->enable || !crtc_state->active)
+ return 0;
+
+ mixer_count = cstate->topology_mixer_count;
+ if (mixer_count < 1 || mixer_count > DCP_MAX_MIXERS) {
+ drm_err(crtc->dev,
+ "CRTC%u: invalid mixer count %u (limit 1..%u)\n",
+ crtc->base.id, mixer_count, DCP_MAX_MIXERS);
+ return -EINVAL;
+ }
+
+ ret = dcp_crtc_setup_mixer_rois(crtc, crtc_state, cstate);
+ if (ret)
+ return ret;
+
+ dcp_mode_limits(&crtc_state->adjusted_mode,
+ dcp_kms->caps->max_width, dcp_kms->caps->max_height,
+ &max_width, &max_height);
+
+ if (crtc_state->adjusted_mode.hdisplay > max_width ||
+ crtc_state->adjusted_mode.vdisplay > max_height) {
+ drm_err(crtc->dev, "CRTC%u: mode %dx%d exceeds DCP caps
%dx%d\n",
+ crtc->base.id,
+ crtc_state->adjusted_mode.hdisplay,
+ crtc_state->adjusted_mode.vdisplay,
+ dcp_kms->caps->max_width,
+ dcp_kms->caps->max_height);
+ return -E2BIG;
+ }
+
+ /* Limit visible planes to the firmware blend-stage count. */
+ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, crtc_state) {
+ if (!pstate || !pstate->visible)
+ continue;
+ num_planes++;
+ }
+
+ if (num_planes > DCP_MAX_BLEND_STAGES) {
+ drm_err(crtc->dev, "CRTC%u: %d planes exceeds max blend stages
%d\n",
+ crtc->base.id, num_planes,
+ DCP_MAX_BLEND_STAGES);
+ return -EINVAL;
+ }
+
+ dcp_crtc_aggregate_bw(crtc_state, true);
+
+ return 0;
+}
+
+static u32 dcp_crtc_get_vblank_counter(struct drm_crtc *crtc)
+{
+ return drm_crtc_vblank_count(crtc);
+}
+
+static void dcp_crtc_atomic_enable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (!dcp_crtc_get_session(crtc, &session_id))
+ return;
+
+ ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+ HFI_MODE_DPMS_ON);
+ if (ret) {
+ dev_err(crtc->dev->dev, "CRTC%u: DPMS ON failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+
+ drm_crtc_vblank_on(crtc);
+}
+
+static void dcp_crtc_atomic_disable(struct drm_crtc *crtc,
+ struct drm_atomic_commit *state)
+{
+ struct dcp_dpu_kms *dcp_kms = dcp_crtc_get_kms(crtc);
+ struct dcp_crtc_state *cstate = to_dcp_crtc_state(crtc->state);
+ u32 session_id;
+ int ret;
+
+ if (dcp_crtc_get_session(crtc, &session_id)) {
+ ret = dcp_hfi_crtc_set_bw(dcp_kms->hfi, session_id, 0, 0, 0, 0,
0);
+ if (ret) {
+ dev_err(crtc->dev->dev,
+ "CRTC%u: clearing bandwidth vote failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+
+ ret = dcp_hfi_crtc_set_power_mode(dcp_kms->hfi, session_id,
+ HFI_MODE_DPMS_OFF);
+ if (ret) {
+ dev_err(crtc->dev->dev, "CRTC%u: DPMS OFF failed: %d\n",
+ to_dcp_crtc(crtc)->display_id, ret);
+ cstate->hfi_error = ret;
+ }
+ }
+
+ drm_crtc_vblank_off(crtc);
+}
+
+static const struct drm_crtc_funcs dcp_crtc_funcs = {
+ .set_config = drm_atomic_helper_set_config,
+ .page_flip = drm_atomic_helper_page_flip,
+ .atomic_create_state = dcp_crtc_atomic_create_state,
+ .atomic_duplicate_state = dcp_crtc_atomic_duplicate_state,
+ .atomic_destroy_state = dcp_crtc_atomic_destroy_state,
+ .enable_vblank = msm_crtc_enable_vblank,
+ .disable_vblank = msm_crtc_disable_vblank,
+ .get_vblank_counter = dcp_crtc_get_vblank_counter,
+};
+
+static const struct drm_crtc_helper_funcs dcp_crtc_helper_funcs = {
+ .atomic_check = dcp_crtc_atomic_check,
+ .atomic_enable = dcp_crtc_atomic_enable,
+ .atomic_disable = dcp_crtc_atomic_disable,
+ .atomic_begin = dcp_crtc_atomic_begin,
+ .atomic_flush = dcp_crtc_atomic_flush,
+ .mode_valid = dcp_crtc_mode_valid,
+};
+
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+ struct drm_plane *primary_plane,
+ u32 display_id)
+{
+ struct drm_device *dev = kms->dev;
+ struct dcp_crtc *dcp_crtc;
+ int ret;
+
+ dcp_crtc = drmm_kzalloc(dev, sizeof(*dcp_crtc), GFP_KERNEL);
+ if (!dcp_crtc)
+ return ERR_PTR(-ENOMEM);
+
+ dcp_crtc->display_id = display_id;
+
+ spin_lock_init(&dcp_crtc->spin_lock);
+ init_waitqueue_head(&dcp_crtc->frame_done_wq);
+ atomic_set(&dcp_crtc->pending_kickoff_cnt, 0);
+
+ ret = drmm_crtc_init_with_planes(dev, &dcp_crtc->base,
+ primary_plane, NULL,
+ &dcp_crtc_funcs,
+ NULL);
+ if (ret) {
+ dev_err(dev->dev, "CRTC%u init failed: %d\n", display_id, ret);
+ return ERR_PTR(ret);
+ }
+
+ drm_crtc_helper_add(&dcp_crtc->base, &dcp_crtc_helper_funcs);
+
+ dev_dbg(dev->dev, "CRTC%u initialized\n", display_id);
+ return &dcp_crtc->base;
+}
diff --git a/drivers/gpu/drm/msm/dcp/dcp_crtc.h
b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
new file mode 100644
index 000000000000..d22c891750f4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_crtc.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP CRTC implementation.
+ */
+
+#ifndef __DCP_CRTC_H__
+#define __DCP_CRTC_H__
+
+#include <linux/completion.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_crtc.h>
+
+struct dcp_dpu_kms;
+struct drm_plane;
+
+#define DCP_MAX_MIXERS 6
+#define DCP_MAX_BLEND_STAGES 12
+
+static inline void dcp_mode_limits(const struct drm_display_mode *mode,
+ u32 cap_width, u32 cap_height,
+ u32 *max_width, u32 *max_height)
+{
+ *max_width = cap_width;
+ *max_height = cap_height;
+
+ if (mode->hdisplay < mode->vdisplay && cap_width > cap_height) {
+ *max_width = cap_height;
+ *max_height = cap_width;
+ }
+}
+
+struct dcp_mixer_roi {
+ u32 x;
+ u32 y;
+ u32 w;
+ u32 h;
+};
+
+struct dcp_crtc {
+ struct drm_crtc base;
+ u32 display_id;
+ u32 session_id;
+ bool session_valid;
+
+ struct drm_pending_vblank_event *event;
+ wait_queue_head_t frame_done_wq;
+ atomic_t pending_kickoff_cnt;
+ /* Protects CRTC-local vblank and commit state. */
+ spinlock_t spin_lock;
+ u32 vblank_cb_count;
+ bool vblank_enabled;
+ ktime_t vblank_cb_time;
+ bool enabled;
+ u32 commit_seq_id;
+ u32 pending_seq_id;
+};
+
+struct dcp_crtc_state {
+ struct drm_crtc_state base;
+
+ u32 topology_mixer_count;
+ u32 topology_bits_per_component;
+ struct dcp_mixer_roi mixer_roi[DCP_MAX_MIXERS];
+
+ struct dcp_mixer_roi update_roi;
+ int hfi_error;
+ u64 bw_ctl;
+
+ u64 dram_ib;
+ u64 dram_ab;
+ u64 llcc_ib;
+ u64 llcc_ab;
+ u64 core_clk;
+};
+
+#define to_dcp_crtc(x) container_of(x, struct dcp_crtc, base)
+#define to_dcp_crtc_state(x) container_of(x, struct dcp_crtc_state, base)
+
+struct drm_crtc *dcp_crtc_init(struct dcp_dpu_kms *kms,
+ struct drm_plane *primary_plane,
+ u32 display_id);
+
+void dcp_crtc_commit_kickoff(struct drm_crtc *crtc);
+void dcp_crtc_wait_for_commit_done(struct drm_crtc *crtc);
+void dcp_crtc_complete_commit(struct drm_crtc *crtc);
+int dcp_crtc_vblank(struct drm_crtc *crtc, bool enable);
+void dcp_crtc_vblank_callback(struct drm_crtc *crtc);
+void dcp_crtc_frame_done_callback(struct drm_crtc *crtc);
+
+struct dcp_drv;
+void dcp_crtc_hfi_vblank_cb(struct dcp_drv *drv, u32 session_id);
+void dcp_crtc_hfi_frame_done_cb(struct dcp_drv *drv, u32 session_id, u32
sequence_id);
+
+void dcp_crtc_set_session(struct drm_crtc *crtc, u32 session_id);
+void dcp_crtc_clear_session(struct drm_crtc *crtc);
+bool dcp_crtc_get_session(struct drm_crtc *crtc, u32 *session_id);
+void dcp_crtc_set_topology_state(struct drm_crtc_state *state,
+ u32 mixer_count,
+ u32 bits_per_component);
+
+#endif /* __DCP_CRTC_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
new file mode 100644
index 000000000000..573376785be2
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.c
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS driver.
+ */
+
+#include <drm/drm_crtc.h>
+
+#include "dcp_dpu_kms.h"
+
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dpu_kms,
+ u32 display_id)
+{
+ struct drm_crtc *crtc;
+ u32 idx = 0;
+
+ if (!dpu_kms || !dpu_kms->dev)
+ return NULL;
+
+ drm_for_each_crtc(crtc, dpu_kms->dev) {
+ if (idx++ == display_id)
+ return crtc;
+ }
+
+ return NULL;
+}
+
diff --git a/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
new file mode 100644
index 000000000000..4601571a994f
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_dpu_kms.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * DCP KMS state.
+ */
+
+#ifndef __DCP_DPU_KMS_H__
+#define __DCP_DPU_KMS_H__
+
+#include <linux/spinlock.h>
+
+#include "msm_drv.h"
+#include "msm_kms.h"
+
+struct dcp_hfi;
+struct dcp_caps;
+struct dcp_dsi_bridge;
+struct dcp_drv;
+struct drm_crtc;
+
+#define DCP_DPU_AUTOSUSPEND_DELAY_MS 5000
+#define DCP_DPU_MAX_CRTCS 4
+#define DCP_DPU_MAX_PLANES 12
+
+struct dcp_dpu_kms {
+ struct msm_kms base;
+ struct drm_device *dev;
+ struct device *pm_dev;
+ struct dcp_drv *drv;
+ struct dcp_hfi *hfi;
+ struct dcp_caps *caps;
+ struct device_node *dpus_np;
+ struct device *iommu_dev;
+ bool rpm_enabled;
+ bool commit_pm_active;
+ struct dcp_dsi_bridge *dsi_bridge[2];
+};
+
+#define to_dcp_dpu_kms(x) container_of(x, struct dcp_dpu_kms, base)
+struct drm_crtc *dcp_dpu_kms_get_crtc(struct dcp_dpu_kms *dpu_kms,
+ u32 display_id);
+int dcp_dpu_kms_prepare_crtc_session(struct dcp_dpu_kms *dpu_kms,
+ struct drm_crtc *crtc);
+
+int dcp_dpu_drm_obj_init(struct dcp_dpu_kms *dpu_kms);
+extern const struct msm_kms_funcs dcp_dpu_kms_funcs;
+int dcp_kms_init(struct drm_device *ddev);
+
+#endif /* __DCP_DPU_KMS_H__ */
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
index 664d44f0e48b..25e822ce096a 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi.h
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi.h
@@ -169,6 +169,8 @@ struct dcp_hfi {
struct mutex caps_cache_lock;
struct dcp_hfi_caps_cache *caps_cache;
+ void (*vblank_cb)(struct dcp_drv *drv, u32 display_id);
+ void (*frame_done_cb)(struct dcp_drv *drv, u32 display_id, u32
sequence_id);
void (*hotplug_cb)(struct dcp_drv *drv, u32 intf_idx, bool connected);
void (*error_cb)(struct dcp_drv *drv, u32 display_id, u32 error_code);
diff --git a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
index bf27cbf9b8a4..fea14a2d5095 100644
--- a/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
+++ b/drivers/gpu/drm/msm/dcp/dcp_hfi_core.c
@@ -20,6 +20,7 @@
#include "dcp_hfi.h"
#include "dcp_hfi_priv.h"
#include "dcp_drv.h"
+#include "dcp_crtc.h"
#include "hfi/hfi_packet.h"
#include "hfi/hfi_packer.h"
#include "hfi/hfi_unpacker.h"
@@ -264,18 +265,35 @@ static void dcp_hfi_store_caps_payload(struct dcp_hfi
*hfi,
dev_dbg(hfi->dev, "%s: stored %u bytes\n", name, n);
}
+static void dcp_hfi_dispatch_frame_done(struct dcp_hfi *hfi,
+ const struct hfi_packet_info *pkt_info)
+{
+ const struct hfi_display_frame_event_data *frame = NULL;
+ u32 sequence_id = 0;
+
+ if (pkt_info->payload_ptr &&
+ pkt_info->payload_size >= sizeof(*frame)) {
+ frame = pkt_info->payload_ptr;
+ sequence_id = frame->bufferflip_index;
+ }
+ dcp_crtc_hfi_frame_done_cb(hfi->drv, pkt_info->id & 0xFFFF,
+ sequence_id);
+}
+
/* Route asynchronous events and capability packets before flag handling. */
static bool dcp_hfi_dispatch_event(struct dcp_hfi *hfi,
const struct hfi_packet_info *pkt_info)
{
switch (pkt_info->cmd) {
case HFI_COMMAND_DISPLAY_EVENT_VSYNC:
+ dcp_crtc_hfi_vblank_cb(hfi->drv, pkt_info->id & 0xFFFF);
return true;
case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_START:
return true;
case HFI_COMMAND_DISPLAY_EVENT_FRAME_SCAN_COMPLETE:
+ dcp_hfi_dispatch_frame_done(hfi, pkt_info);
return true;
case HFI_COMMAND_DISPLAY_EVENT_HPD_STATUS: {
@@ -1019,6 +1037,9 @@ int dcp_hfi_init(struct dcp_drv *drv,
struct dcp_hfi *hfi;
int i, ret;
+ if (!dma_dev)
+ return -ENODEV;
+
hfi = devm_kzalloc(&vdev->dev, sizeof(*hfi), GFP_KERNEL);
if (!hfi)
return -ENOMEM;
diff --git a/drivers/gpu/drm/msm/dcp/dcp_plane.c
b/drivers/gpu/drm/msm/dcp/dcp_plane.c
new file mode 100644
index 000000000000..e804fb8cfa1b
--- /dev/null
+++ b/drivers/gpu/drm/msm/dcp/dcp_plane.c
@@ -0,0 +1,1073 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ * DCP plane state validation and HFI property packet construction.
+ */
+
+#include <linux/math64.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_blend.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_plane.h>
+#include <drm/drm_rect.h>
+
+#include "msm_drv.h"
+
+#include "dcp_crtc.h"
+#include "dcp_hfi.h"
+#include "dcp_dpu_kms.h"
+#include "dcp_plane.h"
+
+#define DCP_ZPOS_MAX 255
+#define DCP_MAX_IMG_WIDTH 0x3FFF
+#define DCP_MAX_IMG_HEIGHT 0x3FFF
+#define DCP_SSPP_MAX_PITCH_SIZE 0xFF00
+#define DCP_MAX_UPSCALE_RATIO 20
+#define DCP_MAX_DOWNSCALE_RATIO 4
+
+static const u32 dcp_plane_formats[] = {
+ DRM_FORMAT_ARGB8888, DRM_FORMAT_ABGR8888,
+ DRM_FORMAT_RGBA8888, DRM_FORMAT_BGRA8888,
+ DRM_FORMAT_XRGB8888, DRM_FORMAT_XBGR8888,
+ DRM_FORMAT_RGBX8888, DRM_FORMAT_BGRX8888,
+ DRM_FORMAT_RGB888, DRM_FORMAT_BGR888,
+ DRM_FORMAT_RGB565, DRM_FORMAT_BGR565,
+ DRM_FORMAT_ARGB1555, DRM_FORMAT_ABGR1555,
+ DRM_FORMAT_RGBA5551, DRM_FORMAT_BGRA5551,
+ DRM_FORMAT_XRGB1555, DRM_FORMAT_XBGR1555,
+ DRM_FORMAT_RGBX5551, DRM_FORMAT_BGRX5551,
+ DRM_FORMAT_ARGB4444, DRM_FORMAT_ABGR4444,
+ DRM_FORMAT_RGBA4444, DRM_FORMAT_BGRA4444,
+ DRM_FORMAT_XRGB4444, DRM_FORMAT_XBGR4444,
+ DRM_FORMAT_RGBX4444, DRM_FORMAT_BGRX4444,
+ DRM_FORMAT_ARGB2101010, DRM_FORMAT_XRGB2101010,
+ DRM_FORMAT_ABGR2101010, DRM_FORMAT_XBGR2101010,
+ DRM_FORMAT_NV12, DRM_FORMAT_NV21,
+ DRM_FORMAT_P010, DRM_FORMAT_YUV420, DRM_FORMAT_YVU420,
+};
+
+static const u64 dcp_format_modifiers[] = {
+ DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ DRM_FORMAT_MOD_LINEAR,
+ DRM_FORMAT_MOD_INVALID,
+};
+
+static struct dcp_dpu_kms *_dcp_plane_get_kms(struct drm_plane *plane)
+{
+ struct msm_drm_private *priv = plane->dev->dev_private;
+
+ return to_dcp_dpu_kms(priv->kms);
+}
+
+static bool dcp_plane_hfi_pipe_valid(struct dcp_dpu_kms *dcp_kms, u32 pipe_id)
+{
+ const struct dcp_caps *caps;
+ u32 i;
+
+ if (!dcp_kms || !dcp_kms->caps)
+ return false;
+
+ caps = dcp_kms->caps;
+ for (i = 0; i < caps->num_vig_pipes; i++)
+ if (caps->vig_pipe_ids[i] == pipe_id)
+ return true;
+
+ for (i = 0; i < caps->num_dma_pipes; i++)
+ if (caps->dma_pipe_ids[i] == pipe_id)
+ return true;
+
+ return false;
+}
+
+static bool dcp_plane_pipe_is_vig(const struct dcp_caps *caps, u32 pipe_id)
+{
+ u32 i;
+
+ for (i = 0; i < caps->num_vig_pipes; i++)
+ if (caps->vig_pipe_ids[i] == pipe_id)
+ return true;
+
+ return false;
+}
+
+static bool dcp_plane_pipe_busy(struct drm_atomic_commit *state,
+ struct drm_plane *self, u32 pipe_id)
+{
+ struct drm_plane *plane;
+ struct drm_plane_state *plane_state;
+ struct drm_plane *iter_plane;
+ struct drm_plane_state *new_plane_state;
+ int i;
+
+ drm_for_each_plane(plane, state->dev) {
+ if (plane == self)
+ continue;
+
+ plane_state = plane->state;
+ for_each_new_plane_in_state(state, iter_plane, new_plane_state,
i) {
+ if (iter_plane == plane) {
+ plane_state = new_plane_state;
+ break;
+ }
+ }
+
+ if (plane_state && plane_state->crtc && plane_state->visible &&
+ to_dcp_plane_state(plane_state)->hfi_pipe_id == pipe_id)
+ return true;
+ }
+
+ return false;
+}
+
+static u32 dcp_plane_assign_pipe(struct dcp_dpu_kms *dcp_kms,
+ struct drm_atomic_commit *state,
+ struct drm_plane *plane,
+ struct dcp_plane_state *pstate,
+ bool yuv, bool scaling)
+{
+ const struct dcp_caps *caps = dcp_kms->caps;
+ u32 i, pipe_id;
+
+ /* YUV scanout requires a VIG pipe with the CSC path. */
+ if (dcp_plane_hfi_pipe_valid(dcp_kms, pstate->hfi_pipe_id) &&
+ (!yuv || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+ (!scaling || dcp_plane_pipe_is_vig(caps, pstate->hfi_pipe_id)) &&
+ !dcp_plane_pipe_busy(state, plane, pstate->hfi_pipe_id))
+ return pstate->hfi_pipe_id;
+
+ /*
+ * R1/virtual pipes are intentionally not allocated as independent
+ * planes yet. They require transactional physical-pipe/R1 ownership
+ * and multirect programming, which will be added with the resource
+ * manager.
+ */
+ for (i = 0; i < caps->num_vig_pipes; i++) {
+ pipe_id = caps->vig_pipe_ids[i];
+ if (!dcp_plane_pipe_busy(state, plane, pipe_id))
+ return pipe_id;
+ }
+
+ if (yuv || scaling)
+ return U32_MAX;
+
+ for (i = 0; i < caps->num_dma_pipes; i++) {
+ pipe_id = caps->dma_pipe_ids[i];
+ if (!dcp_plane_pipe_busy(state, plane, pipe_id))
+ return pipe_id;
+ }
+
+ return U32_MAX;
+}
+
+static const struct {
+ u32 fourcc;
+ u64 modifier;
+ u32 hfi_fmt;
+} dcp_fmt_table[] = {
+ { DRM_FORMAT_ABGR8888, DRM_FORMAT_MOD_QCOM_COMPRESSED,
HFI_COLOR_FORMAT_UBWC_RGBA8888 },
+ { DRM_FORMAT_XBGR8888, DRM_FORMAT_MOD_QCOM_COMPRESSED,
HFI_COLOR_FORMAT_UBWC_RGBX8888 },
+ { DRM_FORMAT_ABGR2101010, DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ HFI_COLOR_FORMAT_UBWC_RGBA10_10_10_2 },
+ { DRM_FORMAT_XBGR2101010, DRM_FORMAT_MOD_QCOM_COMPRESSED,
+ HFI_COLOR_FORMAT_UBWC_RGBX10_10_10_2 },
+ { DRM_FORMAT_BGR565, DRM_FORMAT_MOD_QCOM_COMPRESSED,
HFI_COLOR_FORMAT_UBWC_RGB565 },
+ { DRM_FORMAT_NV12, DRM_FORMAT_MOD_QCOM_COMPRESSED,
HFI_COLOR_FORMAT_UBWC_NV12 },
+ { DRM_FORMAT_P010, DRM_FORMAT_MOD_QCOM_COMPRESSED,
HFI_COLOR_FORMAT_UBWC_P010 },
+
+ { DRM_FORMAT_RGB565, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGR565 },
+ { DRM_FORMAT_BGR565, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGB565 },
+ { DRM_FORMAT_RGB888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGR888 },
+ { DRM_FORMAT_BGR888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGB888 },
+ { DRM_FORMAT_ARGB8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRA8888 },
+ { DRM_FORMAT_ABGR8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBA8888 },
+ { DRM_FORMAT_RGBA8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ABGR8888 },
+ { DRM_FORMAT_BGRA8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ARGB8888 },
+ { DRM_FORMAT_XRGB8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRX8888 },
+ { DRM_FORMAT_XBGR8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBX8888 },
+ { DRM_FORMAT_RGBX8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XBGR8888 },
+ { DRM_FORMAT_BGRX8888, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XRGB8888 },
+ { DRM_FORMAT_ARGB2101010, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRA10_10_10_2 },
+ { DRM_FORMAT_ABGR2101010, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBA10_10_10_2 },
+ { DRM_FORMAT_RGBA1010102, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ABGR2_10_10_10 },
+ { DRM_FORMAT_BGRA1010102, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ARGB2_10_10_10 },
+ { DRM_FORMAT_XRGB2101010, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRX10_10_10_2 },
+ { DRM_FORMAT_XBGR2101010, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBX10_10_10_2 },
+ { DRM_FORMAT_RGBX1010102, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XBGR2_10_10_10 },
+ { DRM_FORMAT_BGRX1010102, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XRGB2_10_10_10 },
+ { DRM_FORMAT_ARGB1555, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRA5551 },
+ { DRM_FORMAT_ABGR1555, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBA5551 },
+ { DRM_FORMAT_RGBA5551, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ABGR1555 },
+ { DRM_FORMAT_BGRA5551, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ARGB1555 },
+ { DRM_FORMAT_XRGB1555, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRX5551 },
+ { DRM_FORMAT_XBGR1555, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBX5551 },
+ { DRM_FORMAT_RGBX5551, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XBGR1555 },
+ { DRM_FORMAT_BGRX5551, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XRGB1555 },
+ { DRM_FORMAT_ARGB4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRA4444 },
+ { DRM_FORMAT_ABGR4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBA4444 },
+ { DRM_FORMAT_RGBA4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ABGR4444 },
+ { DRM_FORMAT_BGRA4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_ARGB4444 },
+ { DRM_FORMAT_XRGB4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_BGRX4444 },
+ { DRM_FORMAT_XBGR4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_RGBX4444 },
+ { DRM_FORMAT_RGBX4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XBGR4444 },
+ { DRM_FORMAT_BGRX4444, DRM_FORMAT_MOD_LINEAR,
HFI_COLOR_FORMAT_XRGB4444 },
+ { DRM_FORMAT_YUV420, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_YU12
},
+ { DRM_FORMAT_YVU420, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_YV12
},
+ { DRM_FORMAT_NV12, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_NV12
},
+ { DRM_FORMAT_NV21, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_NV21
},
+ { DRM_FORMAT_P010, DRM_FORMAT_MOD_LINEAR, HFI_COLOR_FORMAT_P010
},
+};
+
+static u32 dcp_drm_to_hfi_format(u32 fourcc, u64 modifier)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dcp_fmt_table); i++) {
+ if (dcp_fmt_table[i].fourcc == fourcc &&
+ dcp_fmt_table[i].modifier == modifier)
+ return dcp_fmt_table[i].hfi_fmt;
+ }
+
+ return U32_MAX;
+}
+
+static u32 dcp_drm_to_hfi_rotation(u32 drm_rotation)
+{
+ u32 hfi_rot = 0;
+
+ if (drm_rotation & DRM_MODE_ROTATE_0)
+ hfi_rot |= HFI_DISPLAY_ROTATION_0;
+ if (drm_rotation & DRM_MODE_ROTATE_90)
+ hfi_rot |= HFI_DISPLAY_ROTATION_90;
+ if (drm_rotation & DRM_MODE_ROTATE_180)
+ hfi_rot |= HFI_DISPLAY_ROTATION_180;
+ if (drm_rotation & DRM_MODE_ROTATE_270)
+ hfi_rot |= HFI_DISPLAY_ROTATION_270;
+ if (drm_rotation & DRM_MODE_REFLECT_X)
+ hfi_rot |= HFI_DISPLAY_REFLECT_X;
+ if (drm_rotation & DRM_MODE_REFLECT_Y)
+ hfi_rot |= HFI_DISPLAY_REFLECT_Y;
+
+ if (!hfi_rot)
+ hfi_rot = HFI_DISPLAY_ROTATION_0;
+
+ return hfi_rot;
+}
+
+static u32 dcp_drm_to_hfi_blend_op(u32 drm_blend_mode)
+{
+ switch (drm_blend_mode) {
+ case DRM_MODE_BLEND_PIXEL_NONE: return HFI_BLEND_OP_OPAQUE;
+ case DRM_MODE_BLEND_PREMULTI: return HFI_BLEND_OP_PREMULTIPLIED;
+ case DRM_MODE_BLEND_COVERAGE: return HFI_BLEND_OP_COVERAGE;
+ default: return HFI_BLEND_OP_NOT_DEFINED;
+ }
+}
+
+static u16 dcp_compute_bg_alpha(u32 pixel_blend_mode, u16 fg_alpha)
+{
+ switch (pixel_blend_mode) {
+ case DRM_MODE_BLEND_PIXEL_NONE:
+ return DRM_BLEND_ALPHA_OPAQUE - fg_alpha;
+ case DRM_MODE_BLEND_PREMULTI:
+ case DRM_MODE_BLEND_COVERAGE:
+ return (fg_alpha != DRM_BLEND_ALPHA_OPAQUE) ? fg_alpha : 0;
+ default:
+ return DRM_BLEND_ALPHA_OPAQUE;
+ }
+}
+
+static u32 dcp_hfi_scale_alpha(const struct drm_plane_state *state, u32 alpha)
+{
+ if (!state || !state->crtc)
+ return alpha << 8;
+
+ if (to_dcp_crtc_state(state->crtc->state)->topology_bits_per_component
>= 10)
+ return alpha;
+
+ return alpha << 8;
+}
+
+static u64 dcp_plane_calc_bw(u32 bpp,
+ const struct drm_display_mode *mode,
+ const struct drm_rect *src,
+ const struct drm_rect *dst)
+{
+ int src_w = drm_rect_width(src) >> 16;
+ int src_h = drm_rect_height(src) >> 16;
+ int dst_h = drm_rect_height(dst);
+ int fps = drm_mode_vrefresh(mode);
+ int vbp = mode->vtotal - mode->vsync_end;
+ int vpw = mode->vsync_end - mode->vsync_start;
+ int vfp = mode->vsync_start - mode->vdisplay;
+ u32 hw_latency = 25;
+ u64 scale_factor = (src_h > dst_h) ? mult_frac(src_h, 1, dst_h) : 1;
+ u64 plane_bw, prefill_bw;
+
+ plane_bw = (u64)src_w * mode->vtotal * fps * bpp * scale_factor;
+ prefill_bw = plane_bw * hw_latency;
+
+ if ((vbp + vpw) > hw_latency)
+ do_div(prefill_bw, (vbp + vpw));
+ else if ((vbp + vpw + vfp) < hw_latency)
+ do_div(prefill_bw, (vbp + vpw + vfp));
+ else
+ do_div(prefill_bw, hw_latency);
+
+ /* HFI DRAM/LLCC votes are expressed in Kbps. */
+ return div_u64(max(plane_bw, prefill_bw), 1000);
+}
+
+static u64 dcp_plane_calc_clk(const struct drm_display_mode *mode,
+ const struct drm_rect *src,
+ const struct drm_rect *dst)
+{
+ int src_h = drm_rect_height(src) >> 16;
+ int dst_w = drm_rect_width(dst);
+ int dst_h = drm_rect_height(dst);
+ int fps = drm_mode_vrefresh(mode);
+ u64 clk = (u64)dst_w * mode->vtotal * fps;
+
+ if (src_h > dst_h) {
+ clk *= src_h;
+ do_div(clk, dst_h);
+ }
+ return clk;
+}
+
+void dcp_plane_calc_perf(const struct drm_plane_state *state,
+ const struct drm_display_mode *mode,
+ struct dcp_plane_perf *perf)
+{
+ struct drm_rect src, dst;
+ u32 bpp;
+
+ memset(perf, 0, sizeof(*perf));
+
+ if (!state || !state->visible || !state->fb || !mode || !mode->clock ||
+ !drm_mode_vrefresh(mode))
+ return;
+
+ src = drm_plane_state_src(state);
+ dst = drm_plane_state_dest(state);
+ bpp = drm_format_info_bpp(state->fb->format, 0);
+
+ perf->fetch_bw = dcp_plane_calc_bw(bpp, mode, &src, &dst);
+ perf->clk = dcp_plane_calc_clk(mode, &src, &dst);
+}
+
+static inline u32 *prop_u32(u32 *p, u32 prop_id, u32 lid, u32 val)
+{
+ *p++ = HFI_PACK_KEY(prop_id, 0, 2);
+ *p++ = lid;
+ *p++ = val;
+ return p;
+}
+
+static inline u32 *prop_roi(u32 *p, u32 prop_id, u32 lid,
+ u32 x, u32 y, u32 w, u32 h)
+{
+ *p++ = HFI_PACK_KEY(prop_id, 0, 5);
+ *p++ = lid;
+ *p++ = x; *p++ = y; *p++ = w; *p++ = h;
+ return p;
+}
+
+static inline u32 *prop_attach(u32 *p, u32 lid)
+{
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_DISPLAY_ATTACH_LAYER, 0, 1);
+ *p++ = lid;
+ return p;
+}
+
+static inline u32 *prop_src_addr(u32 *p, u32 lid,
+ const dma_addr_t *iova, u32 hfi_fmt)
+{
+ int i;
+
+ *p++ = HFI_PACK_KEY(HFI_PROPERTY_LAYER_SRC_ADDR, 0, 13);