/*
 * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (C) 2014-2021 The Linux Foundation. All rights reserved.
 * Copyright (C) 2013 Red Hat
 * Author: Rob Clark <robdclark@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define pr_fmt(fmt)	"[drm:%s:%d] " fmt, __func__, __LINE__

#include <linux/debugfs.h>
#include <linux/dma-buf.h>
#include <drm/sde_drm.h>
#include <drm/msm_drm_pp.h>
#include <linux/version.h>

#include "msm_prop.h"
#include "msm_drv.h"

#include "sde_kms.h"
#include "sde_fence.h"
#include "sde_formats.h"
#include "sde_hw_sspp.h"
#include "sde_hw_catalog_format.h"
#include "sde_trace.h"
#include "sde_crtc.h"
#include "sde_vbif.h"
#include "sde_plane.h"
#include "sde_color_processing.h"

#define SDE_DEBUG_PLANE(pl, fmt, ...) SDE_DEBUG("plane%d " fmt,\
		(pl) ? (pl)->base.base.id : -1, ##__VA_ARGS__)

#define SDE_ERROR_PLANE(pl, fmt, ...) SDE_ERROR("plane%d " fmt,\
		(pl) ? (pl)->base.base.id : -1, ##__VA_ARGS__)

#define DECIMATED_DIMENSION(dim, deci) (((dim) + ((1 << (deci)) - 1)) >> (deci))
#define PHASE_STEP_SHIFT	21
#define PHASE_STEP_UNIT_SCALE   ((int) (1 << PHASE_STEP_SHIFT))
#define PHASE_RESIDUAL		15

#define SHARP_STRENGTH_DEFAULT	32
#define SHARP_EDGE_THR_DEFAULT	112
#define SHARP_SMOOTH_THR_DEFAULT	8
#define SHARP_NOISE_THR_DEFAULT	2

#define SDE_NAME_SIZE  12

#define SDE_PLANE_COLOR_FILL_FLAG	BIT(31)

#define TIME_MULTIPLEX_RECT(r0, r1, buffer_lines) \
	 ((r0).y >= ((r1).y + (r1).h + buffer_lines))

#define SDE_QSEED_DEFAULT_DYN_EXP 0x0

/**
 * enum sde_plane_qos - Different qos configurations for each pipe
 *
 * @SDE_PLANE_QOS_VBLANK_CTRL: Setup VBLANK qos for the pipe.
 * @SDE_PLANE_QOS_VBLANK_AMORTIZE: Enables Amortization within pipe.
 *	this configuration is mutually exclusive from VBLANK_CTRL.
 * @SDE_PLANE_QOS_PANIC_CTRL: Setup panic for the pipe.
 */
enum sde_plane_qos {
	SDE_PLANE_QOS_VBLANK_CTRL = BIT(0),
	SDE_PLANE_QOS_VBLANK_AMORTIZE = BIT(1),
	SDE_PLANE_QOS_PANIC_CTRL = BIT(2),
};

static int plane_prop_array[PLANE_PROP_COUNT] = {SDE_PLANE_DIRTY_ALL};

static struct sde_kms *_sde_plane_get_kms(struct drm_plane *plane)
{
	struct msm_drm_private *priv;

	if (!plane || !plane->dev)
		return NULL;
	priv = plane->dev->dev_private;
	if (!priv)
		return NULL;
	return to_sde_kms(priv->kms);
}

static struct sde_hw_ctl *_sde_plane_get_hw_ctl(const struct drm_plane *plane)
{
	struct drm_plane_state *pstate = NULL;
	struct drm_crtc *drm_crtc = NULL;
	struct sde_crtc *sde_crtc = NULL;
	struct sde_crtc_mixer *mixer = NULL;
	struct sde_hw_ctl *ctl = NULL;

	if (!plane) {
		DRM_ERROR("Invalid plane %pK\n", plane);
		return NULL;
	}

	pstate = plane->state;
	if (!pstate) {
		DRM_ERROR("Invalid plane state %pK\n", pstate);
		return NULL;
	}

	drm_crtc = pstate->crtc;
	if (!drm_crtc) {
		DRM_ERROR("Invalid drm_crtc %pK\n", drm_crtc);
		return NULL;
	}

	sde_crtc = to_sde_crtc(drm_crtc);
	if (!sde_crtc) {
		DRM_ERROR("invalid sde_crtc %pK\n", sde_crtc);
		return NULL;
	}

	/* it will always return the first mixer and single CTL */
	mixer = sde_crtc->mixers;
	if (!mixer) {
		DRM_ERROR("invalid mixer %pK\n", mixer);
		return NULL;
	}

	ctl = mixer->hw_ctl;
	if (!mixer) {
		DRM_ERROR("invalid ctl %pK\n", ctl);
		return NULL;
	}

	return ctl;
}

static void _sde_plane_setup_panel_stacking(struct sde_plane *psde,
					    struct sde_plane_state *pstate)
{
	struct sde_hw_pipe_line_insertion_cfg *cfg;
	struct sde_crtc_state *cstate;
	u32 h_start = 0, h_total = 0, y_start = 0;
	struct drm_plane_state *dpstate = NULL;
	struct drm_crtc *drm_crtc = NULL;

	if (!psde || !psde->base.state || !psde->base.state->crtc) {
		SDE_ERROR("Invalid plane psde %p or drm plane state or drm crtc\n", psde);
		return;
	}

	dpstate = psde->base.state;
	drm_crtc = dpstate->crtc;
	cstate = to_sde_crtc_state(drm_crtc->state);
	pstate->lineinsertion_feature = cstate->line_insertion.panel_line_insertion_enable;

	if ((!test_bit(SDE_SSPP_LINE_INSERTION, (unsigned long *)&psde->features)) ||
	    !cstate->line_insertion.panel_line_insertion_enable)
		return;

	cfg = &pstate->line_insertion_cfg;
	memset(cfg, 0, sizeof(*cfg));
	if (!cstate->line_insertion.padding_height)
		return;

	sde_crtc_calc_vpadding_param(psde->base.state->crtc->state,
				     pstate->base.crtc_y, pstate->base.crtc_h,
				     &y_start, &h_start, &h_total);
	cfg->enable = true;
	cfg->dummy_lines = cstate->line_insertion.padding_dummy;
	cfg->active_lines = cstate->line_insertion.padding_active;
	cfg->first_active_lines = h_start;
	cfg->dst_h = h_total;
	psde->pipe_cfg.dst_rect.y += y_start - pstate->base.crtc_y;
}

static bool sde_plane_enabled(const struct drm_plane_state *state)
{
	return state && state->fb && state->crtc;
}

bool sde_plane_is_sec_ui_allowed(struct drm_plane *plane)
{
	struct sde_plane *psde;

	if (!plane)
		return false;

	psde = to_sde_plane(plane);

	return !(psde->features & BIT(SDE_SSPP_BLOCK_SEC_UI));
}

void sde_plane_setup_src_split_order(struct drm_plane *plane,
		enum sde_sspp_multirect_index rect_mode, bool enable)
{
	struct sde_plane *psde;

	if (!plane)
		return;

	psde = to_sde_plane(plane);
	if (psde->pipe_hw->ops.set_src_split_order)
		psde->pipe_hw->ops.set_src_split_order(psde->pipe_hw,
					rect_mode, enable);
}

void sde_plane_set_sid(struct drm_plane *plane, u32 vm)
{
	struct sde_plane *psde;
	struct sde_kms *sde_kms;
	struct msm_drm_private *priv;

	if (!plane || !plane->dev) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return;
	}

	sde_kms = to_sde_kms(priv->kms);

	psde = to_sde_plane(plane);
	sde_hw_set_sspp_sid(sde_kms->hw_sid, psde->pipe, vm, sde_kms->catalog);
}

static void _sde_plane_set_qos_lut(struct drm_plane *plane,
		struct drm_crtc *crtc,
		struct drm_framebuffer *fb)
{
	struct sde_plane *psde;
	const struct sde_format *fmt = NULL;
	u32 frame_rate, qos_count, fps_index = 0, lut_index, creq_lut_index, ds_lut_index;
	struct sde_perf_cfg *perf;
	struct sde_plane_state *pstate;
	struct sde_crtc *sde_crtc = to_sde_crtc(crtc);
	bool inline_rot = false, landscape = false;
	struct drm_display_mode *mode;
	u32 fl_require0 = 0;

	if (!plane || !fb) {
		SDE_ERROR("invalid arguments\n");
		return;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);

	if (!psde->pipe_hw || !psde->pipe_sblk || !psde->catalog) {
		SDE_ERROR("invalid arguments\n");
		return;
	} else if (!psde->pipe_hw->ops.setup_qos_lut) {
		return;
	}

	mode = &crtc->state->adjusted_mode;
	landscape = mode->hdisplay > mode->vdisplay ? true : false;

	frame_rate = drm_mode_vrefresh(&crtc->mode);
	perf = &psde->catalog->perf;
	qos_count = perf->qos_refresh_count;
	while ((fps_index < qos_count) && perf->qos_refresh_rate) {
		if ((frame_rate <= perf->qos_refresh_rate[fps_index]) ||
				(fps_index == qos_count - 1))
			break;
		fps_index++;
	}

	if (psde->is_rt_pipe) {
		fmt = sde_get_sde_format_ext(fb->format->format, fb->modifier);
	        inline_rot = (pstate->rotation & DRM_MODE_ROTATE_90);

		if (inline_rot && SDE_IS_IN_ROT_RESTRICTED_FMT(psde->catalog, fmt))
			lut_index = SDE_QOS_LUT_USAGE_INLINE_RESTRICTED_FMTS;
		else if (inline_rot)
			lut_index = SDE_QOS_LUT_USAGE_INLINE;
		else if (fmt && SDE_FORMAT_IS_LINEAR(fmt))
			lut_index = SDE_QOS_LUT_USAGE_LINEAR;
		else
			lut_index = SDE_QOS_LUT_USAGE_MACROTILE;
	} else {
		lut_index = (psde->wb_usage_type == WB_USAGE_OFFLINE_WB) ?
					SDE_QOS_LUT_USAGE_OFFLINE_WB : SDE_QOS_LUT_USAGE_NRT;
	}

	creq_lut_index = lut_index * SDE_CREQ_LUT_TYPE_MAX;
	if (psde->scaler3_cfg.enable)
		creq_lut_index += SDE_CREQ_LUT_TYPE_QSEED;
	creq_lut_index += (fps_index * SDE_QOS_LUT_USAGE_MAX * SDE_CREQ_LUT_TYPE_MAX);
	psde->pipe_qos_cfg.creq_lut = perf->creq_lut[creq_lut_index];

	ds_lut_index = lut_index * SDE_DANGER_SAFE_LUT_TYPE_MAX;
	if (landscape) {
		if (psde->catalog->qos_target_time_ns && sde_crtc->line_time_in_ns)
			fl_require0 = psde->catalog->qos_target_time_ns /
							(sde_crtc->line_time_in_ns * 2);
		if (!fl_require0 || fl_require0 < 4.5)
			ds_lut_index += SDE_DANGER_SAFE_LUT_TYPE_LANDSCAPE;
	}
	ds_lut_index += (fps_index * SDE_QOS_LUT_USAGE_MAX * SDE_DANGER_SAFE_LUT_TYPE_MAX);
	psde->pipe_qos_cfg.danger_lut = perf->danger_lut[ds_lut_index];
	psde->pipe_qos_cfg.safe_lut = perf->safe_lut[ds_lut_index];

	trace_sde_perf_set_qos_luts(psde->pipe - SSPP_VIG0, (fmt) ? fmt->base.pixel_format : 0,
			(fmt) ? fmt->fetch_mode : 0, psde->pipe_qos_cfg.danger_lut,
			psde->pipe_qos_cfg.safe_lut, psde->pipe_qos_cfg.creq_lut);

	SDE_DEBUG("plane%u: pnum:%d fmt:%4.4s fps:%d mode:%d lut[0x%x,0x%x 0x%llx] rt:%d type:%d\n",
		plane->base.id, psde->pipe - SSPP_VIG0,
		fmt ? (char *)&fmt->base.pixel_format : NULL, frame_rate,
		fmt ? fmt->fetch_mode : -1, psde->pipe_qos_cfg.danger_lut,
		psde->pipe_qos_cfg.safe_lut, psde->pipe_qos_cfg.creq_lut,
		psde->is_rt_pipe, psde->wb_usage_type);

	psde->pipe_hw->ops.setup_qos_lut(psde->pipe_hw, &psde->pipe_qos_cfg);
}

/**
 * _sde_plane_set_qos_ctrl - set QoS control of the given plane
 * @plane:		Pointer to drm plane
 * @enable:		true to enable QoS control
 * @flags:		QoS control mode (enum sde_plane_qos)
 */
static void _sde_plane_set_qos_ctrl(struct drm_plane *plane,
	bool enable, u32 flags)
{
	struct sde_plane *psde;

	if (!plane) {
		SDE_ERROR("invalid arguments\n");
		return;
	}

	psde = to_sde_plane(plane);

	if (!psde->pipe_hw || !psde->pipe_sblk) {
		SDE_ERROR("invalid arguments\n");
		return;
	} else if (!psde->pipe_hw->ops.setup_qos_ctrl) {
		return;
	}

	if (flags & SDE_PLANE_QOS_VBLANK_CTRL) {
		psde->pipe_qos_cfg.creq_vblank = psde->pipe_sblk->creq_vblank;
		psde->pipe_qos_cfg.danger_vblank =
				psde->pipe_sblk->danger_vblank;
		psde->pipe_qos_cfg.vblank_en = enable;
	}

	if (flags & SDE_PLANE_QOS_VBLANK_AMORTIZE) {
		/* this feature overrules previous VBLANK_CTRL */
		psde->pipe_qos_cfg.vblank_en = false;
		psde->pipe_qos_cfg.creq_vblank = 0; /* clear vblank bits */
		psde->pipe_qos_cfg.danger_vblank = 0;
	}

	if (flags & SDE_PLANE_QOS_PANIC_CTRL)
		psde->pipe_qos_cfg.danger_safe_en = enable;

	if (!psde->is_rt_pipe) {
		psde->pipe_qos_cfg.vblank_en = false;
		psde->pipe_qos_cfg.danger_safe_en = false;
	}

	SDE_DEBUG("plane%u: pnum:%d ds:%d vb:%d pri[0x%x, 0x%x] is_rt:%d\n",
		plane->base.id,
		psde->pipe - SSPP_VIG0,
		psde->pipe_qos_cfg.danger_safe_en,
		psde->pipe_qos_cfg.vblank_en,
		psde->pipe_qos_cfg.creq_vblank,
		psde->pipe_qos_cfg.danger_vblank,
		psde->is_rt_pipe);

	psde->pipe_hw->ops.setup_qos_ctrl(psde->pipe_hw,
			&psde->pipe_qos_cfg);
}

void sde_plane_set_revalidate(struct drm_plane *plane, bool enable)
{
	struct sde_plane *psde;

	if (!plane)
		return;

	psde = to_sde_plane(plane);
	psde->revalidate = enable;
}

int sde_plane_danger_signal_ctrl(struct drm_plane *plane, bool enable)
{
	struct sde_plane *psde;
	int rc;

	if (!plane) {
		SDE_ERROR("invalid arguments\n");
		return -EINVAL;
	}

	psde = to_sde_plane(plane);

	if (!psde->is_rt_pipe)
		goto end;

	rc = pm_runtime_resume_and_get(plane->dev->dev);
	if (rc < 0) {
		SDE_ERROR("failed to enable power resource %d\n", rc);
		SDE_EVT32(rc, SDE_EVTLOG_ERROR);
		return rc;
	}

	_sde_plane_set_qos_ctrl(plane, enable, SDE_PLANE_QOS_PANIC_CTRL);

	pm_runtime_put_sync(plane->dev->dev);

end:
	return 0;
}

/**
 * _sde_plane_set_ot_limit - set OT limit for the given plane
 * @plane:		Pointer to drm plane
 * @crtc:		Pointer to drm crtc
 */
static void _sde_plane_set_ot_limit(struct drm_plane *plane,
		struct drm_crtc *crtc)
{
	struct sde_plane *psde;
	struct sde_vbif_set_ot_params ot_params;
	struct msm_drm_private *priv;
	struct sde_kms *sde_kms;

	if (!plane || !plane->dev || !crtc) {
		SDE_ERROR("invalid arguments plane %d crtc %d\n",
				!plane, !crtc);
		return;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return;
	}

	sde_kms = to_sde_kms(priv->kms);
	psde = to_sde_plane(plane);
	if (!psde->pipe_hw) {
		SDE_ERROR("invalid pipe reference\n");
		return;
	}

	memset(&ot_params, 0, sizeof(ot_params));
	ot_params.xin_id = psde->pipe_hw->cap->xin_id;
	ot_params.num = psde->pipe_hw->idx - SSPP_NONE;
	ot_params.width = psde->pipe_cfg.src_rect.w;
	ot_params.height = psde->pipe_cfg.src_rect.h;
	ot_params.is_wfd = ((psde->is_rt_pipe)
				|| (psde->wb_usage_type == WB_USAGE_OFFLINE_WB)) ? false : true;
	ot_params.frame_rate = drm_mode_vrefresh(&crtc->mode);
	ot_params.vbif_idx = VBIF_RT;
	ot_params.clk_ctrl = psde->pipe_hw->cap->clk_ctrl;
	ot_params.rd = true;

	sde_vbif_set_ot_limit(sde_kms, &ot_params);
}

/**
 * _sde_plane_set_vbif_qos - set vbif QoS for the given plane
 * @plane:		Pointer to drm plane
 */
static void _sde_plane_set_qos_remap(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_vbif_set_qos_params qos_params;
	struct msm_drm_private *priv;
	struct sde_kms *sde_kms;

	if (!plane || !plane->dev) {
		SDE_ERROR("invalid arguments\n");
		return;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return;
	}

	sde_kms = to_sde_kms(priv->kms);
	psde = to_sde_plane(plane);
	if (!psde->pipe_hw) {
		SDE_ERROR("invalid pipe reference\n");
		return;
	}

	memset(&qos_params, 0, sizeof(qos_params));
	qos_params.vbif_idx = VBIF_RT;
	qos_params.clk_ctrl = psde->pipe_hw->cap->clk_ctrl;
	qos_params.xin_id = psde->pipe_hw->cap->xin_id;
	qos_params.num = psde->pipe_hw->idx - SSPP_VIG0;
	if (psde->is_rt_pipe)
		qos_params.client_type =  VBIF_RT_CLIENT;
	else
		qos_params.client_type = (psde->wb_usage_type == WB_USAGE_OFFLINE_WB) ?
						VBIF_OFFLINE_WB_CLIENT : VBIF_NRT_CLIENT;

	SDE_DEBUG("plane%d pipe:%d vbif:%d xin:%d rt:%d, clk_ctrl:%d\n",
			plane->base.id, qos_params.num,
			qos_params.vbif_idx,
			qos_params.xin_id, qos_params.client_type,
			qos_params.clk_ctrl);

	sde_vbif_set_qos_remap(sde_kms, &qos_params);
}

/**
 * _sde_plane_set_ts_prefill - set prefill with traffic shaper
 * @plane:	Pointer to drm plane
 * @pstate:	Pointer to sde plane state
 */
static void _sde_plane_set_ts_prefill(struct drm_plane *plane,
		struct sde_plane_state *pstate)
{
	struct sde_plane *psde;
	struct sde_hw_pipe_ts_cfg cfg;
	struct msm_drm_private *priv;
	struct sde_kms *sde_kms;

	if (!plane || !plane->dev) {
		SDE_ERROR("invalid arguments");
		return;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return;
	}

	sde_kms = to_sde_kms(priv->kms);
	psde = to_sde_plane(plane);
	if (!psde->pipe_hw) {
		SDE_ERROR("invalid pipe reference\n");
		return;
	}

	if (!psde->pipe_hw || !psde->pipe_hw->ops.setup_ts_prefill)
		return;

	_sde_plane_set_qos_ctrl(plane, false, SDE_PLANE_QOS_VBLANK_AMORTIZE);

	memset(&cfg, 0, sizeof(cfg));
	cfg.size = sde_plane_get_property(pstate,
			PLANE_PROP_PREFILL_SIZE);
	cfg.time = sde_plane_get_property(pstate,
			PLANE_PROP_PREFILL_TIME);

	SDE_DEBUG("plane%d size:%llu time:%llu\n",
			plane->base.id, cfg.size, cfg.time);
	SDE_EVT32_VERBOSE(DRMID(plane), cfg.size, cfg.time);
	psde->pipe_hw->ops.setup_ts_prefill(psde->pipe_hw, &cfg,
			pstate->multirect_index);
}

/* helper to update a state's input fence pointer from the property */
static void _sde_plane_set_input_fence(struct sde_plane *psde,
		struct sde_plane_state *pstate, uint64_t fd)
{
	if (!psde || !pstate) {
		SDE_ERROR("invalid arg(s), plane %d state %d\n",
				!psde, !pstate);
		return;
	}

	/* clear previous reference */
	if (pstate->input_fence)
		sde_sync_put(pstate->input_fence);

	/* get fence pointer for later */
	if (fd == 0)
		pstate->input_fence = NULL;
	else
		pstate->input_fence = sde_sync_get(fd);

	SDE_DEBUG_PLANE(psde, "0x%llX\n", fd);
}

int sde_plane_wait_input_fence(struct drm_plane *plane, uint32_t wait_ms)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	uint32_t prefix;
	void *input_fence;
	int ret = -EINVAL;
	signed long rc;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
	} else if (!plane->state) {
		SDE_ERROR_PLANE(to_sde_plane(plane), "invalid state\n");
	} else {
		psde = to_sde_plane(plane);
		pstate = to_sde_plane_state(plane->state);
		input_fence = pstate->input_fence;

		if (input_fence) {
			prefix = sde_sync_get_name_prefix(input_fence);
			rc = sde_sync_wait(input_fence, wait_ms);

			switch (rc) {
			case 0:
				SDE_ERROR_PLANE(psde, "%ums timeout on %08X fd %lld\n",
						wait_ms, prefix, sde_plane_get_property(pstate,
						PLANE_PROP_INPUT_FENCE));
				psde->is_error = true;
				sde_kms_timeline_status(plane->dev);
				ret = -ETIMEDOUT;
				break;
			case -ERESTARTSYS:
				SDE_ERROR_PLANE(psde,
					"%ums wait interrupted on %08X\n",
					wait_ms, prefix);
				psde->is_error = true;
				ret = -ERESTARTSYS;
				break;
			case -EINVAL:
				SDE_ERROR_PLANE(psde,
					"invalid fence param for %08X\n",
						prefix);
				psde->is_error = true;
				ret = -EINVAL;
				break;
			case -EBADF:
				SDE_INFO("plane%d spec fd signaled on bind failure fd %lld\n",
					plane->base.id,
					sde_plane_get_property(pstate, PLANE_PROP_INPUT_FENCE));
				psde->is_error = true;
				ret = 0;
				break;
			default:
				SDE_DEBUG_PLANE(psde, "signaled\n");
				ret = 0;
				break;
			}

			if (ret)
				SDE_EVT32(DRMID(plane), -ret, prefix, SDE_EVTLOG_ERROR);
			else
				SDE_EVT32_VERBOSE(DRMID(plane), -ret, prefix);
		} else {
			ret = 0;
		}
	}
	return ret;
}

/**
 * _sde_plane_get_aspace: gets the address space based on the
 *            fb_translation mode property
 */
static int _sde_plane_get_aspace(
		struct sde_plane *psde,
		struct sde_plane_state *pstate,
		struct msm_gem_address_space **aspace)
{
	struct sde_kms *kms;
	int mode;

	if (!psde || !pstate || !aspace) {
		SDE_ERROR("invalid parameters\n");
		return -EINVAL;
	}

	kms = _sde_plane_get_kms(&psde->base);
	if (!kms) {
		SDE_ERROR("invalid kms\n");
		return -EINVAL;
	}

	mode = sde_plane_get_property(pstate,
			PLANE_PROP_FB_TRANSLATION_MODE);

	switch (mode) {
	case SDE_DRM_FB_NON_SEC:
		*aspace = kms->aspace[MSM_SMMU_DOMAIN_UNSECURE];
		if (!aspace)
			return -EINVAL;
		break;
	case SDE_DRM_FB_SEC:
		*aspace = kms->aspace[MSM_SMMU_DOMAIN_SECURE];
		if (!aspace)
			return -EINVAL;
		break;
	case SDE_DRM_FB_NON_SEC_DIR_TRANS:
	case SDE_DRM_FB_SEC_DIR_TRANS:
		*aspace = NULL;
		break;
	default:
		SDE_ERROR("invalid fb_translation mode:%d\n", mode);
		return -EFAULT;
	}

	return 0;
}

static inline void _sde_plane_set_scanout(struct drm_plane *plane,
		struct sde_plane_state *pstate,
		struct sde_hw_pipe_cfg *pipe_cfg,
		struct drm_framebuffer *fb)
{
	struct sde_plane *psde;
	struct msm_gem_address_space *aspace = NULL;
	int ret, mode;
	bool secure = false;

	if (!plane || !pstate || !pipe_cfg || !fb) {
		SDE_ERROR(
			"invalid arg(s), plane %d state %d cfg %d fb %d\n",
			!plane, !pstate, !pipe_cfg, !fb);
		return;
	}

	psde = to_sde_plane(plane);
	if (!psde->pipe_hw) {
		SDE_ERROR_PLANE(psde, "invalid pipe_hw\n");
		return;
	}

	ret = _sde_plane_get_aspace(psde, pstate, &aspace);
	if (ret) {
		SDE_ERROR_PLANE(psde, "Failed to get aspace %d\n", ret);
		return;
	}

	/*
	 * framebuffer prepare is deferred for prepare_fb calls that
	 * happen during the transition from secure to non-secure.
	 * Handle the prepare at this point for such cases. This can be
	 * expected for one or two frames during the transition.
	 */
	if (aspace && pstate->defer_prepare_fb) {
		SDE_EVT32(DRMID(plane), psde->pipe, aspace->domain_attached);
		ret = msm_framebuffer_prepare(fb, pstate->aspace);
		if (ret) {
			SDE_ERROR_PLANE(psde,
				"failed to prepare framebuffer %d\n", ret);
			return;
		}
		pstate->defer_prepare_fb = false;
	}
	mode = sde_plane_get_property(pstate, PLANE_PROP_FB_TRANSLATION_MODE);
	if ((mode == SDE_DRM_FB_SEC) || (mode == SDE_DRM_FB_SEC_DIR_TRANS))
		secure = true;

	ret = sde_format_populate_layout(aspace, fb, &pipe_cfg->layout);
	if (ret == -EAGAIN)
		SDE_DEBUG_PLANE(psde, "not updating same src addrs\n");
	else if (ret) {
		SDE_ERROR_PLANE(psde, "failed to get format layout, %d\n", ret);

		/*
		 * Force solid fill color on error. This is to prevent
		 * smmu faults during secure session transition.
		 */
		psde->is_error = true;
	} else if (psde->pipe_hw->ops.setup_sourceaddress) {
		SDE_EVT32_VERBOSE(psde->pipe_hw->idx,
				pipe_cfg->layout.width,
				pipe_cfg->layout.height,
				pipe_cfg->layout.plane_addr[0],
				pipe_cfg->layout.plane_size[0],
				pipe_cfg->layout.plane_addr[1],
				pipe_cfg->layout.plane_size[1],
				pipe_cfg->layout.plane_addr[2],
				pipe_cfg->layout.plane_size[2],
				pipe_cfg->layout.plane_addr[3],
				pipe_cfg->layout.plane_size[3],
				pstate->multirect_index,
				secure);
		psde->pipe_hw->ops.setup_sourceaddress(psde->pipe_hw, pipe_cfg,
						pstate->multirect_index);
	}
}

static int _sde_plane_setup_scaler3_lut(struct sde_plane *psde,
		struct sde_plane_state *pstate)
{
	struct sde_hw_scaler3_cfg *cfg;
	int ret = 0;

	if (!psde || !pstate) {
		SDE_ERROR("invalid args\n");
		return -EINVAL;
	}

	cfg = &psde->scaler3_cfg;

	cfg->dir_lut = msm_property_get_blob(
			&psde->property_info,
			&pstate->property_state, &cfg->dir_len,
			PLANE_PROP_SCALER_LUT_ED);
	cfg->cir_lut = msm_property_get_blob(
			&psde->property_info,
			&pstate->property_state, &cfg->cir_len,
			PLANE_PROP_SCALER_LUT_CIR);
	cfg->sep_lut = msm_property_get_blob(
			&psde->property_info,
			&pstate->property_state, &cfg->sep_len,
			PLANE_PROP_SCALER_LUT_SEP);
	if (!cfg->dir_lut || !cfg->cir_lut || !cfg->sep_lut)
		ret = -ENODATA;
	return ret;
}

static int _sde_plane_setup_scaler3lite_lut(struct sde_plane *psde,
		struct sde_plane_state *pstate)
{
	struct sde_hw_scaler3_cfg *cfg;

	cfg = &psde->scaler3_cfg;

	cfg->sep_lut = msm_property_get_blob(
			&psde->property_info,
			&pstate->property_state, &cfg->sep_len,
			PLANE_PROP_SCALER_LUT_SEP);

	return cfg->sep_lut ? 0 : -ENODATA;
}

static void _sde_plane_setup_scaler3(struct sde_plane *psde,
		struct sde_plane_state *pstate, const struct sde_format *fmt,
		uint32_t chroma_subsmpl_h, uint32_t chroma_subsmpl_v)
{
	uint32_t decimated, i, src_w, src_h, dst_w, dst_h, src_h_pre_down;
	struct sde_hw_scaler3_cfg *scale_cfg;
	bool pre_down_supported = (psde->features & BIT(SDE_SSPP_PREDOWNSCALE));
	bool inline_rotation = (pstate->rotation & DRM_MODE_ROTATE_90);

	if (!fmt || !chroma_subsmpl_h || !chroma_subsmpl_v) {
		SDE_ERROR("fmt %d smp_h %d smp_v %d\n", !fmt,
				chroma_subsmpl_h, chroma_subsmpl_v);
		return;
	}

	scale_cfg = &psde->scaler3_cfg;
	src_w = psde->pipe_cfg.src_rect.w;
	src_h = psde->pipe_cfg.src_rect.h;
	dst_w = psde->pipe_cfg.dst_rect.w;
	dst_h = psde->pipe_cfg.dst_rect.h;

	memset(scale_cfg, 0, sizeof(*scale_cfg));
	memset(&psde->pixel_ext, 0, sizeof(struct sde_hw_pixel_ext));

	/*
	 * For inline rotation cases, scaler config is post-rotation,
	 * so swap the dimensions here. However, pixel extension will
	 * need pre-rotation settings, this will be corrected below
	 * when calculating pixel extension settings.
	 */
	if (inline_rotation)
		swap(src_w, src_h);

	decimated = DECIMATED_DIMENSION(src_w,
			psde->pipe_cfg.horz_decimation);
	scale_cfg->phase_step_x[SDE_SSPP_COMP_0] =
		mult_frac((1 << PHASE_STEP_SHIFT), decimated, dst_w);
	decimated = DECIMATED_DIMENSION(src_h,
			psde->pipe_cfg.vert_decimation);
	scale_cfg->phase_step_y[SDE_SSPP_COMP_0] =
		mult_frac((1 << PHASE_STEP_SHIFT), decimated, dst_h);


	scale_cfg->phase_step_y[SDE_SSPP_COMP_1_2] =
		scale_cfg->phase_step_y[SDE_SSPP_COMP_0] / chroma_subsmpl_v;
	scale_cfg->phase_step_x[SDE_SSPP_COMP_1_2] =
		scale_cfg->phase_step_x[SDE_SSPP_COMP_0] / chroma_subsmpl_h;

	scale_cfg->phase_step_x[SDE_SSPP_COMP_2] =
		scale_cfg->phase_step_x[SDE_SSPP_COMP_1_2];
	scale_cfg->phase_step_y[SDE_SSPP_COMP_2] =
		scale_cfg->phase_step_y[SDE_SSPP_COMP_1_2];

	scale_cfg->phase_step_x[SDE_SSPP_COMP_3] =
		scale_cfg->phase_step_x[SDE_SSPP_COMP_0];
	scale_cfg->phase_step_y[SDE_SSPP_COMP_3] =
		scale_cfg->phase_step_y[SDE_SSPP_COMP_0];

	for (i = 0; i < SDE_MAX_PLANES; i++) {
		/*
		 * For inline rotation cases with pre-downscaling enabled
		 * set x pre-downscale value if required. Only x direction
		 * is currently supported. Use src_h as values have been swapped
		 * and x direction corresponds to height value.
		 */
		src_h_pre_down = src_h;
		if (pre_down_supported && inline_rotation) {
			if (i == 0 && (src_h > mult_frac(dst_h, 11, 5)))
				src_h_pre_down = src_h / 2;
		}

		scale_cfg->src_width[i] = DECIMATED_DIMENSION(src_w,
				psde->pipe_cfg.horz_decimation);
		scale_cfg->src_height[i] = DECIMATED_DIMENSION(src_h_pre_down,
				psde->pipe_cfg.vert_decimation);
		if (i == SDE_SSPP_COMP_1_2 || i == SDE_SSPP_COMP_2) {
			scale_cfg->src_width[i] /= chroma_subsmpl_h;
			scale_cfg->src_height[i] /= chroma_subsmpl_v;
		}
		scale_cfg->preload_x[i] = psde->pipe_sblk->scaler_blk.h_preload;
		scale_cfg->preload_y[i] = psde->pipe_sblk->scaler_blk.v_preload;

		/* For pixel extension we need the pre-rotated orientation */
		if (inline_rotation) {
			psde->pixel_ext.num_ext_pxls_top[i] =
				scale_cfg->src_width[i];
			psde->pixel_ext.num_ext_pxls_left[i] =
				scale_cfg->src_height[i];
		} else {
			psde->pixel_ext.num_ext_pxls_top[i] =
				scale_cfg->src_height[i];
			psde->pixel_ext.num_ext_pxls_left[i] =
				scale_cfg->src_width[i];
		}
	}

	if ((!(SDE_FORMAT_IS_YUV(fmt)) && (src_h == dst_h)
		&& (src_w == dst_w) && !inline_rotation) ||
		pstate->multirect_mode)
		return;

	SDE_DEBUG_PLANE(psde,
		"setting bilinear: src:%dx%d dst:%dx%d chroma:%dx%d fmt:%x\n",
			src_w, src_h, dst_w, dst_h,
			chroma_subsmpl_v, chroma_subsmpl_h,
			fmt->base.pixel_format);

	scale_cfg->dst_width = dst_w;
	scale_cfg->dst_height = dst_h;
	scale_cfg->y_rgb_filter_cfg = SDE_SCALE_BIL;
	scale_cfg->uv_filter_cfg = SDE_SCALE_BIL;
	scale_cfg->alpha_filter_cfg = SDE_SCALE_ALPHA_BIL;
	scale_cfg->lut_flag = 0;
	scale_cfg->blend_cfg = 1;
	scale_cfg->enable = 1;
	scale_cfg->dyn_exp_disabled = SDE_QSEED_DEFAULT_DYN_EXP;
}

/**
 * _sde_plane_setup_scaler2 - determine default scaler phase steps/filter type
 * @psde: Pointer to SDE plane object
 * @src: Source size
 * @dst: Destination size
 * @phase_steps: Pointer to output array for phase steps
 * @filter: Pointer to output array for filter type
 * @fmt: Pointer to format definition
 * @chroma_subsampling: Subsampling amount for chroma channel
 *
 * Returns: 0 on success
 */
static int _sde_plane_setup_scaler2(struct sde_plane *psde,
		uint32_t src, uint32_t dst, uint32_t *phase_steps,
		enum sde_hw_filter *filter, const struct sde_format *fmt,
		uint32_t chroma_subsampling)
{
	if (!psde || !phase_steps || !filter || !fmt) {
		SDE_ERROR(
			"invalid arg(s), plane %d phase %d filter %d fmt %d\n",
			!psde, !phase_steps, !filter, !fmt);
		return -EINVAL;
	}

	/* calculate phase steps, leave init phase as zero */
	phase_steps[SDE_SSPP_COMP_0] =
		mult_frac(1 << PHASE_STEP_SHIFT, src, dst);
	phase_steps[SDE_SSPP_COMP_1_2] =
		phase_steps[SDE_SSPP_COMP_0] / chroma_subsampling;
	phase_steps[SDE_SSPP_COMP_2] = phase_steps[SDE_SSPP_COMP_1_2];
	phase_steps[SDE_SSPP_COMP_3] = phase_steps[SDE_SSPP_COMP_0];

	/* calculate scaler config, if necessary */
	if (SDE_FORMAT_IS_YUV(fmt) || src != dst) {
		filter[SDE_SSPP_COMP_3] =
			(src <= dst) ? SDE_SCALE_FILTER_BIL :
			SDE_SCALE_FILTER_PCMN;

		if (SDE_FORMAT_IS_YUV(fmt)) {
			filter[SDE_SSPP_COMP_0] = SDE_SCALE_FILTER_CA;
			filter[SDE_SSPP_COMP_1_2] = filter[SDE_SSPP_COMP_3];
		} else {
			filter[SDE_SSPP_COMP_0] = filter[SDE_SSPP_COMP_3];
			filter[SDE_SSPP_COMP_1_2] =
				SDE_SCALE_FILTER_NEAREST;
		}
	} else {
		/* disable scaler */
		filter[SDE_SSPP_COMP_0] = SDE_SCALE_FILTER_MAX;
		filter[SDE_SSPP_COMP_1_2] = SDE_SCALE_FILTER_MAX;
		filter[SDE_SSPP_COMP_3] = SDE_SCALE_FILTER_MAX;
	}
	return 0;
}

/**
 * _sde_plane_setup_pixel_ext - determine default pixel extension values
 * @psde: Pointer to SDE plane object
 * @src: Source size
 * @dst: Destination size
 * @decimated_src: Source size after decimation, if any
 * @phase_steps: Pointer to output array for phase steps
 * @out_src: Output array for pixel extension values
 * @out_edge1: Output array for pixel extension first edge
 * @out_edge2: Output array for pixel extension second edge
 * @filter: Pointer to array for filter type
 * @fmt: Pointer to format definition
 * @chroma_subsampling: Subsampling amount for chroma channel
 * @post_compare: Whether to chroma subsampled source size for comparisions
 */
static void _sde_plane_setup_pixel_ext(struct sde_plane *psde,
		uint32_t src, uint32_t dst, uint32_t decimated_src,
		uint32_t *phase_steps, uint32_t *out_src, int *out_edge1,
		int *out_edge2, enum sde_hw_filter *filter,
		const struct sde_format *fmt, uint32_t chroma_subsampling,
		bool post_compare)
{
	int64_t edge1, edge2, caf;
	uint32_t src_work;
	int i, tmp;

	if (psde && phase_steps && out_src && out_edge1 &&
			out_edge2 && filter && fmt) {
		/* handle CAF for YUV formats */
		if (SDE_FORMAT_IS_YUV(fmt) && *filter == SDE_SCALE_FILTER_CA)
			caf = PHASE_STEP_UNIT_SCALE;
		else
			caf = 0;

		for (i = 0; i < SDE_MAX_PLANES; i++) {
			src_work = decimated_src;
			if (i == SDE_SSPP_COMP_1_2 || i == SDE_SSPP_COMP_2)
				src_work /= chroma_subsampling;
			if (post_compare)
				src = src_work;
			if (!SDE_FORMAT_IS_YUV(fmt) && (src == dst)) {
				/* unity */
				edge1 = 0;
				edge2 = 0;
			} else if (dst >= src) {
				/* upscale */
				edge1 = (1 << PHASE_RESIDUAL);
				edge1 -= caf;
				edge2 = (1 << PHASE_RESIDUAL);
				edge2 += (dst - 1) * *(phase_steps + i);
				edge2 -= (src_work - 1) * PHASE_STEP_UNIT_SCALE;
				edge2 += caf;
				edge2 = -(edge2);
			} else {
				/* downscale */
				edge1 = 0;
				edge2 = (dst - 1) * *(phase_steps + i);
				edge2 -= (src_work - 1) * PHASE_STEP_UNIT_SCALE;
				edge2 += *(phase_steps + i);
				edge2 = -(edge2);
			}

			/* only enable CAF for luma plane */
			caf = 0;

			/* populate output arrays */
			*(out_src + i) = src_work;

			/* edge updates taken from __pxl_extn_helper */
			if (edge1 >= 0) {
				tmp = (uint32_t)edge1;
				tmp >>= PHASE_STEP_SHIFT;
				*(out_edge1 + i) = -tmp;
			} else {
				tmp = (uint32_t)(-edge1);
				*(out_edge1 + i) =
					(tmp + PHASE_STEP_UNIT_SCALE - 1) >>
					PHASE_STEP_SHIFT;
			}
			if (edge2 >= 0) {
				tmp = (uint32_t)edge2;
				tmp >>= PHASE_STEP_SHIFT;
				*(out_edge2 + i) = -tmp;
			} else {
				tmp = (uint32_t)(-edge2);
				*(out_edge2 + i) =
					(tmp + PHASE_STEP_UNIT_SCALE - 1) >>
					PHASE_STEP_SHIFT;
			}
		}
	}
}

static inline void _sde_plane_setup_csc(struct sde_plane *psde)
{
	static const struct sde_csc_cfg sde_csc_YUV2RGB_601L = {
		{
			/* S15.16 format */
			0x00012A00, 0x00000000, 0x00019880,
			0x00012A00, 0xFFFF9B80, 0xFFFF3000,
			0x00012A00, 0x00020480, 0x00000000,
		},
		/* signed bias */
		{ 0xfff0, 0xff80, 0xff80,},
		{ 0x0, 0x0, 0x0,},
		/* unsigned clamp */
		{ 0x10, 0xeb, 0x10, 0xf0, 0x10, 0xf0,},
		{ 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,},
	};
	static const struct sde_csc_cfg sde_csc10_YUV2RGB_601L = {
		{
			/* S15.16 format */
			0x00012A00, 0x00000000, 0x00019880,
			0x00012A00, 0xFFFF9B80, 0xFFFF3000,
			0x00012A00, 0x00020480, 0x00000000,
			},
		/* signed bias */
		{ 0xffc0, 0xfe00, 0xfe00,},
		{ 0x0, 0x0, 0x0,},
		/* unsigned clamp */
		{ 0x40, 0x3ac, 0x40, 0x3c0, 0x40, 0x3c0,},
		{ 0x00, 0x3ff, 0x00, 0x3ff, 0x00, 0x3ff,},
	};

	if (!psde) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	/* revert to kernel default if override not available */
	if (psde->csc_usr_ptr)
		psde->csc_ptr = psde->csc_usr_ptr;
	else if (BIT(SDE_SSPP_CSC_10BIT) & psde->features)
		psde->csc_ptr = (struct sde_csc_cfg *)&sde_csc10_YUV2RGB_601L;
	else
		psde->csc_ptr = (struct sde_csc_cfg *)&sde_csc_YUV2RGB_601L;

	SDE_DEBUG_PLANE(psde, "using 0x%X 0x%X 0x%X...\n",
			psde->csc_ptr->csc_mv[0],
			psde->csc_ptr->csc_mv[1],
			psde->csc_ptr->csc_mv[2]);
}

static void sde_color_process_plane_setup(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	uint32_t hue, saturation, value, contrast;
	struct drm_msm_memcol *memcol = NULL;
	struct drm_msm_3d_gamut *vig_gamut = NULL;
	struct drm_msm_igc_lut *igc = NULL;
	struct drm_msm_pgc_lut *gc = NULL;
	size_t memcol_sz = 0, size = 0;
	struct sde_hw_cp_cfg hw_cfg = {};
	struct sde_hw_ctl *ctl = _sde_plane_get_hw_ctl(plane);
	bool fp16_igc, fp16_unmult;
	struct drm_msm_fp16_gc *fp16_gc = NULL;
	struct drm_msm_fp16_csc *fp16_csc = NULL;

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);

	hue = (uint32_t) sde_plane_get_property(pstate, PLANE_PROP_HUE_ADJUST);
	if (psde->pipe_hw->ops.setup_pa_hue)
		psde->pipe_hw->ops.setup_pa_hue(psde->pipe_hw, &hue);
	saturation = (uint32_t) sde_plane_get_property(pstate,
		PLANE_PROP_SATURATION_ADJUST);
	if (psde->pipe_hw->ops.setup_pa_sat)
		psde->pipe_hw->ops.setup_pa_sat(psde->pipe_hw, &saturation);
	value = (uint32_t) sde_plane_get_property(pstate,
		PLANE_PROP_VALUE_ADJUST);
	if (psde->pipe_hw->ops.setup_pa_val)
		psde->pipe_hw->ops.setup_pa_val(psde->pipe_hw, &value);
	contrast = (uint32_t) sde_plane_get_property(pstate,
		PLANE_PROP_CONTRAST_ADJUST);
	if (psde->pipe_hw->ops.setup_pa_cont)
		psde->pipe_hw->ops.setup_pa_cont(psde->pipe_hw, &contrast);

	if (psde->pipe_hw->ops.setup_pa_memcolor) {
		/* Skin memory color setup */
		memcol = msm_property_get_blob(&psde->property_info,
					&pstate->property_state,
					&memcol_sz,
					PLANE_PROP_SKIN_COLOR);
		psde->pipe_hw->ops.setup_pa_memcolor(psde->pipe_hw,
					MEMCOLOR_SKIN, memcol);

		/* Sky memory color setup */
		memcol = msm_property_get_blob(&psde->property_info,
					&pstate->property_state,
					&memcol_sz,
					PLANE_PROP_SKY_COLOR);
		psde->pipe_hw->ops.setup_pa_memcolor(psde->pipe_hw,
					MEMCOLOR_SKY, memcol);

		/* Foliage memory color setup */
		memcol = msm_property_get_blob(&psde->property_info,
					&pstate->property_state,
					&memcol_sz,
					PLANE_PROP_FOLIAGE_COLOR);
		psde->pipe_hw->ops.setup_pa_memcolor(psde->pipe_hw,
					MEMCOLOR_FOLIAGE, memcol);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_VIG_GAMUT &&
			psde->pipe_hw->ops.setup_vig_gamut) {
		vig_gamut = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_VIG_GAMUT);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = vig_gamut;
		psde->pipe_hw->ops.setup_vig_gamut(psde->pipe_hw, &hw_cfg);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_VIG_IGC &&
			psde->pipe_hw->ops.setup_vig_igc) {
		igc = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_VIG_IGC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = igc;
		psde->pipe_hw->ops.setup_vig_igc(psde->pipe_hw, &hw_cfg);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_DMA_IGC &&
			psde->pipe_hw->ops.setup_dma_igc) {
		igc = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_DMA_IGC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = igc;
		psde->pipe_hw->ops.setup_dma_igc(psde->pipe_hw, &hw_cfg,
				pstate->multirect_index);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_DMA_GC &&
			psde->pipe_hw->ops.setup_dma_gc) {
		gc = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_DMA_GC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = gc;
		psde->pipe_hw->ops.setup_dma_gc(psde->pipe_hw, &hw_cfg,
				pstate->multirect_index);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_FP16_IGC &&
			psde->pipe_hw->ops.setup_fp16_igc) {
		fp16_igc = !!sde_plane_get_property(pstate,
				PLANE_PROP_FP16_IGC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = sizeof(bool);
		hw_cfg.payload = &fp16_igc;
		psde->pipe_hw->ops.setup_fp16_igc(psde->pipe_hw,
				pstate->multirect_index, &hw_cfg);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_FP16_GC &&
			psde->pipe_hw->ops.setup_fp16_gc) {
		fp16_gc = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_FP16_GC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = fp16_gc;
		psde->pipe_hw->ops.setup_fp16_gc(psde->pipe_hw,
				pstate->multirect_index, &hw_cfg);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_FP16_CSC &&
			psde->pipe_hw->ops.setup_fp16_csc) {
		fp16_csc = msm_property_get_blob(&psde->property_info,
				&pstate->property_state,
				&size,
				PLANE_PROP_FP16_CSC);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = size;
		hw_cfg.payload = fp16_csc;
		psde->pipe_hw->ops.setup_fp16_csc(psde->pipe_hw,
				pstate->multirect_index, &hw_cfg);
	}

	if (pstate->dirty & SDE_PLANE_DIRTY_FP16_UNMULT &&
			psde->pipe_hw->ops.setup_fp16_unmult) {
		fp16_unmult = !!sde_plane_get_property(pstate,
				PLANE_PROP_FP16_UNMULT);
		hw_cfg.last_feature = 0;
		hw_cfg.ctl = ctl;
		hw_cfg.len = sizeof(bool);
		hw_cfg.payload = &fp16_unmult;
		psde->pipe_hw->ops.setup_fp16_unmult(psde->pipe_hw,
				pstate->multirect_index, &hw_cfg);
	}
}

static void _sde_plane_setup_scaler(struct sde_plane *psde,
		struct sde_plane_state *pstate,
		const struct sde_format *fmt, bool color_fill)
{
	struct sde_hw_pixel_ext *pe;
	uint32_t chroma_subsmpl_h, chroma_subsmpl_v;
	const struct drm_format_info *info = NULL;

	if (!psde || !fmt || !pstate) {
		SDE_ERROR("invalid arg(s), plane %d fmt %d state %d\n",
				!psde, !fmt, !pstate);
		return;
	}

	memcpy(&psde->scaler3_cfg, &pstate->scaler3_cfg,
			sizeof(psde->scaler3_cfg));
	memcpy(&psde->pixel_ext, &pstate->pixel_ext,
			sizeof(psde->pixel_ext));

	info = drm_format_info(fmt->base.pixel_format);
	pe = &psde->pixel_ext;

	psde->pipe_cfg.horz_decimation =
		sde_plane_get_property(pstate, PLANE_PROP_H_DECIMATE);
	psde->pipe_cfg.vert_decimation =
		sde_plane_get_property(pstate, PLANE_PROP_V_DECIMATE);

	/* don't chroma subsample if decimating */
	chroma_subsmpl_h = psde->pipe_cfg.horz_decimation ? 1 : info->hsub;
	chroma_subsmpl_v = psde->pipe_cfg.vert_decimation ? 1 : info->vsub;

	/* update scaler */
	if (psde->features & BIT(SDE_SSPP_SCALER_QSEED3) ||
			(psde->features & BIT(SDE_SSPP_SCALER_QSEED3LITE))) {
		int rc = -EINVAL;

		if (!color_fill && !psde->debugfs_default_scale)
			rc = is_qseed3_rev_qseed3lite(psde->pipe_hw->catalog) ?
			_sde_plane_setup_scaler3lite_lut(psde, pstate) :
				_sde_plane_setup_scaler3_lut(psde, pstate);
		if (rc || pstate->scaler_check_state !=
					SDE_PLANE_SCLCHECK_SCALER_V2) {
			SDE_EVT32_VERBOSE(DRMID(&psde->base), color_fill,
					pstate->scaler_check_state,
					psde->debugfs_default_scale, rc,
					psde->pipe_cfg.src_rect.w,
					psde->pipe_cfg.src_rect.h,
					psde->pipe_cfg.dst_rect.w,
					psde->pipe_cfg.dst_rect.h,
					pstate->multirect_mode);

			/* calculate default config for QSEED3 */
			_sde_plane_setup_scaler3(psde, pstate, fmt,
					chroma_subsmpl_h, chroma_subsmpl_v);
		}
	} else if (pstate->scaler_check_state != SDE_PLANE_SCLCHECK_SCALER_V1 ||
			color_fill || psde->debugfs_default_scale) {
		uint32_t deci_dim, i;

		/* calculate default configuration for QSEED2 */
		memset(pe, 0, sizeof(struct sde_hw_pixel_ext));

		SDE_DEBUG_PLANE(psde, "default config\n");
		deci_dim = DECIMATED_DIMENSION(psde->pipe_cfg.src_rect.w,
				psde->pipe_cfg.horz_decimation);
		_sde_plane_setup_scaler2(psde,
				deci_dim,
				psde->pipe_cfg.dst_rect.w,
				pe->phase_step_x,
				pe->horz_filter, fmt, chroma_subsmpl_h);

		if (SDE_FORMAT_IS_YUV(fmt))
			deci_dim &= ~0x1;
		_sde_plane_setup_pixel_ext(psde, psde->pipe_cfg.src_rect.w,
				psde->pipe_cfg.dst_rect.w, deci_dim,
				pe->phase_step_x,
				pe->roi_w,
				pe->num_ext_pxls_left,
				pe->num_ext_pxls_right, pe->horz_filter, fmt,
				chroma_subsmpl_h, 0);

		deci_dim = DECIMATED_DIMENSION(psde->pipe_cfg.src_rect.h,
				psde->pipe_cfg.vert_decimation);
		_sde_plane_setup_scaler2(psde,
				deci_dim,
				psde->pipe_cfg.dst_rect.h,
				pe->phase_step_y,
				pe->vert_filter, fmt, chroma_subsmpl_v);
		_sde_plane_setup_pixel_ext(psde, psde->pipe_cfg.src_rect.h,
				psde->pipe_cfg.dst_rect.h, deci_dim,
				pe->phase_step_y,
				pe->roi_h,
				pe->num_ext_pxls_top,
				pe->num_ext_pxls_btm, pe->vert_filter, fmt,
				chroma_subsmpl_v, 1);

		for (i = 0; i < SDE_MAX_PLANES; i++) {
			if (pe->num_ext_pxls_left[i] >= 0)
				pe->left_rpt[i] = pe->num_ext_pxls_left[i];
			else
				pe->left_ftch[i] = pe->num_ext_pxls_left[i];

			if (pe->num_ext_pxls_right[i] >= 0)
				pe->right_rpt[i] = pe->num_ext_pxls_right[i];
			else
				pe->right_ftch[i] = pe->num_ext_pxls_right[i];

			if (pe->num_ext_pxls_top[i] >= 0)
				pe->top_rpt[i] = pe->num_ext_pxls_top[i];
			else
				pe->top_ftch[i] = pe->num_ext_pxls_top[i];

			if (pe->num_ext_pxls_btm[i] >= 0)
				pe->btm_rpt[i] = pe->num_ext_pxls_btm[i];
			else
				pe->btm_ftch[i] = pe->num_ext_pxls_btm[i];
		}
	}

	if (psde->pipe_hw->ops.setup_pre_downscale)
		psde->pipe_hw->ops.setup_pre_downscale(psde->pipe_hw,
				&pstate->pre_down);
}

/**
 * _sde_plane_color_fill - enables color fill on plane
 * @psde:   Pointer to SDE plane object
 * @color:  RGB fill color value, [23..16] Blue, [15..8] Green, [7..0] Red
 * @alpha:  8-bit fill alpha value, 255 selects 100% alpha
 * Returns: 0 on success
 */
static int _sde_plane_color_fill(struct sde_plane *psde,
		uint32_t color, uint32_t alpha)
{
	const struct sde_format *fmt;
	const struct drm_plane *plane;
	struct sde_plane_state *pstate;
	bool blend_enable = true;

	if (!psde || !psde->base.state) {
		SDE_ERROR("invalid plane\n");
		return -EINVAL;
	}

	if (!psde->pipe_hw) {
		SDE_ERROR_PLANE(psde, "invalid plane h/w pointer\n");
		return -EINVAL;
	}

	plane = &psde->base;
	pstate = to_sde_plane_state(plane->state);

	SDE_DEBUG_PLANE(psde, "\n");

	/*
	 * select fill format to match user property expectation,
	 * h/w only supports RGB variants
	 */
	fmt = sde_get_sde_format(DRM_FORMAT_ABGR8888);

	blend_enable = (SDE_DRM_BLEND_OP_OPAQUE !=
			sde_plane_get_property(pstate, PLANE_PROP_BLEND_OP));

	/* update sspp */
	if (fmt && psde->pipe_hw->ops.setup_solidfill) {
		psde->pipe_hw->ops.setup_solidfill(psde->pipe_hw,
				(color & 0xFFFFFF) | ((alpha & 0xFF) << 24),
				pstate->multirect_index);

		/* override scaler/decimation if solid fill */
		psde->pipe_cfg.src_rect.x = 0;
		psde->pipe_cfg.src_rect.y = 0;
		psde->pipe_cfg.src_rect.w = psde->pipe_cfg.dst_rect.w;
		psde->pipe_cfg.src_rect.h = psde->pipe_cfg.dst_rect.h;
		_sde_plane_setup_scaler(psde, pstate, fmt, true);

		if (psde->pipe_hw->ops.setup_format)
			psde->pipe_hw->ops.setup_format(psde->pipe_hw,
					fmt, blend_enable,
					SDE_SSPP_SOLID_FILL,
					pstate->multirect_index);

		if (psde->pipe_hw->ops.setup_rects)
			psde->pipe_hw->ops.setup_rects(psde->pipe_hw,
					&psde->pipe_cfg,
					pstate->multirect_index);

		if (psde->pipe_hw->ops.setup_pe)
			psde->pipe_hw->ops.setup_pe(psde->pipe_hw,
					&psde->pixel_ext);
		if (psde->pipe_hw->ops.setup_scaler &&
				pstate->multirect_index != SDE_SSPP_RECT_1) {
			psde->pipe_hw->ctl = _sde_plane_get_hw_ctl(plane);
			psde->pipe_hw->ops.setup_scaler(psde->pipe_hw,
					&psde->pipe_cfg, &psde->pixel_ext,
					&psde->scaler3_cfg);
		}
	}

	return 0;
}

/**
* sde_plane_rot_atomic_check - verify rotator update of the given state
* @plane: Pointer to drm plane
* @state: Pointer to drm plane state to be validated
* return: 0 if success; error code otherwise
*/
static int sde_plane_rot_atomic_check(struct drm_plane *plane,
	struct drm_plane_state *state)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate, *old_pstate;
	int ret = 0;
	u32 rotation;

	if (!plane || !state) {
		SDE_ERROR("invalid plane/state\n");
		return -EINVAL;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(state);
	old_pstate = to_sde_plane_state(plane->state);

	/* check inline rotation and simplify the transform */
	rotation = drm_rotation_simplify(
			state->rotation,
			DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 |
			DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y);

	if ((rotation & DRM_MODE_ROTATE_180) ||
		(rotation & DRM_MODE_ROTATE_270)) {
		SDE_ERROR_PLANE(psde,
			"invalid rotation transform must be simplified 0x%x\n",
			rotation);
		ret = -EINVAL;
		goto exit;
	}

	if (rotation & DRM_MODE_ROTATE_90) {
		struct msm_drm_private *priv = plane->dev->dev_private;
		struct sde_kms *sde_kms;
		const struct msm_format *msm_fmt;
		const struct sde_format *fmt;
		struct sde_rect src;
		bool q16_data = true;

		POPULATE_RECT(&src, state->src_x, state->src_y,
			state->src_w, state->src_h, q16_data);
		/*
		 * DRM framework expects rotation flag in counter-clockwise
		 * direction and the HW expects in clockwise direction.
		 * Flip the flags to match with HW.
		 */
		rotation ^= (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y);

		if (!psde->pipe_sblk->in_rot_maxdwnscale_rt_num ||
			!psde->pipe_sblk->in_rot_maxdwnscale_rt_denom ||
			!psde->pipe_sblk->in_rot_maxdwnscale_nrt ||
			!psde->pipe_sblk->in_rot_maxheight ||
			!psde->pipe_sblk->in_rot_format_list ||
			!(psde->features & BIT(SDE_SSPP_TRUE_INLINE_ROT))) {
			SDE_ERROR_PLANE(psde,
			    "wrong config rt:%d/%d nrt:%d fmt:%d h:%d 0x%x\n",
				!psde->pipe_sblk->in_rot_maxdwnscale_rt_num,
				!psde->pipe_sblk->in_rot_maxdwnscale_rt_denom,
				!psde->pipe_sblk->in_rot_maxdwnscale_nrt,
				!psde->pipe_sblk->in_rot_format_list,
				!psde->pipe_sblk->in_rot_maxheight,
				psde->features);
			ret = -EINVAL;
			goto exit;
		}

		/* check for valid height */
		if (src.h > psde->pipe_sblk->in_rot_maxheight) {
			SDE_ERROR_PLANE(psde,
				"invalid height for inline rot:%d max:%d\n",
				src.h, psde->pipe_sblk->in_rot_maxheight);
			ret = -EINVAL;
			goto exit;
		}

		if (!sde_plane_enabled(state))
			goto exit;

		/* check for valid formats supported by inline rot */
		sde_kms = to_sde_kms(priv->kms);
		msm_fmt = msm_framebuffer_format(state->fb);
		fmt = to_sde_format(msm_fmt);
		ret = sde_format_validate_fmt(&sde_kms->base, fmt,
			psde->pipe_sblk->in_rot_format_list);
	}

exit:
	pstate->rotation = rotation;
	return ret;
}

static bool _sde_plane_halt_requests(struct drm_plane *plane,
		uint32_t xin_id, bool halt_forced_clk, bool enable)
{
	struct sde_plane *psde;
	struct msm_drm_private *priv;
	struct sde_vbif_set_xin_halt_params halt_params;

	if (!plane || !plane->dev) {
		SDE_ERROR("invalid arguments\n");
		return false;
	}

	psde = to_sde_plane(plane);
	if (!psde->pipe_hw || !psde->pipe_hw->cap) {
		SDE_ERROR("invalid pipe reference\n");
		return false;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return false;
	}

	memset(&halt_params, 0, sizeof(halt_params));
	halt_params.vbif_idx = VBIF_RT;
	halt_params.xin_id = xin_id;
	halt_params.clk_ctrl = psde->pipe_hw->cap->clk_ctrl;
	halt_params.forced_on = halt_forced_clk;
	halt_params.enable = enable;

	return sde_vbif_set_xin_halt(to_sde_kms(priv->kms), &halt_params);
}

void sde_plane_halt_requests(struct drm_plane *plane, bool enable)
{
	struct sde_plane *psde;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);
	if (!psde->pipe_hw || !psde->pipe_hw->cap) {
		SDE_ERROR("invalid pipe reference\n");
		return;
	}

	SDE_EVT32(DRMID(plane), psde->xin_halt_forced_clk, enable);

	psde->xin_halt_forced_clk =
		_sde_plane_halt_requests(plane, psde->pipe_hw->cap->xin_id,
				psde->xin_halt_forced_clk, enable);
}

void sde_plane_secure_ctrl_xin_client(struct drm_plane *plane,
				struct drm_crtc *crtc)
{
	struct sde_plane *psde;

	if (!plane || !crtc) {
		SDE_ERROR("invalid plane/crtc\n");
		return;
	}
	psde = to_sde_plane(plane);

	if (psde->features & BIT(SDE_SSPP_BLOCK_SEC_UI))
		return;

	/* do all VBIF programming for the sec-ui allowed SSPP */
	_sde_plane_set_qos_remap(plane);
	_sde_plane_set_ot_limit(plane, crtc);
}

/**
 * sde_plane_rot_install_properties - install plane rotator properties
 * @plane: Pointer to drm plane
 * @catalog: Pointer to mdss configuration
 * return: none
 */
static void sde_plane_rot_install_properties(struct drm_plane *plane,
	struct sde_mdss_cfg *catalog)
{
	struct sde_plane *psde = to_sde_plane(plane);
	unsigned int supported_rotations = DRM_MODE_ROTATE_0 |
		DRM_MODE_ROTATE_180 | DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y;
	int ret = 0;

	if (!plane || !psde) {
		SDE_ERROR("invalid plane\n");
		return;
	} else if (!catalog) {
		SDE_ERROR("invalid catalog\n");
		return;
	}

	if (!psde->is_virtual && psde->features & BIT(SDE_SSPP_TRUE_INLINE_ROT))
		supported_rotations |= DRM_MODE_ROTATE_90 | DRM_MODE_ROTATE_270;

	ret = drm_plane_create_rotation_property(plane,
			DRM_MODE_ROTATE_0, supported_rotations);
	if (ret) {
		DRM_ERROR("create rotation property failed: %d\n", ret);
		return;
	}
}

void sde_plane_clear_multirect(const struct drm_plane_state *drm_state)
{
	struct sde_plane_state *pstate;

	if (!drm_state)
		return;

	pstate = to_sde_plane_state(drm_state);

	pstate->multirect_index = SDE_SSPP_RECT_SOLO;
	pstate->multirect_mode = SDE_SSPP_MULTIRECT_NONE;
}

/**
 * multi_rect validate API allows to validate only R0 and R1 RECT
 * passing for each plane. Client of this API must not pass multiple
 * plane which are not sharing same XIN client. Such calls will fail
 * even though kernel client is passing valid multirect configuration.
 */
int sde_plane_validate_multirect_v2(struct sde_multirect_plane_states *plane)
{
	struct sde_plane_state *pstate[R_MAX];
	const struct drm_plane_state *drm_state[R_MAX];
	struct sde_rect src[R_MAX], dst[R_MAX];
	struct sde_plane *sde_plane[R_MAX];
	const struct sde_format *fmt[R_MAX];
	int xin_id[R_MAX];
	bool q16_data = true;
	int i, j, buffer_lines, width_threshold[R_MAX];
	unsigned int max_tile_height = 1;
	bool parallel_fetch_qualified = true;
	enum sde_sspp_multirect_mode mode = SDE_SSPP_MULTIRECT_NONE;
	const struct msm_format *msm_fmt;
	bool const_alpha_enable = true;

	for (i = 0; i < R_MAX; i++) {
		drm_state[i] = i ? plane->r1 : plane->r0;
		if (!drm_state[i]) {
			SDE_ERROR("drm plane state is NULL\n");
			return -EINVAL;
		}

		pstate[i] = to_sde_plane_state(drm_state[i]);
		sde_plane[i] = to_sde_plane(drm_state[i]->plane);
		xin_id[i] = sde_plane[i]->pipe_hw->cap->xin_id;

		for (j = 0; j < i; j++) {
			if (xin_id[i] != xin_id[j]) {
				SDE_ERROR_PLANE(sde_plane[i],
					"invalid multirect validate call base:%d xin_id:%d curr:%d xin:%d\n",
					j, xin_id[j], i, xin_id[i]);
				return -EINVAL;
			}
		}

		msm_fmt = msm_framebuffer_format(drm_state[i]->fb);
		if (!msm_fmt) {
			SDE_ERROR_PLANE(sde_plane[i], "null fb\n");
			return -EINVAL;
		}
		fmt[i] = to_sde_format(msm_fmt);

		if (SDE_FORMAT_IS_UBWC(fmt[i]) &&
		    (fmt[i]->tile_height > max_tile_height))
			max_tile_height = fmt[i]->tile_height;

		POPULATE_RECT(&src[i], drm_state[i]->src_x, drm_state[i]->src_y,
			drm_state[i]->src_w, drm_state[i]->src_h, q16_data);
		POPULATE_RECT(&dst[i], drm_state[i]->crtc_x,
				drm_state[i]->crtc_y, drm_state[i]->crtc_w,
				drm_state[i]->crtc_h, !q16_data);

		if (src[i].w != dst[i].w || src[i].h != dst[i].h) {
			SDE_ERROR_PLANE(sde_plane[i],
				"scaling is not supported in multirect mode\n");
			return -EINVAL;
		}

		if (pstate[i]->rotation & DRM_MODE_ROTATE_90) {
			SDE_ERROR_PLANE(sde_plane[i],
				"inline rotation is not supported in mulirect mode\n");
			return -EINVAL;
		}

		if (SDE_FORMAT_IS_YUV(fmt[i])) {
			SDE_ERROR_PLANE(sde_plane[i],
				"Unsupported format for multirect mode\n");
			return -EINVAL;
		}

		/**
		 * SSPP PD_MEM is split half - one for each RECT.
		 * Tiled formats need 5 lines of buffering while fetching
		 * whereas linear formats need only 2 lines.
		 * So we cannot support more than half of the supported SSPP
		 * width for tiled formats.
		 */
		width_threshold[i] = sde_plane[i]->pipe_sblk->maxlinewidth;
		if (SDE_FORMAT_IS_UBWC(fmt[i]))
			width_threshold[i] /= 2;

		if (parallel_fetch_qualified && src[i].w > width_threshold[i])
			parallel_fetch_qualified = false;

		if (sde_plane[i]->is_virtual)
			mode = sde_plane_get_property(pstate[i],
					PLANE_PROP_MULTIRECT_MODE);

		if (pstate[i]->const_alpha_en != const_alpha_enable)
			const_alpha_enable = false;

	}

	buffer_lines = 2 * max_tile_height;

	/**
	 * fallback to driver mode selection logic if client is using
	 * multirect plane without setting property.
	 *
	 * validate multirect mode configuration based on rectangle
	 */
	switch (mode) {
	case SDE_SSPP_MULTIRECT_NONE:
		if (parallel_fetch_qualified)
			mode = SDE_SSPP_MULTIRECT_PARALLEL;
		else if (TIME_MULTIPLEX_RECT(dst[R1], dst[R0], buffer_lines) ||
			 TIME_MULTIPLEX_RECT(dst[R0], dst[R1], buffer_lines))
			mode = SDE_SSPP_MULTIRECT_TIME_MX;
		else
			SDE_ERROR(
				"planes(%d - %d) multirect mode selection fail\n",
				drm_state[R0]->plane->base.id,
				drm_state[R1]->plane->base.id);
		break;

	case SDE_SSPP_MULTIRECT_PARALLEL:
		if (!parallel_fetch_qualified) {
			SDE_ERROR("R0 plane:%d width_threshold:%d src_w:%d\n",
				drm_state[R0]->plane->base.id,
				width_threshold[R0],  src[R0].w);
			SDE_ERROR("R1 plane:%d width_threshold:%d src_w:%d\n",
				drm_state[R1]->plane->base.id,
				width_threshold[R1],  src[R1].w);
			SDE_ERROR("parallel fetch not qualified\n");
			mode = SDE_SSPP_MULTIRECT_NONE;
		}
		break;

	case SDE_SSPP_MULTIRECT_TIME_MX:
		if (!TIME_MULTIPLEX_RECT(dst[R1], dst[R0], buffer_lines) &&
		    !TIME_MULTIPLEX_RECT(dst[R0], dst[R1], buffer_lines)) {
			SDE_ERROR(
				"buffer_lines:%d R0 plane:%d dst_y:%d dst_h:%d\n",
				buffer_lines, drm_state[R0]->plane->base.id,
				dst[R0].y, dst[R0].h);
			SDE_ERROR(
				"buffer_lines:%d R1 plane:%d dst_y:%d dst_h:%d\n",
				buffer_lines, drm_state[R1]->plane->base.id,
				dst[R1].y, dst[R1].h);
			SDE_ERROR("time multiplexed fetch not qualified\n");
			mode = SDE_SSPP_MULTIRECT_NONE;
		}
		break;

	default:
		SDE_ERROR("bad mode:%d selection\n", mode);
		mode = SDE_SSPP_MULTIRECT_NONE;
		break;
	}

	for (i = 0; i < R_MAX; i++) {
		pstate[i]->multirect_mode = mode;
		pstate[i]->const_alpha_en = const_alpha_enable;
	}

	if (mode == SDE_SSPP_MULTIRECT_NONE)
		return -EINVAL;

	if (sde_plane[R0]->is_virtual) {
		pstate[R0]->multirect_index = SDE_SSPP_RECT_1;
		pstate[R1]->multirect_index = SDE_SSPP_RECT_0;
	} else {
		pstate[R0]->multirect_index = SDE_SSPP_RECT_0;
		pstate[R1]->multirect_index = SDE_SSPP_RECT_1;
	}

	SDE_DEBUG_PLANE(sde_plane[R0], "R0: %d - %d\n",
		pstate[R0]->multirect_mode, pstate[R0]->multirect_index);
	SDE_DEBUG_PLANE(sde_plane[R1], "R1: %d - %d\n",
		pstate[R1]->multirect_mode, pstate[R1]->multirect_index);

	return 0;
}

/**
 * sde_plane_ctl_flush - set/clear control flush bitmask for the given plane
 * @plane: Pointer to drm plane structure
 * @ctl: Pointer to hardware control driver
 * @set: set if true else clear
 */
void sde_plane_ctl_flush(struct drm_plane *plane, struct sde_hw_ctl *ctl,
		bool set)
{
	if (!plane || !ctl) {
		SDE_ERROR("invalid parameters\n");
		return;
	}

	if (!ctl->ops.update_bitmask_sspp) {
		SDE_ERROR("invalid ops\n");
		return;
	}

	ctl->ops.update_bitmask_sspp(ctl, sde_plane_pipe(plane), set);
}

static int sde_plane_prepare_fb(struct drm_plane *plane,
		struct drm_plane_state *new_state)
{
	struct drm_framebuffer *fb = new_state->fb;
	struct sde_plane *psde = to_sde_plane(plane);
	struct sde_plane_state *pstate = to_sde_plane_state(new_state);
	struct sde_hw_fmt_layout layout;
	struct msm_gem_address_space *aspace;
	int ret;

	if (!fb)
		return 0;

	SDE_DEBUG_PLANE(psde, "FB[%u]\n", fb->base.id);

	ret = _sde_plane_get_aspace(psde, pstate, &aspace);
	if (ret) {
		SDE_ERROR_PLANE(psde, "Failed to get aspace\n");
		return ret;
	}

	/* cache aspace */
	pstate->aspace = aspace;

	/*
	 * when transitioning from secure to non-secure,
	 * plane->prepare_fb happens before the commit. In such case,
	 * defer the prepare_fb and handled it late, during the commit
	 * after attaching the domains as part of the transition
	 */
	pstate->defer_prepare_fb = (aspace && !aspace->domain_attached) ?
							true : false;

	if (pstate->defer_prepare_fb) {
		SDE_EVT32(DRMID(plane), psde->pipe);
		SDE_DEBUG_PLANE(psde,
		    "domain not attached, prepare_fb handled later\n");
		return 0;
	}

	if (pstate->aspace && fb) {
		ret = msm_framebuffer_prepare(fb,
				pstate->aspace);
		if (ret) {
			SDE_ERROR("failed to prepare framebuffer fb:%d plane:%d pipe:%d ret:%d\n",
				 fb->base.id, plane->base.id, psde->pipe, ret);
			SDE_EVT32(fb->base.id, plane->base.id, psde->pipe, ret, SDE_EVTLOG_ERROR);
			return ret;
		}
	}

	/* validate framebuffer layout before commit */
	ret = sde_format_populate_layout(pstate->aspace,
			fb, &layout);
	if (ret) {
		SDE_ERROR_PLANE(psde, "failed to get format layout, %d\n", ret);
		return ret;
	}

	return 0;
}

static void sde_plane_cleanup_fb(struct drm_plane *plane,
		struct drm_plane_state *old_state)
{
	struct sde_plane *psde = to_sde_plane(plane);
	struct sde_plane_state *old_pstate;

	if (!old_state || !old_state->fb || !plane)
		return;

	old_pstate = to_sde_plane_state(old_state);

	SDE_DEBUG_PLANE(psde, "FB[%u]\n", old_state->fb->base.id);

	msm_framebuffer_cleanup(old_state->fb, old_pstate->aspace);

}

static void _sde_plane_sspp_atomic_check_mode_changed(struct sde_plane *psde,
		struct drm_plane_state *state,
		struct drm_plane_state *old_state)
{
	struct sde_plane_state *pstate = to_sde_plane_state(state);
	struct sde_plane_state *old_pstate = to_sde_plane_state(old_state);
	struct drm_framebuffer *fb, *old_fb;

	/* no need to check it again */
	if (pstate->dirty == SDE_PLANE_DIRTY_ALL)
		return;

	if (!sde_plane_enabled(state) || !sde_plane_enabled(old_state)
			|| psde->is_error) {
		SDE_DEBUG_PLANE(psde,
			"enabling/disabling full modeset required\n");
		pstate->dirty |= SDE_PLANE_DIRTY_ALL;
	} else if (to_sde_plane_state(old_state)->pending) {
		SDE_DEBUG_PLANE(psde, "still pending\n");
		pstate->dirty |= SDE_PLANE_DIRTY_ALL;
	} else if (pstate->multirect_index != old_pstate->multirect_index ||
			pstate->multirect_mode != old_pstate->multirect_mode) {
		SDE_DEBUG_PLANE(psde, "multirect config updated\n");
		pstate->dirty |= SDE_PLANE_DIRTY_ALL;
	} else if (state->src_w != old_state->src_w ||
		   state->src_h != old_state->src_h ||
		   state->src_x != old_state->src_x ||
		   state->src_y != old_state->src_y) {
		SDE_DEBUG_PLANE(psde, "src rect updated\n");
		pstate->dirty |= SDE_PLANE_DIRTY_RECTS;
	} else if (state->crtc_w != old_state->crtc_w ||
		   state->crtc_h != old_state->crtc_h ||
		   state->crtc_x != old_state->crtc_x ||
		   state->crtc_y != old_state->crtc_y) {
		SDE_DEBUG_PLANE(psde, "crtc rect updated\n");
		pstate->dirty |= SDE_PLANE_DIRTY_RECTS;
	} else if (pstate->excl_rect.w != old_pstate->excl_rect.w ||
		   pstate->excl_rect.h != old_pstate->excl_rect.h ||
		   pstate->excl_rect.x != old_pstate->excl_rect.x ||
		   pstate->excl_rect.y != old_pstate->excl_rect.y) {
		SDE_DEBUG_PLANE(psde, "excl_rect updated\n");
		pstate->dirty |= SDE_PLANE_DIRTY_RECTS;
	} else if (pstate->rotation != old_pstate->rotation) {
		SDE_DEBUG_PLANE(psde, "rotation updated 0x%x->0x%x\n",
			pstate->rotation, old_pstate->rotation);
		pstate->dirty |= SDE_PLANE_DIRTY_FORMAT;
	}

	fb = state->fb;
	old_fb = old_state->fb;

	if (!fb || !old_fb) {
		SDE_DEBUG_PLANE(psde, "can't compare fb handles\n");
	} else if ((fb->format->format != old_fb->format->format) ||
			pstate->const_alpha_en != old_pstate->const_alpha_en) {
		SDE_DEBUG_PLANE(psde, "format change\n");
		pstate->dirty |= SDE_PLANE_DIRTY_FORMAT | SDE_PLANE_DIRTY_RECTS;
	} else {
		uint64_t new_mod = fb->modifier;
		uint64_t old_mod = old_fb->modifier;
		uint32_t *new_pitches = fb->pitches;
		uint32_t *old_pitches = old_fb->pitches;
		uint32_t *new_offset = fb->offsets;
		uint32_t *old_offset = old_fb->offsets;
		int i;

		if (new_mod != old_mod) {
			SDE_DEBUG_PLANE(psde,
				"format modifiers change new_mode:%llu old_mode:%llu\n",
				new_mod, old_mod);
			pstate->dirty |= SDE_PLANE_DIRTY_FORMAT |
				SDE_PLANE_DIRTY_RECTS;
		}

		for (i = 0; i < ARRAY_SIZE(fb->pitches); i++) {
			if (new_pitches[i] != old_pitches[i]) {
				SDE_DEBUG_PLANE(psde,
					"pitches change plane:%d old_pitches:%u new_pitches:%u\n",
					i, old_pitches[i], new_pitches[i]);
				pstate->dirty |= SDE_PLANE_DIRTY_RECTS;
				break;
			}
		}
		for (i = 0; i < ARRAY_SIZE(fb->offsets); i++) {
			if (new_offset[i] != old_offset[i]) {
				SDE_DEBUG_PLANE(psde,
					"offset change plane:%d old_offset:%u new_offset:%u\n",
					i, old_offset[i], new_offset[i]);
				pstate->dirty |= SDE_PLANE_DIRTY_FORMAT |
					SDE_PLANE_DIRTY_RECTS;
				break;
			}
		}
	}
}

int sde_plane_validate_src_addr(struct drm_plane *plane,
		unsigned long base_addr, u32 size)
{
	int ret =  -EINVAL;
	u32 addr;
	struct sde_plane *psde = to_sde_plane(plane);

	if (!psde || !base_addr || !size) {
		SDE_ERROR_PLANE(psde, "invalid arguments\n");
		return ret;
	}

	if (psde->pipe_hw && psde->pipe_hw->ops.get_sourceaddress) {
		addr = psde->pipe_hw->ops.get_sourceaddress(psde->pipe_hw,
				is_sde_plane_virtual(plane));
		if ((addr >= base_addr) && (addr < (base_addr + size)))
			ret = 0;
	}

	return ret;
}

static inline bool _sde_plane_is_pre_downscale_enabled(
		struct sde_hw_inline_pre_downscale_cfg *pre_down)
{
	return pre_down->pre_downscale_x_0 || pre_down->pre_downscale_y_0;
}

static int _sde_plane_validate_scaler_v2(struct sde_plane *psde,
		struct sde_plane_state *pstate,
		const struct sde_format *fmt,
		uint32_t img_w, uint32_t img_h,
		uint32_t src_w, uint32_t src_h,
		uint32_t deci_w, uint32_t deci_h)
{
	struct sde_hw_inline_pre_downscale_cfg *pd_cfg;
	bool pre_down_en;
	int i;

	if (!psde || !pstate || !fmt) {
		SDE_ERROR_PLANE(psde, "invalid arguments\n");
		return -EINVAL;
	}

	if (psde->debugfs_default_scale ||
	   (pstate->scaler_check_state != SDE_PLANE_SCLCHECK_SCALER_V2 &&
	    pstate->scaler_check_state != SDE_PLANE_SCLCHECK_SCALER_V2_CHECK))
		return 0;

	pd_cfg = &pstate->pre_down;
	pre_down_en = _sde_plane_is_pre_downscale_enabled(pd_cfg);

	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_INVALID;

	for (i = 0; i < SDE_MAX_PLANES; i++) {
		uint32_t hor_req_pixels, hor_fetch_pixels;
		uint32_t vert_req_pixels, vert_fetch_pixels;
		uint32_t src_w_tmp, src_h_tmp;
		uint32_t scaler_w, scaler_h;
		uint32_t pre_down_ratio_x = 1, pre_down_ratio_y = 1;
		bool rot;

		/* re-use color plane 1's config for plane 2 */
		if (i == 2)
			continue;

		if (pre_down_en) {
			if (i == 0 && pd_cfg->pre_downscale_x_0)
				pre_down_ratio_x = pd_cfg->pre_downscale_x_0;
			if (i == 0 && pd_cfg->pre_downscale_y_0)
				pre_down_ratio_y = pd_cfg->pre_downscale_y_0;
			if ((i == 1 || i == 2) && pd_cfg->pre_downscale_x_1)
				pre_down_ratio_x = pd_cfg->pre_downscale_x_1;
			if ((i == 1 || i == 2) && pd_cfg->pre_downscale_y_1)
				pre_down_ratio_y = pd_cfg->pre_downscale_y_1;
			SDE_DEBUG_PLANE(psde, "pre_down[%d]: x:%d, y:%d\n",
					i, pre_down_ratio_x, pre_down_ratio_y);
		}

		src_w_tmp = src_w;
		src_h_tmp = src_h;

		/*
		 * For chroma plane, width is half for the following sub sampled
		 * formats. Except in case of decimation, where hardware avoids
		 * 1 line of decimation instead of downsampling.
		 */
		if (i == 1) {
			if (!deci_w &&
					(fmt->chroma_sample == SDE_CHROMA_420 ||
					 fmt->chroma_sample == SDE_CHROMA_H2V1))
				src_w_tmp >>= 1;
			if (!deci_h &&
					(fmt->chroma_sample == SDE_CHROMA_420 ||
					 fmt->chroma_sample == SDE_CHROMA_H1V2))
				src_h_tmp >>= 1;
		}

		hor_req_pixels = pstate->pixel_ext.roi_w[i];
		vert_req_pixels = pstate->pixel_ext.roi_h[i];

		hor_fetch_pixels = DECIMATED_DIMENSION(src_w_tmp +
			(int8_t)(pstate->pixel_ext.left_ftch[i] & 0xFF) +
			(int8_t)(pstate->pixel_ext.right_ftch[i] & 0xFF),
			deci_w);
		vert_fetch_pixels = DECIMATED_DIMENSION(src_h_tmp +
			(int8_t)(pstate->pixel_ext.top_ftch[i] & 0xFF) +
			(int8_t)(pstate->pixel_ext.btm_ftch[i] & 0xFF),
			deci_h);

		if ((hor_req_pixels != hor_fetch_pixels) ||
			(hor_fetch_pixels > img_w) ||
			(vert_req_pixels != vert_fetch_pixels) ||
			(vert_fetch_pixels > img_h)) {
			SDE_ERROR_PLANE(psde,
					"req %d/%d, fetch %d/%d, src %dx%d\n",
					hor_req_pixels, vert_req_pixels,
					hor_fetch_pixels, vert_fetch_pixels,
					img_w, img_h);
			return -EINVAL;
		}

		/*
		 * swap the scaler src width & height for inline-rotation 90
		 * comparison with Pixel-Extension, as PE is based on
		 * pre-rotation and QSEED is based on post-rotation
		 */
		rot = pstate->rotation & DRM_MODE_ROTATE_90;
		scaler_w = rot ? pstate->scaler3_cfg.src_height[i]
				    : pstate->scaler3_cfg.src_width[i];
		scaler_h = rot ? pstate->scaler3_cfg.src_width[i]
				    : pstate->scaler3_cfg.src_height[i];
		/*
		 * Alpha plane can only be scaled using bilinear or pixel
		 * repeat/drop, src_width and src_height are only specified
		 * for Y and UV plane
		 */
		if (i != 3 && (hor_req_pixels / pre_down_ratio_x != scaler_w ||
					vert_req_pixels / pre_down_ratio_y !=
					scaler_h)) {
			SDE_ERROR_PLANE(psde,
			"roi[%d] roi:%dx%d scaler:%dx%d src:%dx%d rot:%d pd:%d/%d\n",
				i, pstate->pixel_ext.roi_w[i],
				pstate->pixel_ext.roi_h[i], scaler_w, scaler_h,
				src_w, src_h, rot, pre_down_ratio_x, pre_down_ratio_y);
			return -EINVAL;
		}

		/*
		 * SSPP fetch , unpack output and QSEED3 input lines need
		 * to match for Y plane
		 */
		if (i == 0 &&
			(sde_plane_get_property(pstate, PLANE_PROP_SRC_CONFIG) &
			BIT(SDE_DRM_DEINTERLACE)) &&
			((pstate->scaler3_cfg.src_height[i] != (src_h/2)) ||
			(pstate->pixel_ext.roi_h[i] != (src_h/2)))) {
			SDE_ERROR_PLANE(psde,
				"de-interlace fail roi[%d] %d/%d, src %dx%d, src %dx%d\n",
				i, pstate->pixel_ext.roi_w[i],
				pstate->pixel_ext.roi_h[i],
				pstate->scaler3_cfg.src_width[i],
				pstate->scaler3_cfg.src_height[i],
				src_w, src_h);
			return -EINVAL;
		}
	}

	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_SCALER_V2;
	return 0;
}

static inline bool _sde_plane_has_pre_downscale(struct sde_plane *psde)
{
	return (psde->features & BIT(SDE_SSPP_PREDOWNSCALE));
}

static int _sde_atomic_check_pre_downscale(struct sde_plane *psde,
		struct sde_plane_state *pstate, struct sde_rect *dst,
		u32 src_w, u32 src_h)
{
	int ret = 0;
	u32 min_ratio_numer, min_ratio_denom;
	struct sde_hw_inline_pre_downscale_cfg *pd_cfg = &pstate->pre_down;
	bool pd_x;
	bool pd_y;

	if (!_sde_plane_is_pre_downscale_enabled(pd_cfg))
		return ret;

	pd_x = pd_cfg->pre_downscale_x_0 > 1;
	pd_y = pd_cfg->pre_downscale_y_0 > 1;

	min_ratio_numer = psde->pipe_sblk->in_rot_maxdwnscale_rt_nopd_num;
	min_ratio_denom = psde->pipe_sblk->in_rot_maxdwnscale_rt_nopd_denom;

	if (pd_x && !_sde_plane_has_pre_downscale(psde)) {
		SDE_ERROR_PLANE(psde,
			"hw does not support pre-downscale X: 0x%x\n",
			psde->features);
		ret = -EINVAL;
	} else if (pd_y && !(psde->features & BIT(SDE_SSPP_PREDOWNSCALE_Y))) {
		SDE_ERROR_PLANE(psde,
			"hw does not support pre-downscale Y: 0x%x\n",
			psde->features);
		ret = -EINVAL;
	} else if (!min_ratio_numer || !min_ratio_denom) {
		SDE_ERROR_PLANE(psde,
			"min downscale ratio not set! %u / %u\n",
			min_ratio_numer, min_ratio_denom);
		ret = -EINVAL;
	/* compare pre-rotated src w/h with post-rotated dst h/w resp. */
	} else if (pd_x && (src_w < mult_frac(dst->h, min_ratio_numer,
			min_ratio_denom))) {
		SDE_ERROR_PLANE(psde,
			"failed min downscale-x check %u->%u, %u/%u\n",
			src_w, dst->h, min_ratio_numer, min_ratio_denom);
		ret = -EINVAL;
	} else if (pd_y && (src_h < mult_frac(dst->w, min_ratio_numer,
			min_ratio_denom))) {
		SDE_ERROR_PLANE(psde,
			"failed min downscale-y check %u->%u, %u/%u\n",
			src_h, dst->w, min_ratio_numer, min_ratio_denom);
		ret = -EINVAL;
	}

	return ret;
}

static void _sde_plane_get_max_downscale_limits(struct sde_plane *psde,
		struct sde_plane_state *pstate, bool rt_client, u32 dst_h,
		u32 src_h, u32 *max_numer_w, u32 *max_denom_w,
		u32 *max_numer_h, u32 *max_denom_h)
{
	bool rotated, has_predown, default_scale;
	const struct sde_sspp_sub_blks *sblk;
	struct sde_hw_inline_pre_downscale_cfg *pd = NULL;

	rotated = pstate->rotation & DRM_MODE_ROTATE_90;
	sblk = psde->pipe_sblk;
	*max_numer_w = sblk->maxdwnscale;
	*max_denom_w = 1;
	*max_numer_h = sblk->maxdwnscale;
	*max_denom_h = 1;

	has_predown = _sde_plane_has_pre_downscale(psde);
	if (has_predown)
		pd = &pstate->pre_down;

	default_scale = psde->debugfs_default_scale ||
	    (pstate->scaler_check_state != SDE_PLANE_SCLCHECK_SCALER_V2 &&
	    pstate->scaler_check_state != SDE_PLANE_SCLCHECK_SCALER_V2_CHECK);

	/**
	 * Inline rotation has different max vertical downscaling limits since
	 * the source-width becomes the scaler's pre-downscaled source-height.
	 **/
	if (rotated) {
		if (pd != NULL && rt_client && has_predown) {
			if (default_scale)
				pd->pre_downscale_x_0 = (src_h >
					mult_frac(dst_h, 11, 5)) ? 2 : 0;
			*max_numer_h = pd->pre_downscale_x_0 ?
				sblk->in_rot_maxdwnscale_rt_num :
				sblk->in_rot_maxdwnscale_rt_nopd_num;
			*max_denom_h = pd->pre_downscale_x_0 ?
				sblk->in_rot_maxdwnscale_rt_denom :
				sblk->in_rot_maxdwnscale_rt_nopd_denom;
		} else if (rt_client) {
			*max_numer_h = sblk->in_rot_maxdwnscale_rt_num;
			*max_denom_h = sblk->in_rot_maxdwnscale_rt_denom;
		} else {
			*max_numer_h = sblk->in_rot_maxdwnscale_nrt;
		}
	}
}

static int _sde_atomic_check_decimation_scaler(struct drm_plane_state *state,
	struct sde_plane *psde, const struct sde_format *fmt,
	struct sde_plane_state *pstate, struct sde_rect *src,
	struct sde_rect *dst, u32 width, u32 height)
{
	int ret = 0;
	uint32_t deci_w, deci_h, src_deci_w, src_deci_h;
	uint32_t scaler_src_w, scaler_src_h;
	uint32_t max_downscale_num_w, max_downscale_denom_w;
	uint32_t max_downscale_num_h, max_downscale_denom_h;
	uint32_t max_upscale, max_linewidth;
	bool inline_rotation, rt_client;
	struct drm_crtc *crtc;
	struct drm_crtc_state *new_cstate;
	const struct sde_sspp_sub_blks *sblk;

	if (!state || !state->state || !state->crtc) {
		SDE_ERROR_PLANE(psde, "invalid arguments\n");
		return -EINVAL;
	}

	deci_w = sde_plane_get_property(pstate, PLANE_PROP_H_DECIMATE);
	deci_h = sde_plane_get_property(pstate, PLANE_PROP_V_DECIMATE);

	src_deci_w = DECIMATED_DIMENSION(src->w, deci_w);
	src_deci_h = DECIMATED_DIMENSION(src->h, deci_h);

	/* with inline rotator, the source of the scaler is post-rotated */
	inline_rotation = pstate->rotation & DRM_MODE_ROTATE_90 ? true : false;
	if (inline_rotation) {
		scaler_src_w = src_deci_h;
		scaler_src_h = src_deci_w;
	} else {
		scaler_src_w = src_deci_w;
		scaler_src_h = src_deci_h;
	}
	sblk = psde->pipe_sblk;
	max_upscale = sblk->maxupscale;

	if (inline_rotation)
		max_linewidth = sblk->in_rot_maxheight;
	else if (scaler_src_w != state->crtc_w || scaler_src_h != state->crtc_h)
		max_linewidth = sblk->scaling_linewidth;
	else
		max_linewidth = sblk->maxlinewidth;

	crtc = state->crtc;
	new_cstate = drm_atomic_get_new_crtc_state(state->state, crtc);
	rt_client = sde_crtc_is_rt_client(crtc, new_cstate);

	_sde_plane_get_max_downscale_limits(psde, pstate, rt_client, dst->h,
		scaler_src_h, &max_downscale_num_w, &max_downscale_denom_w,
		&max_downscale_num_h, &max_downscale_denom_h);

	/* decimation validation */
	if ((deci_w || deci_h)
			&& ((deci_w > sblk->maxhdeciexp)
				|| (deci_h > sblk->maxvdeciexp))) {
		SDE_ERROR_PLANE(psde, "too much decimation requested\n");
		ret = -EINVAL;

	} else if ((deci_w || deci_h)
			&& (fmt->fetch_mode != SDE_FETCH_LINEAR)) {
		SDE_ERROR_PLANE(psde, "decimation requires linear fetch\n");
		ret = -EINVAL;

	} else if (!(psde->features & SDE_SSPP_SCALER) &&
		((src->w != dst->w) || (src->h != dst->h))) {
		SDE_ERROR_PLANE(psde,
			"pipe doesn't support scaling %ux%u->%ux%u\n",
			src->w, src->h, dst->w, dst->h);
		ret = -EINVAL;

	/* check scaler source width */
	} else if (scaler_src_w > max_linewidth) {
		SDE_ERROR_PLANE(psde,
			"invalid src w:%u, scaler w:%u, line w:%u, rot: %d\n",
			src->w, scaler_src_w, max_linewidth, inline_rotation);
		ret = -E2BIG;

	/* check max scaler capability */
	} else if (((scaler_src_w * max_upscale) < dst->w) ||
		((scaler_src_h * max_upscale) < dst->h) ||
		(mult_frac(dst->w, max_downscale_num_w, max_downscale_denom_w)
			< scaler_src_w) ||
		(mult_frac(dst->h, max_downscale_num_h, max_downscale_denom_h)
			< scaler_src_h)) {
		SDE_ERROR_PLANE(psde,
			"too much scaling %ux%u->%ux%u rot:%d dwn:%d/%d %d/%d\n",
			scaler_src_w, scaler_src_h, dst->w, dst->h,
			inline_rotation, max_downscale_num_w,
			max_downscale_denom_w, max_downscale_num_h,
			max_downscale_denom_h);
		ret = -E2BIG;

	/* check inline pre-downscale support */
	} else if (inline_rotation && _sde_atomic_check_pre_downscale(psde,
			pstate, dst, src_deci_w, src_deci_h)) {
		ret = -EINVAL;

	/* QSEED validation */
	} else if (_sde_plane_validate_scaler_v2(psde, pstate, fmt,
				width, height, src->w, src->h,
				deci_w, deci_h)) {
		ret = -EINVAL;
	}

	return ret;
}

static int _sde_atomic_check_excl_rect(struct sde_plane *psde,
	struct sde_plane_state *pstate, struct sde_rect *src,
	const struct sde_format *fmt, int ret)
{

	/* check excl rect configs */
	if (!ret && pstate->excl_rect.w && pstate->excl_rect.h) {
		struct sde_rect intersect;

		/*
		 * Check exclusion rect against src rect.
		 * it must intersect with source rect.
		 */
		sde_kms_rect_intersect(src, &pstate->excl_rect, &intersect);
		if (intersect.w != pstate->excl_rect.w ||
				intersect.h != pstate->excl_rect.h ||
				SDE_FORMAT_IS_YUV(fmt)) {
			SDE_ERROR_PLANE(psde,
				"invalid excl_rect:{%d,%d,%d,%d} src:{%d,%d,%d,%d}, fmt: %4.4s\n",
				pstate->excl_rect.x, pstate->excl_rect.y,
				pstate->excl_rect.w, pstate->excl_rect.h,
				src->x, src->y, src->w, src->h,
				(char *)&fmt->base.pixel_format);
			ret = -EINVAL;
		}
		SDE_DEBUG_PLANE(psde, "excl_rect: {%d,%d,%d,%d}\n",
				pstate->excl_rect.x, pstate->excl_rect.y,
				pstate->excl_rect.w, pstate->excl_rect.h);
	}

	return ret;
}


static int _sde_plane_validate_shared_crtc(struct sde_plane *psde,
				struct drm_plane_state *state)
{
	struct sde_kms *sde_kms;
	struct sde_splash_display *splash_display;
	int i;

	sde_kms = _sde_plane_get_kms(&psde->base);

	if (!sde_kms || !state->crtc)
		return 0;

	for (i = 0; i < MAX_DSI_DISPLAYS; i++) {
		splash_display = &sde_kms->splash_data.splash_display[i];

		if (splash_display && splash_display->cont_splash_enabled &&
				splash_display->encoder &&
				state->crtc != splash_display->encoder->crtc) {
			struct sde_sspp_index_info *pipe_info = &splash_display->pipe_info;

			if (test_bit(psde->pipe, pipe_info->pipes)  ||
					test_bit(psde->pipe, pipe_info->virt_pipes)) {
				SDE_ERROR_PLANE(psde, "pipe:%d used in cont-splash on crtc:%d\n",
						psde->pipe,
						splash_display->encoder->crtc->base.id);
				return -EINVAL;
			}
		}
	}

	return 0;

}

static int _sde_plane_sspp_atomic_check_helper(struct sde_plane *psde,
		const struct sde_format *fmt,
		struct sde_rect src, struct sde_rect dst,
		u32 width, u32 height)
{
	int ret = 0;
	u32 min_src_size = SDE_FORMAT_IS_YUV(fmt) ? 2 : 1;

	if (SDE_FORMAT_IS_YUV(fmt) &&
			(!(psde->features & SDE_SSPP_SCALER) ||
			 !(psde->features & (BIT(SDE_SSPP_CSC)
					     | BIT(SDE_SSPP_CSC_10BIT))))) {
		SDE_ERROR_PLANE(psde,
				"plane doesn't have scaler/csc for yuv\n");
		ret = -EINVAL;

	/* check src bounds */
	} else if (width > MAX_IMG_WIDTH || height > MAX_IMG_HEIGHT ||
			src.w < min_src_size || src.h < min_src_size ||
			CHECK_LAYER_BOUNDS(src.x, src.w, width) ||
			CHECK_LAYER_BOUNDS(src.y, src.h, height)) {
		SDE_ERROR_PLANE(psde, "invalid source %u, %u, %ux%u\n",
			src.x, src.y, src.w, src.h);
		ret = -E2BIG;

	/* valid yuv image */
	} else if (SDE_FORMAT_IS_YUV(fmt) && ((src.x & 0x1) || (src.y & 0x1) ||
			 (src.w & 0x1) || (src.h & 0x1))) {
		SDE_ERROR_PLANE(psde, "invalid yuv source %u, %u, %ux%u\n",
				src.x, src.y, src.w, src.h);
		ret = -EINVAL;

	/* min dst support */
	} else if (dst.w < 0x1 || dst.h < 0x1) {
		SDE_ERROR_PLANE(psde, "invalid dest rect %u, %u, %ux%u\n",
				dst.x, dst.y, dst.w, dst.h);
		ret = -EINVAL;
	} else if (SDE_FORMAT_IS_UBWC(fmt) &&
		!psde->catalog->ubwc_rev) {
		SDE_ERROR_PLANE(psde, "ubwc not supported\n");
		ret = -EINVAL;
	}

	return ret;
}

static int sde_plane_sspp_atomic_check(struct drm_plane *plane,
		struct drm_plane_state *state)
{
	int ret = 0;
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	const struct msm_format *msm_fmt;
	const struct sde_format *fmt;
	struct sde_rect src, dst;
	bool q16_data = true;
	struct drm_framebuffer *fb;
	u32 width;
	u32 height;

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(state);

	if (!psde->pipe_sblk) {
		SDE_ERROR_PLANE(psde, "invalid catalog\n");
		return -EINVAL;
	}

	/* src values are in Q16 fixed point, convert to integer */
	POPULATE_RECT(&src, state->src_x, state->src_y,
			state->src_w, state->src_h, q16_data);
	POPULATE_RECT(&dst, state->crtc_x, state->crtc_y, state->crtc_w,
		state->crtc_h, !q16_data);

	SDE_DEBUG_PLANE(psde, "check %d -> %d\n",
		sde_plane_enabled(plane->state), sde_plane_enabled(state));

	if (!sde_plane_enabled(state))
		goto modeset_update;

	fb = state->fb;
	width = fb ? state->fb->width : 0x0;
	height = fb ? state->fb->height : 0x0;

	SDE_DEBUG("plane%d sspp:%x/%dx%d/%4.4s/%llx\n",
			plane->base.id,
			pstate->rotation,
			width, height,
			fb ? (char *) &state->fb->format->format : 0x0,
			fb ? state->fb->modifier : 0x0);
	SDE_DEBUG("src:%dx%d %d,%d crtc:%dx%d+%d+%d\n",
			state->src_w >> 16, state->src_h >> 16,
			state->src_x >> 16, state->src_y >> 16,
			state->crtc_w, state->crtc_h,
			state->crtc_x, state->crtc_y);

	msm_fmt = msm_framebuffer_format(fb);
	fmt = to_sde_format(msm_fmt);

	ret = _sde_plane_sspp_atomic_check_helper(psde, fmt, src, dst, width,
			height);
	if (ret)
		return ret;

	ret = _sde_atomic_check_decimation_scaler(state, psde, fmt, pstate,
		&src, &dst, width, height);
	if (ret)
		return ret;

	ret = _sde_atomic_check_excl_rect(psde, pstate,
		&src, fmt, ret);
	if (ret)
		return ret;

	ret = _sde_plane_validate_shared_crtc(psde, state);
	if (ret)
		return ret;

	pstate->const_alpha_en = fmt->alpha_enable &&
		(SDE_DRM_BLEND_OP_OPAQUE !=
		 sde_plane_get_property(pstate, PLANE_PROP_BLEND_OP)) &&
		(pstate->stage != SDE_STAGE_0);

modeset_update:
	if (!ret)
		_sde_plane_sspp_atomic_check_mode_changed(psde,
				state, plane->state);
	return ret;
}

static int _sde_plane_atomic_check(struct drm_plane *plane,
		struct drm_plane_state *state)
{
	int ret = 0;
	struct sde_plane *psde;
	struct sde_plane_state *pstate;

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(state);

	SDE_DEBUG_PLANE(psde, "\n");

	ret = sde_plane_rot_atomic_check(plane, state);
	if (ret)
		goto exit;

	ret = sde_plane_sspp_atomic_check(plane, state);

exit:
	return ret;
}


#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
static int sde_plane_atomic_check(struct drm_plane *plane,
		struct drm_atomic_state *atomic_state)
{
	struct drm_plane_state *state = NULL;

	if (!plane || !atomic_state) {
		SDE_ERROR("invalid arg(s), plane %d atomic_state %d\n",
				!plane, !atomic_state);
		return -EINVAL;
	}
	state = drm_atomic_get_new_plane_state(atomic_state, plane);
	return _sde_plane_atomic_check(plane, state);
}
#else
static int sde_plane_atomic_check(struct drm_plane *plane,
		struct drm_plane_state *state)
{
	if (!plane || !state) {
		SDE_ERROR("invalid arg(s), plane %d state %d\n",
				!plane, !state);
		return -EINVAL;
	}

	return _sde_plane_atomic_check(plane, state);
}
#endif

void sde_plane_flush(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;

	if (!plane || !plane->state) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);

	/*
	 * These updates have to be done immediately before the plane flush
	 * timing, and may not be moved to the atomic_update/mode_set functions.
	 */
	if (psde->is_error)
		/* force white frame with 100% alpha pipe output on error */
		_sde_plane_color_fill(psde, 0xFFFFFF, 0xFF);
	else if (psde->color_fill & SDE_PLANE_COLOR_FILL_FLAG)
		/* force 100% alpha */
		_sde_plane_color_fill(psde, psde->color_fill, 0xFF);
	else if (psde->pipe_hw && psde->csc_ptr && psde->pipe_hw->ops.setup_csc)
		psde->pipe_hw->ops.setup_csc(psde->pipe_hw, psde->csc_ptr);

	/* flag h/w flush complete */
	if (plane->state)
		pstate->pending = false;
}

/**
 * sde_plane_set_error: enable/disable error condition
 * @plane: pointer to drm_plane structure
 */
void sde_plane_set_error(struct drm_plane *plane, bool error)
{
	struct sde_plane *psde;

	if (!plane)
		return;

	psde = to_sde_plane(plane);
	psde->is_error = error;
}

static void _sde_plane_sspp_setup_sys_cache(struct sde_plane *psde,
		struct sde_plane_state *pstate)
{
	struct drm_plane_state *state = psde->base.state;
	struct sde_sc_cfg *sc_cfg = psde->catalog->sc_cfg;
	struct sde_hw_pipe_sc_cfg *cfg = &pstate->sc_cfg;
	bool prev_rd_en = cfg->rd_en;
	u32 cache_flag, cache_rd_type, cache_wr_type;
	enum sde_sys_cache_state cache_state;

	if (!state->fb) {
		SDE_ERROR("invalid fb on plane %d\n", DRMID(&psde->base));
		return;
	}

	cache_state = pstate->static_cache_state;
	msm_framebuffer_get_cache_hint(state->fb, &cache_flag, &cache_rd_type, &cache_wr_type);

	cfg->rd_en = false;
	cfg->rd_scid = 0x0;
	cfg->flags = SYS_CACHE_EN_FLAG | SYS_CACHE_SCID;

	/*
	 * if condition handles static display legacy path, where internal state machine is
	 * transitioning the "cache_state" variable to program the LLCC cache through
	 * SSPP hardware using SDE_SYS_CACHE_DISP SCID.
	 * else condition handles static display and IWE path, were the frame is programmed to
	 * LLCC cache through WB/CWB path and read back by SSPP hardware. The FB cache hints are
	 * used to pass information on which SCID to use during read path and LLCC cache to
	 * keep active.
	 */
	if (test_bit(SDE_SYS_CACHE_DISP, psde->catalog->sde_sys_cache_type_map)
			&& ((cache_state == CACHE_STATE_FRAME_WRITE)
				|| (cache_state == CACHE_STATE_FRAME_READ))) {
		cfg->type = pstate->static_cache_type;
		cfg->rd_en = true;
		cfg->rd_scid = sc_cfg[cfg->type].llcc_scid;
		if (test_bit(SDE_FEATURE_SYS_CACHE_NSE, psde->catalog->features)) {
			cfg->rd_noallocate = false;
			pstate->static_cache_state = CACHE_STATE_NORMAL;
		} else {
			cfg->rd_noallocate = (cache_state == CACHE_STATE_FRAME_READ);
		}
		cfg->flags |= SYS_CACHE_NO_ALLOC;
	} else if (test_bit(cache_rd_type, psde->catalog->sde_sys_cache_type_map) && cache_flag) {
		cfg->rd_en = true;
		cfg->type = cache_rd_type;
		cfg->rd_scid = sc_cfg[cache_rd_type].llcc_scid;
		cfg->rd_noallocate = false;
		cfg->flags |= SYS_CACHE_NO_ALLOC;
		cache_flag = MSM_FB_CACHE_READ_EN;

		msm_framebuffer_set_cache_hint(state->fb, cache_flag, cache_rd_type, cache_wr_type);
	}

	if (!cfg->rd_en && !prev_rd_en)
		return;

	SDE_EVT32(DRMID(&psde->base), cfg->type, cfg->rd_scid, cfg->rd_en, cfg->rd_noallocate,
			cfg->flags, cache_state, cache_flag, cache_rd_type, cache_wr_type,
			state->fb->base.id);
	psde->pipe_hw->ops.setup_sys_cache(psde->pipe_hw, cfg);
}

void sde_plane_static_img_control(struct drm_plane *plane,
		enum sde_sys_cache_state state, enum sde_sys_cache_type type)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;

	if (!plane || !plane->state) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);

	pstate->static_cache_state = state;
	pstate->static_cache_type = type;

	if (state == CACHE_STATE_FRAME_WRITE || state == CACHE_STATE_FRAME_READ)
		_sde_plane_sspp_setup_sys_cache(psde, pstate);
}

static void _sde_plane_map_prop_to_dirty_bits(void)
{
	plane_prop_array[PLANE_PROP_SCALER_V1] =
	plane_prop_array[PLANE_PROP_SCALER_V2] =
	plane_prop_array[PLANE_PROP_SCALER_LUT_ED] =
	plane_prop_array[PLANE_PROP_SCALER_LUT_CIR] =
	plane_prop_array[PLANE_PROP_SCALER_LUT_SEP] =
	plane_prop_array[PLANE_PROP_H_DECIMATE] =
	plane_prop_array[PLANE_PROP_V_DECIMATE] =
	plane_prop_array[PLANE_PROP_SRC_CONFIG] =
	plane_prop_array[PLANE_PROP_ZPOS] =
	plane_prop_array[PLANE_PROP_EXCL_RECT_V1] =
	plane_prop_array[PLANE_PROP_UBWC_STATS_ROI] =
		SDE_PLANE_DIRTY_RECTS;

	plane_prop_array[PLANE_PROP_CSC_V1] =
	plane_prop_array[PLANE_PROP_CSC_DMA_V1] =
	plane_prop_array[PLANE_PROP_INVERSE_PMA] =
		SDE_PLANE_DIRTY_FORMAT;

	plane_prop_array[PLANE_PROP_MULTIRECT_MODE] =
	plane_prop_array[PLANE_PROP_COLOR_FILL] =
		SDE_PLANE_DIRTY_ALL;

	/* no special action required */
	plane_prop_array[PLANE_PROP_INFO] =
	plane_prop_array[PLANE_PROP_ALPHA] =
	plane_prop_array[PLANE_PROP_INPUT_FENCE] =
	plane_prop_array[PLANE_PROP_BLEND_OP] = 0;

	plane_prop_array[PLANE_PROP_FB_TRANSLATION_MODE] =
		SDE_PLANE_DIRTY_FB_TRANSLATION_MODE;
	plane_prop_array[PLANE_PROP_PREFILL_SIZE] =
	plane_prop_array[PLANE_PROP_PREFILL_TIME] =
		SDE_PLANE_DIRTY_PERF;

	plane_prop_array[PLANE_PROP_VIG_GAMUT] = SDE_PLANE_DIRTY_VIG_GAMUT;
	plane_prop_array[PLANE_PROP_VIG_IGC] = SDE_PLANE_DIRTY_VIG_IGC;
	plane_prop_array[PLANE_PROP_DMA_IGC] = SDE_PLANE_DIRTY_DMA_IGC;
	plane_prop_array[PLANE_PROP_DMA_GC] = SDE_PLANE_DIRTY_DMA_GC;

	plane_prop_array[PLANE_PROP_SKIN_COLOR] =
	plane_prop_array[PLANE_PROP_SKY_COLOR] =
	plane_prop_array[PLANE_PROP_FOLIAGE_COLOR] =
	plane_prop_array[PLANE_PROP_HUE_ADJUST] =
	plane_prop_array[PLANE_PROP_SATURATION_ADJUST] =
	plane_prop_array[PLANE_PROP_VALUE_ADJUST] =
	plane_prop_array[PLANE_PROP_CONTRAST_ADJUST] =
		SDE_PLANE_DIRTY_ALL;

	plane_prop_array[PLANE_PROP_FP16_IGC] = SDE_PLANE_DIRTY_FP16_IGC;
	plane_prop_array[PLANE_PROP_FP16_GC] = SDE_PLANE_DIRTY_FP16_GC;
	plane_prop_array[PLANE_PROP_FP16_CSC] = SDE_PLANE_DIRTY_FP16_CSC;
	plane_prop_array[PLANE_PROP_FP16_UNMULT] = SDE_PLANE_DIRTY_FP16_UNMULT;
}

static inline bool _sde_plane_allow_uidle(struct sde_plane *psde,
	struct sde_rect *src, struct sde_rect *dst)
{
	u32 max_downscale = psde->catalog->uidle_cfg.max_dwnscale;
	u32 downscale = (src->h * 1000)/dst->h;

	return (downscale > max_downscale) ? false : true;
}

static void _sde_plane_setup_uidle(struct drm_crtc *crtc,
	struct sde_plane *psde, struct sde_plane_state *pstate,
	struct sde_rect *src, struct sde_rect *dst)
{
	struct sde_hw_pipe_uidle_cfg cfg;

	u32 line_time = sde_crtc_get_line_time(crtc);
	u32 fal1_target_idle_time_ns =
		psde->catalog->uidle_cfg.fal1_target_idle_time * 1000; /* nS */
	u32 fal10_target_idle_time_ns =
		psde->catalog->uidle_cfg.fal10_target_idle_time * 1000; /* nS */
	u32 fal10_threshold =
		psde->catalog->uidle_cfg.fal10_threshold; /* uS */

	if (line_time && fal10_threshold && fal10_target_idle_time_ns &&
		fal1_target_idle_time_ns) {
		cfg.enable = _sde_plane_allow_uidle(psde, src, dst);
		cfg.fal10_threshold = fal10_threshold;
		cfg.fal10_exit_threshold = fal10_threshold + 2;
		cfg.fal1_threshold = min(1 +
			(fal1_target_idle_time_ns*1000/line_time*2)/1000,
			psde->catalog->uidle_cfg.fal1_max_threshold);
		cfg.fal_allowed_threshold = fal10_threshold +
			(fal10_target_idle_time_ns*1000/line_time*2)/1000;
		cfg.fill_level_scale = 0;

		/*
		 * if uidle fill scale is supported, determing the scale value
		 * and adjust fal10 thresholds to their scaled values.
		 * fal1 thresholds and fal_allowed are not scaled.
		 */
		if (psde->pipe_hw->ops.setup_uidle_fill_scale) {
			u32 fl_require0 = psde->catalog->qos_target_time_ns / line_time * 2;
			u32 fl_require = max(fal10_threshold * 1000, fl_require0);
			u32 fl_scale = fl_require / fal10_threshold;
			u32 fal10_threshold_noscale;

			cfg.fill_level_scale = (fl_scale <= 1) ? 0 : (32 / fl_scale);

			if (cfg.fill_level_scale) {
				fal10_threshold_noscale = fal10_threshold *
					32/cfg.fill_level_scale;
				cfg.fal_allowed_threshold = fal10_threshold_noscale +
					(fal10_target_idle_time_ns * 1000 / line_time * 2) / 1000;
			}
		}
	} else {
		SDE_ERROR("invalid settings, will disable UIDLE %d %d %d %d\n",
			line_time, fal10_threshold, fal10_target_idle_time_ns,
			fal1_target_idle_time_ns);
		memset(&cfg, 0, sizeof(struct sde_hw_pipe_uidle_cfg));
	}

	SDE_DEBUG_PLANE(psde,
		"tholds: fal10=%d fal10_exit=%d fal1=%d fal_allowed=%d fill_scale=%d\n",
			cfg.fal10_threshold, cfg.fal10_exit_threshold,
			cfg.fal1_threshold, cfg.fal_allowed_threshold,
			cfg.fill_level_scale);
	SDE_DEBUG_PLANE(psde,
		"times: line:%d fal1_idle:%d fal10_idle:%d dwnscale:%d\n",
			line_time, fal1_target_idle_time_ns,
			fal10_target_idle_time_ns,
			psde->catalog->uidle_cfg.max_dwnscale);
	SDE_EVT32_VERBOSE(cfg.enable,
		cfg.fal10_threshold, cfg.fal10_exit_threshold,
		cfg.fal1_threshold, cfg.fal_allowed_threshold,
		cfg.fill_level_scale, psde->catalog->uidle_cfg.max_dwnscale);

	if (psde->pipe_hw->ops.setup_uidle_fill_scale)
		psde->pipe_hw->ops.setup_uidle_fill_scale(psde->pipe_hw, &cfg);

	psde->pipe_hw->ops.setup_uidle(
		psde->pipe_hw, &cfg,
		pstate->multirect_index);
}

static void _sde_plane_update_secure_session(struct sde_plane *psde,
	struct sde_plane_state *pstate)
{
	bool enable = false;
	int mode = sde_plane_get_property(pstate,
			PLANE_PROP_FB_TRANSLATION_MODE);

	if ((mode == SDE_DRM_FB_SEC) ||
			(mode == SDE_DRM_FB_SEC_DIR_TRANS))
		enable = true;

	/* update secure session flag */
	psde->pipe_hw->ops.setup_secure_address(psde->pipe_hw,
			pstate->multirect_index,
			enable);
}

static void _sde_plane_update_roi_config(struct drm_plane *plane,
	struct drm_crtc *crtc, struct drm_framebuffer *fb)
{
	const struct sde_format *fmt;
	const struct msm_format *msm_fmt;
	struct sde_plane *psde;
	struct drm_plane_state *state;
	struct sde_plane_state *pstate;
	struct sde_rect src, dst;
	const struct sde_rect *crtc_roi;
	bool q16_data = true;
	int idx;

	psde = to_sde_plane(plane);
	state = plane->state;

	pstate = to_sde_plane_state(state);

	msm_fmt = msm_framebuffer_format(fb);
	if (!msm_fmt) {
		SDE_ERROR("crtc%d plane%d: null format\n",
			DRMID(crtc), DRMID(plane));
		return;
	}

	fmt = to_sde_format(msm_fmt);

	POPULATE_RECT(&src, state->src_x, state->src_y,
		state->src_w, state->src_h, q16_data);
	POPULATE_RECT(&dst, state->crtc_x, state->crtc_y,
		state->crtc_w, state->crtc_h, !q16_data);

	SDE_DEBUG_PLANE(psde,
		"FB[%u] %u,%u,%ux%u->crtc%u %d,%d,%ux%u, %4.4s ubwc %d\n",
			fb->base.id, src.x, src.y, src.w, src.h,
			crtc->base.id, dst.x, dst.y, dst.w, dst.h,
			(char *)&fmt->base.pixel_format,
			SDE_FORMAT_IS_UBWC(fmt));

	if (sde_plane_get_property(pstate, PLANE_PROP_SRC_CONFIG) &
		BIT(SDE_DRM_DEINTERLACE)) {
		SDE_DEBUG_PLANE(psde, "deinterlace\n");
		for (idx = 0; idx < SDE_MAX_PLANES; ++idx)
			psde->pipe_cfg.layout.plane_pitch[idx] <<= 1;
		src.h /= 2;
		src.y  = DIV_ROUND_UP(src.y, 2);
		src.y &= ~0x1;
	}

	/*
	 * adjust layer mixer position of the sspp in the presence
	 * of a partial update to the active lm origin
	 */
	sde_crtc_get_crtc_roi(crtc->state, &crtc_roi);
	dst.x -= crtc_roi->x;
	dst.y -= crtc_roi->y;

	/* check for UIDLE */
	if (psde->pipe_hw->ops.setup_uidle)
		_sde_plane_setup_uidle(crtc, psde, pstate, &src, &dst);

	psde->pipe_cfg.src_rect = src;
	psde->pipe_cfg.dst_rect = dst;

	_sde_plane_setup_scaler(psde, pstate, fmt, false);

	_sde_plane_setup_panel_stacking(psde, pstate);

	/* check for color fill */
	psde->color_fill = (uint32_t)sde_plane_get_property(pstate,
			PLANE_PROP_COLOR_FILL);
	if (psde->color_fill & SDE_PLANE_COLOR_FILL_FLAG) {
		/* skip remaining processing on color fill */
		pstate->dirty = 0x0;
	} else if (psde->pipe_hw->ops.setup_rects) {
		psde->pipe_hw->ops.setup_rects(psde->pipe_hw,
				&psde->pipe_cfg,
				pstate->multirect_index);
	}

	if (psde->pipe_hw->ops.setup_pe &&
			(pstate->multirect_index != SDE_SSPP_RECT_1))
		psde->pipe_hw->ops.setup_pe(psde->pipe_hw,
				&psde->pixel_ext);

	/**
	 * when programmed in multirect mode, scalar block will be
	 * bypassed. Still we need to update alpha and bitwidth
	 * ONLY for RECT0
	 */
	if (psde->pipe_hw->ops.setup_scaler &&
			pstate->multirect_index != SDE_SSPP_RECT_1) {
		psde->pipe_hw->ctl = _sde_plane_get_hw_ctl(plane);
		psde->pipe_hw->ops.setup_scaler(psde->pipe_hw,
				&psde->pipe_cfg, &psde->pixel_ext,
				&psde->scaler3_cfg);
	}

	/* update excl rect */
	if (psde->pipe_hw->ops.setup_excl_rect)
		psde->pipe_hw->ops.setup_excl_rect(psde->pipe_hw,
				&pstate->excl_rect,
				pstate->multirect_index);

	/* enable multirect config of corresponding rect */
	if (psde->pipe_hw->ops.update_multirect)
		psde->pipe_hw->ops.update_multirect(
				psde->pipe_hw,
				true,
				pstate->multirect_index,
				pstate->multirect_mode);
	/* update line insertion */
	if (pstate->lineinsertion_feature && psde->pipe_hw->ops.setup_line_insertion)
		psde->pipe_hw->ops.setup_line_insertion(psde->pipe_hw,
				pstate->multirect_index,
				&pstate->line_insertion_cfg);
}

static void _sde_plane_update_format_and_rects(struct sde_plane *psde,
	struct sde_plane_state *pstate, const struct sde_format *fmt)
{
	uint32_t src_flags = 0;

	SDE_DEBUG_PLANE(psde, "rotation 0x%X\n", pstate->rotation);
	if (pstate->rotation & DRM_MODE_REFLECT_X)
		src_flags |= SDE_SSPP_FLIP_LR;
	if (pstate->rotation & DRM_MODE_REFLECT_Y)
		src_flags |= SDE_SSPP_FLIP_UD;
	if (pstate->rotation & DRM_MODE_ROTATE_90)
		src_flags |= SDE_SSPP_ROT_90;

	/* update format */
	psde->pipe_hw->ops.setup_format(psde->pipe_hw, fmt,
	   pstate->const_alpha_en, src_flags,
	   pstate->multirect_index);

	if (psde->pipe_hw->ops.setup_cdp) {
		struct sde_hw_pipe_cdp_cfg *cdp_cfg = &pstate->cdp_cfg;

		memset(cdp_cfg, 0, sizeof(struct sde_hw_pipe_cdp_cfg));

		cdp_cfg->enable = psde->catalog->perf.cdp_cfg
			   [SDE_PERF_CDP_USAGE_RT].rd_enable;
		cdp_cfg->ubwc_meta_enable =
			   SDE_FORMAT_IS_UBWC(fmt);
		cdp_cfg->tile_amortize_enable =
			   SDE_FORMAT_IS_UBWC(fmt) ||
			   SDE_FORMAT_IS_TILE(fmt);
		cdp_cfg->preload_ahead = SDE_WB_CDP_PRELOAD_AHEAD_64;

		psde->pipe_hw->ops.setup_cdp(psde->pipe_hw, cdp_cfg,
			   pstate->multirect_index);
	}

	_sde_plane_sspp_setup_sys_cache(psde, pstate);

	/* update csc */
	if (SDE_FORMAT_IS_YUV(fmt))
		_sde_plane_setup_csc(psde);
	else
		psde->csc_ptr = 0;

	if (psde->pipe_hw->ops.setup_inverse_pma) {
		uint32_t pma_mode = 0;

		if (fmt->alpha_enable)
			pma_mode = (uint32_t) sde_plane_get_property(
				pstate, PLANE_PROP_INVERSE_PMA);
		psde->pipe_hw->ops.setup_inverse_pma(psde->pipe_hw,
			pstate->multirect_index, pma_mode);
	}

	if (psde->pipe_hw->ops.setup_dgm_csc)
		psde->pipe_hw->ops.setup_dgm_csc(psde->pipe_hw,
			pstate->multirect_index, psde->csc_usr_ptr);

	if (psde->pipe_hw->ops.set_ubwc_stats_roi) {
		if (SDE_FORMAT_IS_UBWC(fmt) && !SDE_FORMAT_IS_YUV(fmt))
			psde->pipe_hw->ops.set_ubwc_stats_roi(psde->pipe_hw,
					pstate->multirect_index, &pstate->ubwc_stats_roi);
		else
			psde->pipe_hw->ops.set_ubwc_stats_roi(psde->pipe_hw,
					pstate->multirect_index, NULL);
	}
}

static void _sde_plane_update_sharpening(struct sde_plane *psde)
{
	psde->sharp_cfg.strength = SHARP_STRENGTH_DEFAULT;
	psde->sharp_cfg.edge_thr = SHARP_EDGE_THR_DEFAULT;
	psde->sharp_cfg.smooth_thr = SHARP_SMOOTH_THR_DEFAULT;
	psde->sharp_cfg.noise_thr = SHARP_NOISE_THR_DEFAULT;

	psde->pipe_hw->ops.setup_sharpening(psde->pipe_hw,
			&psde->sharp_cfg);
}

static void _sde_plane_update_properties(struct drm_plane *plane,
	struct drm_crtc *crtc, struct drm_framebuffer *fb)
{
	uint32_t nplanes;
	const struct msm_format *msm_fmt;
	const struct sde_format *fmt;
	struct sde_plane *psde;
	struct drm_plane_state *state;
	struct sde_plane_state *pstate;

	psde = to_sde_plane(plane);
	state = plane->state;

	pstate = to_sde_plane_state(state);
	if (!pstate) {
		SDE_ERROR("invalid plane state for plane%d\n", DRMID(plane));
		return;
	}

	msm_fmt = msm_framebuffer_format(fb);
	if (!msm_fmt) {
		SDE_ERROR("crtc%d plane%d: null format\n",
			DRMID(crtc), DRMID(plane));
		return;
	}

	fmt = to_sde_format(msm_fmt);
	nplanes = fmt->num_planes;

	/* update secure session flag */
	if (pstate->dirty & SDE_PLANE_DIRTY_FB_TRANSLATION_MODE)
		_sde_plane_update_secure_session(psde, pstate);

	/* update roi config */
	if (pstate->dirty & SDE_PLANE_DIRTY_RECTS)
		_sde_plane_update_roi_config(plane, crtc, fb);

	if ((pstate->dirty & SDE_PLANE_DIRTY_FORMAT ||
			pstate->dirty & SDE_PLANE_DIRTY_RECTS) &&
			psde->pipe_hw->ops.setup_format)
		_sde_plane_update_format_and_rects(psde, pstate, fmt);

	sde_color_process_plane_setup(plane);

	/* update sharpening */
	if ((pstate->dirty & SDE_PLANE_DIRTY_SHARPEN) &&
		psde->pipe_hw->ops.setup_sharpening)
		_sde_plane_update_sharpening(psde);

	if (pstate->dirty & (SDE_PLANE_DIRTY_QOS | SDE_PLANE_DIRTY_RECTS |
			SDE_PLANE_DIRTY_FORMAT))
		_sde_plane_set_qos_lut(plane, crtc, fb);

	_sde_plane_set_qos_ctrl(plane, true, SDE_PLANE_QOS_PANIC_CTRL);
	_sde_plane_set_ot_limit(plane, crtc);
	if (pstate->dirty & SDE_PLANE_DIRTY_PERF)
		_sde_plane_set_ts_prefill(plane, pstate);

	if (pstate->dirty & SDE_PLANE_DIRTY_QOS)
		_sde_plane_set_qos_remap(plane);

	/* clear dirty */
	pstate->dirty = 0x0;
}

static void _sde_plane_check_lut_dirty(struct sde_plane *psde,
	struct sde_plane_state *pstate)
{
	/**
	 * Valid configuration if scaler is not enabled or
	 * lut flag is set
	 */
	if (pstate->scaler3_cfg.lut_flag || !pstate->scaler3_cfg.enable)
		return;

	pstate->scaler3_cfg.lut_flag = psde->cached_lut_flag;
	SDE_EVT32(DRMID(&psde->base), pstate->scaler3_cfg.lut_flag, SDE_EVTLOG_ERROR);
}

static int sde_plane_sspp_atomic_update(struct drm_plane *plane,
				struct drm_plane_state *old_state)
{
	struct sde_plane *psde;
	struct drm_plane_state *state;
	struct sde_plane_state *pstate;
	struct sde_plane_state *old_pstate;
	struct drm_crtc *crtc;
	struct drm_framebuffer *fb;
	int idx;
	int dirty_prop_flag;
	bool is_rt;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return -EINVAL;
	} else if (!plane->state) {
		SDE_ERROR("invalid plane state\n");
		return -EINVAL;
	} else if (!old_state) {
		SDE_ERROR("invalid old state\n");
		return -EINVAL;
	}

	psde = to_sde_plane(plane);
	state = plane->state;

	pstate = to_sde_plane_state(state);

	old_pstate = to_sde_plane_state(old_state);

	crtc = state->crtc;
	fb = state->fb;
	if (!crtc || !fb) {
		SDE_ERROR_PLANE(psde, "invalid crtc %d or fb %d\n",
				!crtc, !fb);
		return -EINVAL;
	}

	SDE_DEBUG(
		"plane%d sspp:%dx%d/%4.4s/%llx/%dx%d+%d+%d/%x crtc:%dx%d+%d+%d\n",
			plane->base.id,
			state->fb->width, state->fb->height,
			(char *) &state->fb->format->format,
			state->fb->modifier,
			state->src_w >> 16, state->src_h >> 16,
			state->src_x >> 16, state->src_y >> 16,
			pstate->rotation,
			state->crtc_w, state->crtc_h,
			state->crtc_x, state->crtc_y);

	/* Caching the valid lut flag in sde plane */
	if (pstate->scaler3_cfg.enable && pstate->scaler3_cfg.lut_flag)
		psde->cached_lut_flag = pstate->scaler3_cfg.lut_flag;

	/* force reprogramming of all the parameters, if the flag is set */
	if (psde->revalidate) {
		SDE_DEBUG("plane:%d - reconfigure all the parameters\n",
				plane->base.id);
		_sde_plane_check_lut_dirty(psde, pstate);
		pstate->dirty = SDE_PLANE_DIRTY_ALL | SDE_PLANE_DIRTY_CP;
		psde->revalidate = false;
	}

	/* determine what needs to be refreshed */
	mutex_lock(&psde->property_info.property_lock);
	while ((idx = msm_property_pop_dirty(&psde->property_info,
				&pstate->property_state)) >= 0) {
		dirty_prop_flag = plane_prop_array[idx];
		pstate->dirty |= dirty_prop_flag;
	}
	mutex_unlock(&psde->property_info.property_lock);

	/**
	 * since plane_atomic_check is invoked before crtc_atomic_check
	 * in the commit sequence, all the parameters for updating the
	 * plane dirty flag will not be available during
	 * plane_atomic_check as some features params are updated
	 * in crtc_atomic_check (eg.:sDMA). So check for mode_change
	 * before sspp update.
	 */
	_sde_plane_sspp_atomic_check_mode_changed(psde, state,
								old_state);

	/* re-program the output rects always if partial update roi changed */
	if (sde_crtc_is_crtc_roi_dirty(crtc->state))
		pstate->dirty |= SDE_PLANE_DIRTY_RECTS;

	if (pstate->dirty & SDE_PLANE_DIRTY_RECTS)
		memset(&(psde->pipe_cfg), 0, sizeof(struct sde_hw_pipe_cfg));

	_sde_plane_set_scanout(plane, pstate, &psde->pipe_cfg, fb);

	is_rt = sde_crtc_is_rt_client(crtc, crtc->state);
	if (is_rt != psde->is_rt_pipe || crtc->state->mode_changed) {
		psde->is_rt_pipe = is_rt;
		psde->wb_usage_type = psde->is_rt_pipe ? 0 : sde_crtc_get_wb_usage_type(crtc);
		pstate->dirty |= SDE_PLANE_DIRTY_QOS;
	}

	/* early out if nothing dirty */
	if (!pstate->dirty)
		return 0;
	pstate->pending = true;

	_sde_plane_set_qos_ctrl(plane, false, SDE_PLANE_QOS_PANIC_CTRL);

	_sde_plane_update_properties(plane, crtc, fb);

	return 0;
}

static void _sde_plane_atomic_disable(struct drm_plane *plane,
				struct drm_plane_state *old_state)
{
	struct sde_plane *psde;
	struct drm_plane_state *state;
	struct sde_plane_state *pstate;
	u32 multirect_index = SDE_SSPP_RECT_0;
	struct sde_cp_crtc_skip_blend_plane skip_blend_plane;
	u32 blend_type;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return;
	} else if (!plane->state) {
		SDE_ERROR("invalid plane state\n");
		return;
	} else if (!old_state) {
		SDE_ERROR("invalid old state\n");
		return;
	}

	psde = to_sde_plane(plane);
	state = plane->state;
	pstate = to_sde_plane_state(state);

	blend_type = sde_plane_get_property(pstate,
					PLANE_PROP_BLEND_OP);
	/* some of the color features are dependent on plane with skip blend.
	 * if skip blend plane is being disabled, we need to disable color properties.
	*/
	if (blend_type == SDE_DRM_BLEND_OP_SKIP && old_state->crtc) {
		skip_blend_plane.valid_plane = false;
		skip_blend_plane.plane = SSPP_NONE;
		sde_cp_set_skip_blend_plane_info(old_state->crtc, &skip_blend_plane);
		sde_crtc_disable_cp_features(old_state->crtc);
	}

	SDE_EVT32(DRMID(plane), is_sde_plane_virtual(plane),
			pstate->multirect_mode);

	pstate->pending = true;
	pstate->static_cache_state = CACHE_STATE_DISABLED;

	if (is_sde_plane_virtual(plane))
		multirect_index = SDE_SSPP_RECT_1;

	/* disable multirect config of corresponding rect */
	if (psde->pipe_hw && psde->pipe_hw->ops.update_multirect)
		psde->pipe_hw->ops.update_multirect(psde->pipe_hw, false,
				multirect_index, SDE_SSPP_MULTIRECT_TIME_MX);
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
static void _sde_plane_atomic_update(struct drm_plane *plane,
				struct drm_plane_state *old_state)
#else
static void sde_plane_atomic_update(struct drm_plane *plane,
				struct drm_plane_state *old_state)
#endif
{
	struct sde_plane *psde;
	struct drm_plane_state *state;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return;
	} else if (!plane->state) {
		SDE_ERROR("invalid plane state\n");
		return;
	}

	psde = to_sde_plane(plane);
	psde->is_error = false;
	state = plane->state;

	SDE_DEBUG_PLANE(psde, "\n");

	if (!sde_plane_enabled(state)) {
		_sde_plane_atomic_disable(plane, old_state);
	} else {
		int ret;

		ret = sde_plane_sspp_atomic_update(plane, old_state);
		/* atomic_check should have ensured that this doesn't fail */
		WARN_ON(ret < 0);
	}
}

#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
static void sde_plane_atomic_update(struct drm_plane *plane,
				struct drm_atomic_state *atomic_state)
{
	struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(atomic_state, plane);

	_sde_plane_atomic_update(plane, old_state);
}
#endif

void sde_plane_restore(struct drm_plane *plane)
{
	struct sde_plane *psde;

	if (!plane || !plane->state) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);

	/*
	 * Revalidate is only true here if idle PC occurred and
	 * there is no plane state update in current commit cycle.
	 */
	if (!psde->revalidate)
		return;

	SDE_DEBUG_PLANE(psde, "\n");

	/* last plane state is same as current state */
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0))
	_sde_plane_atomic_update(plane, plane->state);
#else
	sde_plane_atomic_update(plane, plane->state);
#endif
}

bool sde_plane_is_cache_required(struct drm_plane *plane,
		enum sde_sys_cache_type type)
{
	struct sde_plane_state *pstate;
	u32 cache_flag, cache_rd_type, cache_wr_type;

	if (!plane || !plane->state) {
		SDE_ERROR("invalid plane\n");
		return false;
	}

	pstate = to_sde_plane_state(plane->state);
	msm_framebuffer_get_cache_hint(plane->state->fb, &cache_flag, &cache_rd_type,
			&cache_wr_type);

	/* check if llcc is required for the plane */
	if (pstate->sc_cfg.rd_en && ((pstate->sc_cfg.type == type)
			|| (cache_flag && (cache_rd_type == type))
			|| (cache_flag && (cache_wr_type == type)))) {
		SDE_EVT32_VERBOSE(DRMID(plane), type, pstate->sc_cfg.rd_en, pstate->sc_cfg.type,
				cache_flag, cache_rd_type, cache_wr_type,
				plane->state->fb->base.id);
		return true;
	}

	return false;
}

static void _sde_plane_install_master_only_properties(struct sde_plane *psde)
{
	char feature_name[256];

	if (psde->pipe_sblk->maxhdeciexp) {
		msm_property_install_range(&psde->property_info,
				"h_decimate", 0x0, 0,
				psde->pipe_sblk->maxhdeciexp, 0,
				PLANE_PROP_H_DECIMATE);
	}

	if (psde->pipe_sblk->maxvdeciexp) {
		msm_property_install_range(&psde->property_info,
				"v_decimate", 0x0, 0,
				psde->pipe_sblk->maxvdeciexp, 0,
				PLANE_PROP_V_DECIMATE);
	}

	if (psde->features & BIT(SDE_SSPP_SCALER_QSEED3)) {
		msm_property_install_range(
				&psde->property_info, "scaler_v2",
				0x0, 0, ~0, 0, PLANE_PROP_SCALER_V2);
		msm_property_install_blob(&psde->property_info,
				"lut_ed", 0, PLANE_PROP_SCALER_LUT_ED);
		msm_property_install_blob(&psde->property_info,
				"lut_cir", 0,
				PLANE_PROP_SCALER_LUT_CIR);
		msm_property_install_blob(&psde->property_info,
				"lut_sep", 0,
				PLANE_PROP_SCALER_LUT_SEP);
	} else if (psde->features & BIT(SDE_SSPP_SCALER_QSEED3LITE)) {
		msm_property_install_range(
				&psde->property_info, "scaler_v2",
				0x0, 0, ~0, 0, PLANE_PROP_SCALER_V2);
		msm_property_install_blob(&psde->property_info,
				"lut_sep", 0,
				PLANE_PROP_SCALER_LUT_SEP);
	} else if (psde->features & SDE_SSPP_SCALER) {
		msm_property_install_range(
				&psde->property_info, "scaler_v1", 0x0,
				0, ~0, 0, PLANE_PROP_SCALER_V1);
	}

	if (psde->features & BIT(SDE_SSPP_CSC) ||
		psde->features & BIT(SDE_SSPP_CSC_10BIT))
		msm_property_install_volatile_range(
				&psde->property_info, "csc_v1", 0x0,
				0, ~0, 0, PLANE_PROP_CSC_V1);

	if (psde->features & BIT(SDE_SSPP_HSIC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_HUE_V",
			psde->pipe_sblk->hsic_blk.version >> 16);
		msm_property_install_range(&psde->property_info,
			feature_name, 0, 0, 0xFFFFFFFF, 0,
			PLANE_PROP_HUE_ADJUST);
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_SATURATION_V",
			psde->pipe_sblk->hsic_blk.version >> 16);
		msm_property_install_range(&psde->property_info,
			feature_name, 0, 0, 0xFFFFFFFF, 0,
			PLANE_PROP_SATURATION_ADJUST);
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_VALUE_V",
			psde->pipe_sblk->hsic_blk.version >> 16);
		msm_property_install_range(&psde->property_info,
			feature_name, 0, 0, 0xFFFFFFFF, 0,
			PLANE_PROP_VALUE_ADJUST);
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_CONTRAST_V",
			psde->pipe_sblk->hsic_blk.version >> 16);
		msm_property_install_range(&psde->property_info,
			feature_name, 0, 0, 0xFFFFFFFF, 0,
			PLANE_PROP_CONTRAST_ADJUST);
	}

}

static void _sde_plane_install_colorproc_properties(struct sde_plane *psde,
		struct sde_kms_info *info)
{
	char feature_name[256];
	bool is_master = !psde->is_virtual;

	if ((is_master &&
		(psde->features & BIT(SDE_SSPP_INVERSE_PMA))) ||
		(psde->features & BIT(SDE_SSPP_DGM_INVERSE_PMA))) {
		msm_property_install_range(&psde->property_info,
			"inverse_pma", 0x0, 0, 1, 0, PLANE_PROP_INVERSE_PMA);
		sde_kms_info_add_keyint(info, "inverse_pma", 1);
	}

	if (psde->features & BIT(SDE_SSPP_DGM_CSC)) {
		msm_property_install_volatile_range(
			&psde->property_info, "csc_dma_v1", 0x0,
			0, ~0, 0, PLANE_PROP_CSC_DMA_V1);
		sde_kms_info_add_keyint(info, "csc_dma_v1", 1);
	}

	if (psde->features & BIT(SDE_SSPP_MEMCOLOR)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_SKIN_COLOR_V",
			psde->pipe_sblk->memcolor_blk.version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_SKIN_COLOR);
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_SKY_COLOR_V",
			psde->pipe_sblk->memcolor_blk.version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_SKY_COLOR);
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_FOLIAGE_COLOR_V",
			psde->pipe_sblk->memcolor_blk.version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_FOLIAGE_COLOR);
	}

	if (psde->features & BIT(SDE_SSPP_VIG_GAMUT)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_VIG_3D_LUT_GAMUT_V",
			psde->pipe_sblk->gamut_blk.version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_VIG_GAMUT);
	}

	if (psde->features & BIT(SDE_SSPP_VIG_IGC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_VIG_1D_LUT_IGC_V",
			psde->pipe_sblk->igc_blk[0].version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_VIG_IGC);
	}

	if (psde->features & BIT(SDE_SSPP_DMA_IGC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_DGM_1D_LUT_IGC_V",
			psde->pipe_sblk->igc_blk[0].version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_DMA_IGC);
	}

	if (psde->features & BIT(SDE_SSPP_DMA_GC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_DGM_1D_LUT_GC_V",
			psde->pipe_sblk->gc_blk[0].version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_DMA_GC);
	}

	if (psde->features & BIT(SDE_SSPP_FP16_IGC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_FP16_IGC_V",
			psde->pipe_sblk->fp16_igc_blk[0].version >> 16);
		msm_property_install_range(&psde->property_info, feature_name,
				0x0, 0, 1, 0, PLANE_PROP_FP16_IGC);
	}

	if (psde->features & BIT(SDE_SSPP_FP16_GC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_FP16_GC_V",
			psde->pipe_sblk->fp16_gc_blk[0].version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_FP16_GC);
	}

	if (psde->features & BIT(SDE_SSPP_FP16_CSC)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_FP16_CSC_V",
			psde->pipe_sblk->fp16_csc_blk[0].version >> 16);
		msm_property_install_blob(&psde->property_info, feature_name, 0,
			PLANE_PROP_FP16_CSC);
	}

	if (psde->features & BIT(SDE_SSPP_FP16_UNMULT)) {
		snprintf(feature_name, sizeof(feature_name), "%s%d",
			"SDE_SSPP_FP16_UNMULT_V",
			psde->pipe_sblk->fp16_unmult_blk[0].version >> 16);
		msm_property_install_range(&psde->property_info, feature_name,
			0x0, 0, 1, 0, PLANE_PROP_FP16_UNMULT);
	}
}

static void _sde_plane_setup_capabilities_blob(struct sde_plane *psde,
		u32 master_plane_id, struct sde_kms_info *info,
		struct sde_mdss_cfg *catalog)
{
	bool is_master = !psde->is_virtual;
	const struct sde_format_extended *format_list;
	u32 index;
	int pipe_id;

	if (is_master) {
		format_list = psde->pipe_sblk->format_list;
	} else {
		format_list = psde->pipe_sblk->virt_format_list;
		sde_kms_info_add_keyint(info, "primary_smart_plane_id",
				master_plane_id);
	}

	if (format_list) {
		sde_kms_info_start(info, "pixel_formats");
		while (format_list->fourcc_format) {
			sde_kms_info_append_format(info,
					format_list->fourcc_format,
					format_list->modifier);
			++format_list;
		}
		sde_kms_info_stop(info);
	}

	if (psde->pipe_hw && catalog->qseed_hw_rev)
		sde_kms_info_add_keyint(info, "scaler_step_ver", catalog->qseed_hw_rev);

	sde_kms_info_add_keyint(info, "max_linewidth",
			psde->pipe_sblk->maxlinewidth);
	sde_kms_info_add_keyint(info, "max_upscale",
			psde->pipe_sblk->maxupscale);
	sde_kms_info_add_keyint(info, "max_downscale",
			psde->pipe_sblk->maxdwnscale);
	sde_kms_info_add_keyint(info, "max_horizontal_deci",
			psde->pipe_sblk->maxhdeciexp);
	sde_kms_info_add_keyint(info, "max_vertical_deci",
			psde->pipe_sblk->maxvdeciexp);
	sde_kms_info_add_keyint(info, "max_per_pipe_bw",
			psde->pipe_sblk->max_per_pipe_bw * 1000LL);
	sde_kms_info_add_keyint(info, "max_per_pipe_bw_high",
			psde->pipe_sblk->max_per_pipe_bw_high * 1000LL);

	if (SDE_SSPP_VALID_VIG(psde->pipe))
		pipe_id = psde->pipe -  SSPP_VIG0;
	else if (SDE_SSPP_VALID_DMA(psde->pipe))
		pipe_id = psde->pipe -  SSPP_DMA0;
	else
		pipe_id = -1;

	sde_kms_info_add_keyint(info, "pipe_idx", pipe_id);

	index = (master_plane_id == 0) ? 0 : 1;
	if (test_bit(SDE_FEATURE_DEMURA, catalog->features) &&
	    catalog->demura_supported[psde->pipe][index] != ~0x0)
		sde_kms_info_add_keyint(info, "demura_block", index);

	if (psde->features & BIT(SDE_SSPP_SEC_UI_ALLOWED))
		sde_kms_info_add_keyint(info, "sec_ui_allowed", 1);
	if (psde->features & BIT(SDE_SSPP_BLOCK_SEC_UI))
		sde_kms_info_add_keyint(info, "block_sec_ui", 1);

	if (psde->features & BIT(SDE_SSPP_TRUE_INLINE_ROT)) {
		const struct sde_format_extended *inline_rot_fmt_list;

		sde_kms_info_add_keyint(info, "true_inline_rot_rev",
				catalog->true_inline_rot_rev);
		sde_kms_info_add_keyint(info,
			"true_inline_dwnscale_rt",
			(int) (psde->pipe_sblk->in_rot_maxdwnscale_rt_num /
				psde->pipe_sblk->in_rot_maxdwnscale_rt_denom));
		sde_kms_info_add_keyint(info,
				"true_inline_dwnscale_rt_numerator",
				psde->pipe_sblk->in_rot_maxdwnscale_rt_num);
		sde_kms_info_add_keyint(info,
				"true_inline_dwnscale_rt_denominator",
				psde->pipe_sblk->in_rot_maxdwnscale_rt_denom);
		sde_kms_info_add_keyint(info, "true_inline_dwnscale_nrt",
			psde->pipe_sblk->in_rot_maxdwnscale_nrt);
		sde_kms_info_add_keyint(info, "true_inline_max_height",
			psde->pipe_sblk->in_rot_maxheight);

		inline_rot_fmt_list = psde->pipe_sblk->in_rot_format_list;

		if (inline_rot_fmt_list) {
			sde_kms_info_start(info, "inline_rot_pixel_formats");
			while (inline_rot_fmt_list->fourcc_format) {
				sde_kms_info_append_format(info,
					inline_rot_fmt_list->fourcc_format,
					inline_rot_fmt_list->modifier);
				++inline_rot_fmt_list;
			}
			sde_kms_info_stop(info);
		}
	}
}

/* helper to install properties which are common to planes and crtcs */
static void _sde_plane_install_properties(struct drm_plane *plane,
	struct sde_mdss_cfg *catalog, u32 master_plane_id)
{
	static const struct drm_prop_enum_list e_blend_op[] = {
		{SDE_DRM_BLEND_OP_NOT_DEFINED,    "not_defined"},
		{SDE_DRM_BLEND_OP_OPAQUE,         "opaque"},
		{SDE_DRM_BLEND_OP_PREMULTIPLIED,  "premultiplied"},
		{SDE_DRM_BLEND_OP_COVERAGE,       "coverage"},
		{SDE_DRM_BLEND_OP_SKIP,           "skip_blending"},
	};
	static const struct drm_prop_enum_list e_src_config[] = {
		{SDE_DRM_DEINTERLACE, "deinterlace"}
	};
	static const struct drm_prop_enum_list e_fb_translation_mode[] = {
		{SDE_DRM_FB_NON_SEC, "non_sec"},
		{SDE_DRM_FB_SEC, "sec"},
		{SDE_DRM_FB_NON_SEC_DIR_TRANS, "non_sec_direct_translation"},
		{SDE_DRM_FB_SEC_DIR_TRANS, "sec_direct_translation"},
	};
	static const struct drm_prop_enum_list e_multirect_mode[] = {
		{SDE_SSPP_MULTIRECT_NONE, "none"},
		{SDE_SSPP_MULTIRECT_PARALLEL, "parallel"},
		{SDE_SSPP_MULTIRECT_TIME_MX,  "serial"},
	};
	struct sde_kms_info *info;
	struct sde_plane *psde = to_sde_plane(plane);
	bool is_master;
	int zpos_max = 255;
	int zpos_def = 0;

	if (!plane || !psde) {
		SDE_ERROR("invalid plane\n");
		return;
	} else if (!psde->pipe_hw || !psde->pipe_sblk) {
		SDE_ERROR("invalid plane, pipe_hw %d pipe_sblk %d\n",
				!psde->pipe_hw, !psde->pipe_sblk);
		return;
	} else if (!catalog) {
		SDE_ERROR("invalid catalog\n");
		return;
	}

	psde->catalog = catalog;
	is_master = !psde->is_virtual;

	info = vzalloc(sizeof(struct sde_kms_info));
	if (!info) {
		SDE_ERROR("failed to allocate info memory\n");
		return;
	}

	if (sde_is_custom_client()) {
		if (catalog->mixer_count &&
				catalog->mixer[0].sblk->maxblendstages) {
			zpos_max = catalog->mixer[0].sblk->maxblendstages - 1;
			if (test_bit(SDE_FEATURE_BASE_LAYER, catalog->features) &&
					(zpos_max > SDE_STAGE_MAX - 1))
				zpos_max = SDE_STAGE_MAX - 1;
			else if (zpos_max > SDE_STAGE_MAX - SDE_STAGE_0 - 1)
				zpos_max = SDE_STAGE_MAX - SDE_STAGE_0 - 1;
		}
	} else if (plane->type != DRM_PLANE_TYPE_PRIMARY) {
		/* reserve zpos == 0 for primary planes */
		zpos_def = drm_plane_index(plane) + 1;
	}

	msm_property_install_range(&psde->property_info, "zpos",
		0x0, 0, zpos_max, zpos_def, PLANE_PROP_ZPOS);

	msm_property_install_range(&psde->property_info, "alpha",
		0x0, 0, 255, 255, PLANE_PROP_ALPHA);

	/* linux default file descriptor range on each process */
	msm_property_install_range(&psde->property_info, "input_fence",
		0x0, 0, INR_OPEN_MAX, 0, PLANE_PROP_INPUT_FENCE);

	if (is_master)
		_sde_plane_install_master_only_properties(psde);
	else
		msm_property_install_enum(&psde->property_info,
				"multirect_mode", 0x0, 0, e_multirect_mode,
				ARRAY_SIZE(e_multirect_mode), 0,
				PLANE_PROP_MULTIRECT_MODE);

	if (psde->features & BIT(SDE_SSPP_EXCL_RECT))
		msm_property_install_volatile_range(&psde->property_info,
			"excl_rect_v1", 0x0, 0, ~0, 0, PLANE_PROP_EXCL_RECT_V1);

	sde_plane_rot_install_properties(plane, catalog);

	msm_property_install_enum(&psde->property_info, "blend_op", 0x0, 0,
		e_blend_op, ARRAY_SIZE(e_blend_op), 0, PLANE_PROP_BLEND_OP);

	msm_property_install_enum(&psde->property_info, "src_config", 0x0, 1,
		e_src_config, ARRAY_SIZE(e_src_config), 0,
		PLANE_PROP_SRC_CONFIG);

	if (psde->pipe_hw->ops.setup_solidfill)
		msm_property_install_range(&psde->property_info, "color_fill",
				0, 0, 0xFFFFFFFF, 0, PLANE_PROP_COLOR_FILL);

	msm_property_install_range(&psde->property_info, "prefill_size", 0x0,
			0, ~0, 0, PLANE_PROP_PREFILL_SIZE);
	msm_property_install_range(&psde->property_info, "prefill_time", 0x0,
			0, ~0, 0, PLANE_PROP_PREFILL_TIME);

	msm_property_install_blob(&psde->property_info, "capabilities",
			DRM_MODE_PROP_IMMUTABLE, PLANE_PROP_INFO);

	sde_kms_info_reset(info);
	_sde_plane_setup_capabilities_blob(psde, master_plane_id, info,
			catalog);

	_sde_plane_install_colorproc_properties(psde, info);

	msm_property_set_blob(&psde->property_info, &psde->blob_info,
			info->data, SDE_KMS_INFO_DATALEN(info),
			PLANE_PROP_INFO);

	msm_property_install_enum(&psde->property_info, "fb_translation_mode",
			0x0, 0, e_fb_translation_mode,
			ARRAY_SIZE(e_fb_translation_mode), 0,
			PLANE_PROP_FB_TRANSLATION_MODE);

	if (psde->pipe_hw->ops.set_ubwc_stats_roi)
		msm_property_install_range(&psde->property_info, "ubwc_stats_roi",
				0, 0, 0xFFFFFFFF, 0, PLANE_PROP_UBWC_STATS_ROI);
	vfree(info);
}

static inline void _sde_plane_set_csc_v1(struct sde_plane *psde,
		void __user *usr_ptr)
{
	struct sde_drm_csc_v1 csc_v1;
	int i;

	if (!psde) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde->csc_usr_ptr = NULL;
	if (!usr_ptr) {
		SDE_DEBUG_PLANE(psde, "csc data removed\n");
		return;
	}

	if (copy_from_user(&csc_v1, usr_ptr, sizeof(csc_v1))) {
		SDE_ERROR_PLANE(psde, "failed to copy csc data\n");
		return;
	}

	/* populate from user space */
	for (i = 0; i < SDE_CSC_MATRIX_COEFF_SIZE; ++i)
		psde->csc_cfg.csc_mv[i] = csc_v1.ctm_coeff[i] >> 16;
	for (i = 0; i < SDE_CSC_BIAS_SIZE; ++i) {
		psde->csc_cfg.csc_pre_bv[i] = csc_v1.pre_bias[i];
		psde->csc_cfg.csc_post_bv[i] = csc_v1.post_bias[i];
	}
	for (i = 0; i < SDE_CSC_CLAMP_SIZE; ++i) {
		psde->csc_cfg.csc_pre_lv[i] = csc_v1.pre_clamp[i];
		psde->csc_cfg.csc_post_lv[i] = csc_v1.post_clamp[i];
	}
	psde->csc_usr_ptr = &psde->csc_cfg;
}

static inline void _sde_plane_set_scaler_v1(struct sde_plane *psde,
		struct sde_plane_state *pstate, void __user *usr)
{
	struct sde_drm_scaler_v1 scale_v1;
	struct sde_hw_pixel_ext *pe;
	int i;

	if (!psde || !pstate) {
		SDE_ERROR("invalid argument(s)\n");
		return;
	}

	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_NONE;
	if (!usr) {
		SDE_DEBUG_PLANE(psde, "scale data removed\n");
		return;
	}

	if (copy_from_user(&scale_v1, usr, sizeof(scale_v1))) {
		SDE_ERROR_PLANE(psde, "failed to copy scale data\n");
		return;
	}

	/* force property to be dirty, even if the pointer didn't change */
	msm_property_set_dirty(&psde->property_info,
			&pstate->property_state, PLANE_PROP_SCALER_V1);

	/* populate from user space */
	pe = &pstate->pixel_ext;
	memset(pe, 0, sizeof(struct sde_hw_pixel_ext));
	for (i = 0; i < SDE_MAX_PLANES; i++) {
		pe->init_phase_x[i] = scale_v1.init_phase_x[i];
		pe->phase_step_x[i] = scale_v1.phase_step_x[i];
		pe->init_phase_y[i] = scale_v1.init_phase_y[i];
		pe->phase_step_y[i] = scale_v1.phase_step_y[i];

		pe->horz_filter[i] = scale_v1.horz_filter[i];
		pe->vert_filter[i] = scale_v1.vert_filter[i];
	}
	for (i = 0; i < SDE_MAX_PLANES; i++) {
		pe->left_ftch[i] = scale_v1.pe.left_ftch[i];
		pe->right_ftch[i] = scale_v1.pe.right_ftch[i];
		pe->left_rpt[i] = scale_v1.pe.left_rpt[i];
		pe->right_rpt[i] = scale_v1.pe.right_rpt[i];
		pe->roi_w[i] = scale_v1.pe.num_ext_pxls_lr[i];

		pe->top_ftch[i] = scale_v1.pe.top_ftch[i];
		pe->btm_ftch[i] = scale_v1.pe.btm_ftch[i];
		pe->top_rpt[i] = scale_v1.pe.top_rpt[i];
		pe->btm_rpt[i] = scale_v1.pe.btm_rpt[i];
		pe->roi_h[i] = scale_v1.pe.num_ext_pxls_tb[i];
	}

	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_SCALER_V1;

	SDE_EVT32_VERBOSE(DRMID(&psde->base));
	SDE_DEBUG_PLANE(psde, "user property data copied\n");
}

static void _sde_plane_clear_predownscale_settings(
		struct sde_plane_state *pstate)
{
	pstate->pre_down.pre_downscale_x_0 = 0;
	pstate->pre_down.pre_downscale_x_1 = 0;
	pstate->pre_down.pre_downscale_y_0 = 0;
	pstate->pre_down.pre_downscale_y_1 = 0;
}

static inline void _sde_plane_set_scaler_v2(struct sde_plane *psde,
		struct sde_plane_state *pstate, void __user *usr)
{
	struct sde_drm_scaler_v2 scale_v2;
	struct sde_hw_pixel_ext *pe;
	int i;
	struct sde_hw_scaler3_cfg *cfg;
	struct sde_hw_inline_pre_downscale_cfg *pd_cfg;

	if (!psde || !pstate) {
		SDE_ERROR("invalid argument(s)\n");
		return;
	}

	cfg = &pstate->scaler3_cfg;
	pd_cfg = &pstate->pre_down;
	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_NONE;
	if (!usr) {
		SDE_DEBUG_PLANE(psde, "scale data removed\n");
		cfg->enable = 0;
		_sde_plane_clear_predownscale_settings(pstate);
		goto end;
	}

	if (copy_from_user(&scale_v2, usr, sizeof(scale_v2))) {
		SDE_ERROR_PLANE(psde, "failed to copy scale data\n");
		return;
	}

	/* detach/ignore user data if 'disabled' */
	if (!scale_v2.enable) {
		SDE_DEBUG_PLANE(psde, "scale data removed\n");
		cfg->enable = 0;
		_sde_plane_clear_predownscale_settings(pstate);
		goto end;
	}

	/* populate from user space */
	sde_set_scaler_v2(cfg, &scale_v2);

	if (_sde_plane_has_pre_downscale(psde)) {
		pd_cfg->pre_downscale_x_0 = scale_v2.pre_downscale_x_0;
		pd_cfg->pre_downscale_x_1 = scale_v2.pre_downscale_x_1;
		pd_cfg->pre_downscale_y_0 = scale_v2.pre_downscale_y_0;
		pd_cfg->pre_downscale_y_1 = scale_v2.pre_downscale_y_1;
	}

	pe = &pstate->pixel_ext;
	memset(pe, 0, sizeof(struct sde_hw_pixel_ext));

	for (i = 0; i < SDE_MAX_PLANES; i++) {
		pe->left_ftch[i] = scale_v2.pe.left_ftch[i];
		pe->right_ftch[i] = scale_v2.pe.right_ftch[i];
		pe->left_rpt[i] = scale_v2.pe.left_rpt[i];
		pe->right_rpt[i] = scale_v2.pe.right_rpt[i];
		pe->roi_w[i] = scale_v2.pe.num_ext_pxls_lr[i];

		pe->top_ftch[i] = scale_v2.pe.top_ftch[i];
		pe->btm_ftch[i] = scale_v2.pe.btm_ftch[i];
		pe->top_rpt[i] = scale_v2.pe.top_rpt[i];
		pe->btm_rpt[i] = scale_v2.pe.btm_rpt[i];
		pe->roi_h[i] = scale_v2.pe.num_ext_pxls_tb[i];
	}
	pstate->scaler_check_state = SDE_PLANE_SCLCHECK_SCALER_V2_CHECK;

end:
	/* force property to be dirty, even if the pointer didn't change */
	msm_property_set_dirty(&psde->property_info,
			&pstate->property_state, PLANE_PROP_SCALER_V2);

	SDE_EVT32_VERBOSE(DRMID(&psde->base), cfg->enable, cfg->de.enable,
			cfg->src_width[0], cfg->src_height[0],
			cfg->dst_width, cfg->dst_height);
	SDE_DEBUG_PLANE(psde, "user property data copied\n");
}

static void _sde_plane_set_excl_rect_v1(struct sde_plane *psde,
		struct sde_plane_state *pstate, void __user *usr_ptr)
{
	struct drm_clip_rect excl_rect_v1;

	if (!psde || !pstate) {
		SDE_ERROR("invalid argument(s)\n");
		return;
	}

	if (!usr_ptr) {
		memset(&pstate->excl_rect, 0, sizeof(pstate->excl_rect));
		SDE_DEBUG_PLANE(psde, "excl_rect data cleared\n");
		return;
	}

	if (copy_from_user(&excl_rect_v1, usr_ptr, sizeof(excl_rect_v1))) {
		SDE_ERROR_PLANE(psde, "failed to copy excl_rect data\n");
		return;
	}

	/* populate from user space */
	pstate->excl_rect.x = excl_rect_v1.x1;
	pstate->excl_rect.y = excl_rect_v1.y1;
	pstate->excl_rect.w = excl_rect_v1.x2 - excl_rect_v1.x1;
	pstate->excl_rect.h = excl_rect_v1.y2 - excl_rect_v1.y1;

	SDE_DEBUG_PLANE(psde, "excl_rect: {%d,%d,%d,%d}\n",
			pstate->excl_rect.x, pstate->excl_rect.y,
			pstate->excl_rect.w, pstate->excl_rect.h);
}

static void _sde_plane_set_ubwc_stats_roi(struct sde_plane *psde,
		struct sde_plane_state *pstate, void __user *usr_ptr)
{
	struct sde_drm_ubwc_stats_roi roi = {0};

	if (!psde || !pstate) {
		SDE_ERROR("invalid argument(s)\n");
		return;
	}

	if (!usr_ptr) {
		SDE_DEBUG_PLANE(psde, "ubwc roi disabled");
		goto end;
	}

	if (copy_from_user(&roi, usr_ptr, sizeof(roi))) {
		SDE_ERROR_PLANE(psde, "failed to copy ubwc stats roi");
		return;
	}

	if (roi.y_coord0 > psde->pipe_cfg.src_rect.h || roi.y_coord1 > psde->pipe_cfg.src_rect.h) {
		SDE_ERROR_PLANE(psde, "invalid ubwc roi y0 0x%x, y1 0x%x, src height 0x%x",
				roi.y_coord0, roi.y_coord1, psde->pipe_cfg.src_rect.h);
		memset(&roi, 0, sizeof(roi));
	}

end:
	SDE_EVT32(psde, roi.y_coord0, roi.y_coord1);
	memcpy(&pstate->ubwc_stats_roi, &roi, sizeof(struct sde_drm_ubwc_stats_roi));
}

static int sde_plane_atomic_set_property(struct drm_plane *plane,
		struct drm_plane_state *state, struct drm_property *property,
		uint64_t val)
{
	struct sde_plane *psde = plane ? to_sde_plane(plane) : NULL;
	struct sde_plane_state *pstate;
	int idx, ret = -EINVAL;

	SDE_DEBUG_PLANE(psde, "\n");

	if (!plane) {
		SDE_ERROR("invalid plane\n");
	} else if (!state) {
		SDE_ERROR_PLANE(psde, "invalid state\n");
	} else {
		pstate = to_sde_plane_state(state);
		ret = msm_property_atomic_set(&psde->property_info,
				&pstate->property_state, property, val);
		if (!ret) {
			idx = msm_property_index(&psde->property_info,
					property);
			switch (idx) {
			case PLANE_PROP_INPUT_FENCE:
				_sde_plane_set_input_fence(psde, pstate, val);
				break;
			case PLANE_PROP_CSC_V1:
			case PLANE_PROP_CSC_DMA_V1:
				_sde_plane_set_csc_v1(psde, (void __user *)val);
				break;
			case PLANE_PROP_SCALER_V1:
				_sde_plane_set_scaler_v1(psde, pstate,
						(void *)(uintptr_t)val);
				break;
			case PLANE_PROP_SCALER_V2:
				_sde_plane_set_scaler_v2(psde, pstate,
						(void *)(uintptr_t)val);
				break;
			case PLANE_PROP_EXCL_RECT_V1:
				_sde_plane_set_excl_rect_v1(psde, pstate,
						(void *)(uintptr_t)val);
				break;
			case PLANE_PROP_UBWC_STATS_ROI:
				_sde_plane_set_ubwc_stats_roi(psde, pstate,
						(void __user *)(uintptr_t)val);
				break;
			default:
				/* nothing to do */
				break;
			}
		}
	}

	SDE_DEBUG_PLANE(psde, "%s[%d] <= 0x%llx ret=%d\n",
			property->name, property->base.id, val, ret);

	return ret;
}

static int sde_plane_atomic_get_property(struct drm_plane *plane,
		const struct drm_plane_state *state,
		struct drm_property *property, uint64_t *val)
{
	struct sde_plane *psde = plane ? to_sde_plane(plane) : NULL;
	struct sde_plane_state *pstate;
	int ret = -EINVAL;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
	} else if (!state) {
		SDE_ERROR("invalid state\n");
	} else {
		SDE_DEBUG_PLANE(psde, "\n");
		pstate = to_sde_plane_state(state);
		ret = msm_property_atomic_get(&psde->property_info,
				&pstate->property_state, property, val);
	}

	return ret;
}

int sde_plane_helper_reset_custom_properties(struct drm_plane *plane,
		struct drm_plane_state *plane_state)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	struct drm_property *drm_prop;
	enum msm_mdp_plane_property prop_idx;

	if (!plane || !plane_state) {
		SDE_ERROR("invalid params\n");
		return -EINVAL;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane_state);

	pstate->static_cache_state = CACHE_STATE_DISABLED;

	for (prop_idx = 0; prop_idx < PLANE_PROP_COUNT; prop_idx++) {
		uint64_t val = pstate->property_values[prop_idx].value;
		uint64_t def;
		int ret;

		drm_prop = msm_property_index_to_drm_property(
				&psde->property_info, prop_idx);
		if (!drm_prop) {
			/* not all props will be installed, based on caps */
			SDE_DEBUG_PLANE(psde, "invalid property index %d\n",
					prop_idx);
			continue;
		}

		def = msm_property_get_default(&psde->property_info, prop_idx);
		if (val == def)
			continue;

		SDE_DEBUG_PLANE(psde, "set prop %s idx %d from %llu to %llu\n",
				drm_prop->name, prop_idx, val, def);

		ret = sde_plane_atomic_set_property(plane, plane_state,
				drm_prop, def);
		if (ret) {
			SDE_ERROR_PLANE(psde,
					"set property failed, idx %d ret %d\n",
					prop_idx, ret);
			continue;
		}
	}

	return 0;
}

static void sde_plane_destroy(struct drm_plane *plane)
{
	struct sde_plane *psde = plane ? to_sde_plane(plane) : NULL;

	SDE_DEBUG_PLANE(psde, "\n");

	if (psde) {
		_sde_plane_set_qos_ctrl(plane, false, SDE_PLANE_QOS_PANIC_CTRL);

		if (psde->blob_info)
			drm_property_blob_put(psde->blob_info);
		msm_property_destroy(&psde->property_info);
		mutex_destroy(&psde->lock);

		/* this will destroy the states as well */
		drm_plane_cleanup(plane);

		if (psde->pipe_hw)
			sde_hw_sspp_destroy(psde->pipe_hw);

		kfree(psde);
	}
}

void sde_plane_destroy_fb(struct drm_plane_state *state)
{
	struct sde_plane_state *pstate;

	if (!state) {
		SDE_ERROR("invalid arg state %d\n", !state);
		return;
	}

	pstate = to_sde_plane_state(state);

	if (sde_plane_get_property(pstate, PLANE_PROP_FB_TRANSLATION_MODE) ==
			SDE_DRM_FB_SEC) {
		/* remove ref count for frame buffers */
		if (state->fb) {
			drm_framebuffer_put(state->fb);
			state->fb = NULL;
		}
	}

}

static void sde_plane_destroy_state(struct drm_plane *plane,
		struct drm_plane_state *state)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;

	if (!plane || !state) {
		SDE_ERROR("invalid arg(s), plane %d state %d\n",
				!plane, !state);
		return;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(state);

	SDE_DEBUG_PLANE(psde, "\n");

	/* remove ref count for frame buffers */
	if (state->fb)
		drm_framebuffer_put(state->fb);

	/* remove ref count for fence */
	if (pstate->input_fence)
		sde_sync_put(pstate->input_fence);
	pstate->input_fence = 0;

	/* destroy value helper */
	msm_property_destroy_state(&psde->property_info, pstate,
			&pstate->property_state);
}

static struct drm_plane_state *
sde_plane_duplicate_state(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	struct sde_plane_state *old_state;
	struct drm_property *drm_prop;
	uint64_t input_fence_default;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return NULL;
	} else if (!plane->state) {
		SDE_ERROR("invalid plane state\n");
		return NULL;
	}

	old_state = to_sde_plane_state(plane->state);
	psde = to_sde_plane(plane);

	if (old_state->cont_splash_populated) {
		plane->state->crtc = NULL;
		old_state->cont_splash_populated = false;
	}

	pstate = msm_property_alloc_state(&psde->property_info);
	if (!pstate) {
		SDE_ERROR_PLANE(psde, "failed to allocate state\n");
		return NULL;
	}

	SDE_DEBUG_PLANE(psde, "\n");

	/* duplicate value helper */
	msm_property_duplicate_state(&psde->property_info, old_state, pstate,
			&pstate->property_state, pstate->property_values);

	/* clear out any input fence */
	pstate->input_fence = 0;
	input_fence_default = msm_property_get_default(
			&psde->property_info, PLANE_PROP_INPUT_FENCE);
	drm_prop = msm_property_index_to_drm_property(
				&psde->property_info, PLANE_PROP_INPUT_FENCE);
	if (msm_property_atomic_set(&psde->property_info,
				&pstate->property_state, drm_prop,
				input_fence_default))
		SDE_DEBUG_PLANE(psde,
				"error clearing duplicated input fence\n");

	pstate->dirty = 0x0;
	pstate->pending = false;

	__drm_atomic_helper_plane_duplicate_state(plane, &pstate->base);

	/* reset layout offset */
	if (pstate->layout_offset) {
		if (pstate->layout_offset > 0)
			pstate->base.crtc_x += pstate->layout_offset;
		pstate->layout = SDE_LAYOUT_NONE;
		pstate->layout_offset = 0;
	}

	return &pstate->base;
}

static void sde_plane_reset(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);
	SDE_DEBUG_PLANE(psde, "\n");

	if (plane->state && !sde_crtc_is_reset_required(plane->state->crtc)) {
		SDE_DEBUG_PLANE(psde, "avoid reset for plane\n");
		return;
	}

	/* remove previous state, if present */
	if (plane->state) {
		sde_plane_destroy_state(plane, plane->state);
		plane->state = 0;
	}

	pstate = msm_property_alloc_state(&psde->property_info);
	if (!pstate) {
		SDE_ERROR_PLANE(psde, "failed to allocate state\n");
		return;
	}

	/* reset value helper */
	msm_property_reset_state(&psde->property_info, pstate,
			&pstate->property_state,
			pstate->property_values);

	pstate->base.plane = plane;

	plane->state = &pstate->base;
}

void sde_plane_get_frame_data(struct drm_plane *plane,
		struct sde_drm_plane_frame_data *data)
{
	struct sde_plane *psde;
	struct sde_plane_state *pstate;
	struct sde_drm_ubwc_stats_data *ubwc_stats;

	if (!plane) {
		SDE_ERROR("invalid plane\n");
		return;
	}

	psde = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);
	ubwc_stats = &data->ubwc_stats;

	data->plane_id = DRMID(plane);

	if (psde->pipe_hw->ops.get_ubwc_stats_data) {
		memcpy(&ubwc_stats->roi, &pstate->ubwc_stats_roi,
				sizeof(struct sde_drm_ubwc_stats_roi));
		psde->pipe_hw->ops.get_ubwc_stats_data(psde->pipe_hw,
				pstate->multirect_index, ubwc_stats);
	}

	if (psde->pipe_hw->ops.get_ubwc_error)
		ubwc_stats->error = psde->pipe_hw->ops.get_ubwc_error(psde->pipe_hw,
				pstate->multirect_index);

	if (psde->pipe_hw->ops.clear_ubwc_error && ubwc_stats->error)
		psde->pipe_hw->ops.clear_ubwc_error(psde->pipe_hw, pstate->multirect_index);

	if (psde->pipe_hw->ops.get_meta_error)
		ubwc_stats->meta_error = psde->pipe_hw->ops.get_meta_error(psde->pipe_hw,
				pstate->multirect_index);

	if (psde->pipe_hw->ops.clear_meta_error && ubwc_stats->meta_error)
		psde->pipe_hw->ops.clear_meta_error(psde->pipe_hw, pstate->multirect_index);

	if (ubwc_stats->error || ubwc_stats->meta_error) {
		SDE_EVT32(DRMID(plane),  ubwc_stats->error, ubwc_stats->meta_error,
				SDE_EVTLOG_ERROR);
		SDE_DEBUG_PLANE(psde, "ubwc_error:0x%x meta_error:0x%x\n",
				ubwc_stats->error, ubwc_stats->meta_error);
	}
}

#if IS_ENABLED(CONFIG_DEBUG_FS)
static ssize_t _sde_plane_danger_read(struct file *file,
			char __user *buff, size_t count, loff_t *ppos)
{
	struct sde_kms *kms = file->private_data;
	struct sde_mdss_cfg *cfg = kms->catalog;
	int len = 0;
	char buf[40] = {'\0'};

	if (!cfg)
		return -ENODEV;

	if (*ppos)
		return 0; /* the end */

	len = snprintf(buf, sizeof(buf), "%d\n", !kms->has_danger_ctrl);
	if (len < 0 || len >= sizeof(buf))
		return 0;

	if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
		return -EFAULT;

	*ppos += len;   /* increase offset */

	return len;
}

static void _sde_plane_set_danger_state(struct sde_kms *kms, bool enable)
{
	struct drm_plane *plane;

	drm_for_each_plane(plane, kms->dev) {
		if (plane->fb && plane->state) {
			sde_plane_danger_signal_ctrl(plane, enable);
			SDE_DEBUG("plane:%d img:%dx%d ",
				plane->base.id, plane->fb->width,
				plane->fb->height);
			SDE_DEBUG("src[%d,%d,%d,%d] dst[%d,%d,%d,%d]\n",
				plane->state->src_x >> 16,
				plane->state->src_y >> 16,
				plane->state->src_w >> 16,
				plane->state->src_h >> 16,
				plane->state->crtc_x, plane->state->crtc_y,
				plane->state->crtc_w, plane->state->crtc_h);
		} else {
			SDE_DEBUG("Inactive plane:%d\n", plane->base.id);
		}
	}
}

static ssize_t _sde_plane_danger_write(struct file *file,
		    const char __user *user_buf, size_t count, loff_t *ppos)
{
	struct sde_kms *kms = file->private_data;
	struct sde_mdss_cfg *cfg = kms->catalog;
	int disable_panic;
	char buf[10];

	if (!cfg)
		return -EFAULT;

	if (count >= sizeof(buf))
		return -EFAULT;

	if (copy_from_user(buf, user_buf, count))
		return -EFAULT;

	buf[count] = 0;	/* end of string */

	if (kstrtoint(buf, 0, &disable_panic))
		return -EFAULT;

	if (disable_panic) {
		/* Disable panic signal for all active pipes */
		SDE_DEBUG("Disabling danger:\n");
		_sde_plane_set_danger_state(kms, false);
		kms->has_danger_ctrl = false;
	} else {
		/* Enable panic signal for all active pipes */
		SDE_DEBUG("Enabling danger:\n");
		kms->has_danger_ctrl = true;
		_sde_plane_set_danger_state(kms, true);
	}

	return count;
}

static const struct file_operations sde_plane_danger_enable = {
	.open = simple_open,
	.read = _sde_plane_danger_read,
	.write = _sde_plane_danger_write,
};

static int _sde_plane_init_debugfs(struct drm_plane *plane)
{
	struct sde_plane *psde;
	struct sde_kms *kms;
	struct msm_drm_private *priv;
	const struct sde_sspp_sub_blks *sblk = 0;
	const struct sde_sspp_cfg *cfg = 0;

	if (!plane || !plane->dev) {
		SDE_ERROR("invalid arguments\n");
		return -EINVAL;
	}

	priv = plane->dev->dev_private;
	if (!priv || !priv->kms) {
		SDE_ERROR("invalid KMS reference\n");
		return -EINVAL;
	}

	kms = to_sde_kms(priv->kms);
	psde = to_sde_plane(plane);

	if (psde && psde->pipe_hw)
		cfg = psde->pipe_hw->cap;
	if (cfg)
		sblk = cfg->sblk;

	if (!sblk)
		return 0;

	/* create overall sub-directory for the pipe */
	psde->debugfs_root =
		debugfs_create_dir(psde->pipe_name,
				plane->dev->primary->debugfs_root);

	if (!psde->debugfs_root)
		return -ENOMEM;

	/* don't error check these */
	debugfs_create_x64("features", 0400,
		psde->debugfs_root, &psde->features);

	if (cfg->features & BIT(SDE_SSPP_SCALER_QSEED3) ||
			cfg->features & BIT(SDE_SSPP_SCALER_QSEED3LITE) ||
			cfg->features & BIT(SDE_SSPP_SCALER_QSEED2))
		debugfs_create_bool("default_scaling",
				0600,
				psde->debugfs_root,
				&psde->debugfs_default_scale);

	if (cfg->features & BIT(SDE_SSPP_TRUE_INLINE_ROT)) {
		debugfs_create_u32("in_rot_max_downscale_rt_num",
			0600,
			psde->debugfs_root,
			(u32 *) &psde->pipe_sblk->in_rot_maxdwnscale_rt_num);
		debugfs_create_u32("in_rot_max_downscale_rt_denom",
			0600,
			psde->debugfs_root,
			(u32 *) &psde->pipe_sblk->in_rot_maxdwnscale_rt_denom);
		debugfs_create_u32("in_rot_max_downscale_nrt",
			0600,
			psde->debugfs_root,
			(u32 *) &psde->pipe_sblk->in_rot_maxdwnscale_nrt);
		debugfs_create_u32("in_rot_max_height",
			0600,
			psde->debugfs_root,
			(u32 *) &psde->pipe_sblk->in_rot_maxheight);
	}

	debugfs_create_u32("xin_id",
			0400,
			psde->debugfs_root,
			(u32 *) &cfg->xin_id);
	debugfs_create_x32("creq_vblank",
			0600,
			psde->debugfs_root,
			(u32 *) &sblk->creq_vblank);
	debugfs_create_x32("danger_vblank",
			0600,
			psde->debugfs_root,
			(u32 *) &sblk->danger_vblank);

	debugfs_create_file("disable_danger",
			0600,
			psde->debugfs_root,
			kms, &sde_plane_danger_enable);

	return 0;
}

static void _sde_plane_destroy_debugfs(struct drm_plane *plane)
{
	struct sde_plane *psde;

	if (!plane)
		return;
	psde = to_sde_plane(plane);

	debugfs_remove_recursive(psde->debugfs_root);
}
#else
static int _sde_plane_init_debugfs(struct drm_plane *plane)
{
	return 0;
}
static void _sde_plane_destroy_debugfs(struct drm_plane *plane)
{
}
#endif /* CONFIG_DEBUG_FS */

static int sde_plane_late_register(struct drm_plane *plane)
{
	return _sde_plane_init_debugfs(plane);
}

static void sde_plane_early_unregister(struct drm_plane *plane)
{
	_sde_plane_destroy_debugfs(plane);
}

static const struct drm_plane_funcs sde_plane_funcs = {
		.update_plane = drm_atomic_helper_update_plane,
		.disable_plane = drm_atomic_helper_disable_plane,
		.destroy = sde_plane_destroy,
		.atomic_set_property = sde_plane_atomic_set_property,
		.atomic_get_property = sde_plane_atomic_get_property,
		.reset = sde_plane_reset,
		.atomic_duplicate_state = sde_plane_duplicate_state,
		.atomic_destroy_state = sde_plane_destroy_state,
		.late_register = sde_plane_late_register,
		.early_unregister = sde_plane_early_unregister,
};

static const struct drm_plane_helper_funcs sde_plane_helper_funcs = {
		.prepare_fb = sde_plane_prepare_fb,
		.cleanup_fb = sde_plane_cleanup_fb,
		.atomic_check = sde_plane_atomic_check,
		.atomic_update = sde_plane_atomic_update,
};

enum sde_sspp sde_plane_pipe(struct drm_plane *plane)
{
	return plane ? to_sde_plane(plane)->pipe : SSPP_NONE;
}

bool is_sde_plane_virtual(struct drm_plane *plane)
{
	return plane ? to_sde_plane(plane)->is_virtual : false;
}

/* initialize plane */
struct drm_plane *sde_plane_init(struct drm_device *dev,
		uint32_t pipe, bool primary_plane,
		unsigned long possible_crtcs, u32 master_plane_id)
{
	struct drm_plane *plane = NULL, *master_plane = NULL;
	const struct sde_format_extended *format_list;
	struct sde_plane *psde;
	struct msm_drm_private *priv;
	struct sde_kms *kms;
	enum drm_plane_type type;
	struct sde_vbif_clk_client clk_client;
	int ret = -EINVAL;

	if (!dev) {
		SDE_ERROR("[%u]device is NULL\n", pipe);
		goto exit;
	}

	priv = dev->dev_private;
	if (!priv) {
		SDE_ERROR("[%u]private data is NULL\n", pipe);
		goto exit;
	}

	if (!priv->kms) {
		SDE_ERROR("[%u]invalid KMS reference\n", pipe);
		goto exit;
	}
	kms = to_sde_kms(priv->kms);

	if (!kms->catalog) {
		SDE_ERROR("[%u]invalid catalog reference\n", pipe);
		goto exit;
	}

	/* create and zero local structure */
	psde = kzalloc(sizeof(*psde), GFP_KERNEL);
	if (!psde) {
		SDE_ERROR("[%u]failed to allocate local plane struct\n", pipe);
		ret = -ENOMEM;
		goto exit;
	}

	/* cache local stuff for later */
	plane = &psde->base;
	psde->pipe = pipe;
	psde->is_virtual = (master_plane_id != 0);
	INIT_LIST_HEAD(&psde->mplane_list);
	master_plane = drm_plane_find(dev, NULL, master_plane_id);
	if (master_plane) {
		struct sde_plane *mpsde = to_sde_plane(master_plane);

		list_add_tail(&psde->mplane_list, &mpsde->mplane_list);
	}

	/* initialize underlying h/w driver */
	psde->pipe_hw = sde_hw_sspp_init(pipe, kms->mmio, kms->catalog, psde->is_virtual,
			&clk_client);
	if (IS_ERR(psde->pipe_hw)) {
		SDE_ERROR("[%u]SSPP init failed\n", pipe);
		ret = PTR_ERR(psde->pipe_hw);
		goto clean_plane;
	} else if (!psde->pipe_hw->cap || !psde->pipe_hw->cap->sblk) {
		SDE_ERROR("[%u]SSPP init returned invalid cfg\n", pipe);
		goto clean_sspp;
	}

	if (test_bit(SDE_FEATURE_VBIF_CLK_SPLIT, kms->catalog->features)) {
		ret = sde_vbif_clk_register(kms, &clk_client);
		if (ret) {
			SDE_ERROR("failed to register vbif client %d\n",
					clk_client.clk_ctrl);
			goto clean_sspp;
		}
	}

	/* cache features mask for later */
	psde->features = psde->pipe_hw->cap->features_ext;
	psde->perf_features = psde->pipe_hw->cap->perf_features;
	psde->pipe_sblk = psde->pipe_hw->cap->sblk;
	if (!psde->pipe_sblk) {
		SDE_ERROR("[%u]invalid sblk\n", pipe);
		goto clean_sspp;
	}

	if (psde->is_virtual)
		format_list = psde->pipe_sblk->virt_format_list;
	else
		format_list = psde->pipe_sblk->format_list;

	psde->nformats = sde_populate_formats(format_list,
				psde->formats,
				0,
				ARRAY_SIZE(psde->formats));

	if (!psde->nformats) {
		SDE_ERROR("[%u]no valid formats for plane\n", pipe);
		goto clean_sspp;
	}

	if (primary_plane)
		type = DRM_PLANE_TYPE_PRIMARY;
	else
		type = DRM_PLANE_TYPE_OVERLAY;
	ret = drm_universal_plane_init(dev, plane, 0xff, &sde_plane_funcs,
				psde->formats, psde->nformats,
				NULL, type, NULL);
	if (ret)
		goto clean_sspp;

	/* Populate static array of plane property flags */
	_sde_plane_map_prop_to_dirty_bits();

	/* success! finalize initialization */
	drm_plane_helper_add(plane, &sde_plane_helper_funcs);

	msm_property_init(&psde->property_info, &plane->base, dev,
			priv->plane_property, psde->property_data,
			PLANE_PROP_COUNT, PLANE_PROP_BLOBCOUNT,
			sizeof(struct sde_plane_state));

	_sde_plane_install_properties(plane, kms->catalog, master_plane_id);

	/* save user friendly pipe name for later */
	snprintf(psde->pipe_name, SDE_NAME_SIZE, "plane%u", plane->base.id);

	mutex_init(&psde->lock);

	SDE_DEBUG("%s created for pipe:%u id:%u master:%u\n", psde->pipe_name,
					pipe, plane->base.id, master_plane_id);
	return plane;

clean_sspp:
	if (psde && psde->pipe_hw)
		sde_hw_sspp_destroy(psde->pipe_hw);
clean_plane:
	kfree(psde);
exit:
	return ERR_PTR(ret);
}

void sde_plane_add_data_to_minidump_va(struct drm_plane *plane)
{
	struct sde_plane *sde_plane;
	struct sde_plane_state *pstate;

	sde_plane = to_sde_plane(plane);
	pstate = to_sde_plane_state(plane->state);
	sde_mini_dump_add_va_region("sde_plane", sizeof(*sde_plane), sde_plane);
	sde_mini_dump_add_va_region("plane_state", sizeof(*pstate), pstate);
}
