msm: sde: Add mdss 3.0.0 support to sde v4l2 rotator driver

Add mdss 3.0.0 regdma support to sde v4l2 rotator driver.
Regdma enables hardware queueing of multiple concurrent rotation
requests. This reduces software overhead between submission
and improves overall performance.

CRs-Fixed: 972831

Change-Id: I5bb308e552d7ed6edd314a4574181e1ee3498960
Signed-off-by: Alan Kwong <akwong@codeaurora.org>
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
new file mode 100644
index 0000000..a054736
--- /dev/null
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
@@ -0,0 +1,1873 @@
+/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only 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.
+ *
+ */
+
+#define pr_fmt(fmt)	"%s: " fmt, __func__
+
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/file.h>
+#include <linux/sync.h>
+#include <linux/delay.h>
+#include <linux/debugfs.h>
+#include <linux/interrupt.h>
+#include <linux/dma-mapping.h>
+#include <linux/dma-buf.h>
+#include <linux/msm_ion.h>
+
+#include "sde_rotator_core.h"
+#include "sde_rotator_util.h"
+#include "sde_rotator_smmu.h"
+#include "sde_rotator_r3.h"
+#include "sde_rotator_r3_internal.h"
+#include "sde_rotator_r3_hwio.h"
+#include "sde_rotator_r3_debug.h"
+#include "sde_rotator_trace.h"
+
+/* XIN mapping */
+#define XIN_SSPP		0
+#define XIN_WRITEBACK		1
+
+/* wait for at most 2 vsync for lowest refresh rate (24hz) */
+#define KOFF_TIMEOUT msecs_to_jiffies(42 * 32)
+
+/* Macro for constructing the REGDMA command */
+#define SDE_REGDMA_WRITE(p, off, data) \
+	do { \
+		*p++ = REGDMA_OP_REGWRITE | \
+			((off) & REGDMA_ADDR_OFFSET_MASK); \
+		*p++ = (data); \
+	} while (0)
+
+#define SDE_REGDMA_MODIFY(p, off, mask, data) \
+	do { \
+		*p++ = REGDMA_OP_REGMODIFY | \
+			((off) & REGDMA_ADDR_OFFSET_MASK); \
+		*p++ = (mask); \
+		*p++ = (data); \
+	} while (0)
+
+#define SDE_REGDMA_BLKWRITE_INC(p, off, len) \
+	do { \
+		*p++ = REGDMA_OP_BLKWRITE_INC | \
+			((off) & REGDMA_ADDR_OFFSET_MASK); \
+		*p++ = (len); \
+	} while (0)
+
+#define SDE_REGDMA_BLKWRITE_DATA(p, data) \
+	do { \
+		*(p) = (data); \
+		(p)++; \
+	} while (0)
+
+/* Macro for directly accessing mapped registers */
+#define SDE_ROTREG_WRITE(base, off, data) \
+	writel_relaxed(data, (base + (off)))
+
+#define SDE_ROTREG_READ(base, off) \
+	readl_relaxed(base + (off))
+
+/**
+ * sde_hw_rotator_get_ctx(): Retrieve rotator context from rotator HW based
+ * on provided session_id. Each rotator has a different session_id.
+ */
+static struct sde_hw_rotator_context *sde_hw_rotator_get_ctx(
+		struct sde_hw_rotator *rot, u32 session_id,
+		enum sde_rot_queue_prio q_id)
+{
+	int i;
+	struct sde_hw_rotator_context  *ctx = NULL;
+
+	for (i = 0; i < SDE_HW_ROT_REGDMA_TOTAL_CTX; i++) {
+		ctx = rot->rotCtx[q_id][i];
+
+		if (ctx && (ctx->session_id == session_id)) {
+			SDEROT_DBG(
+				"rotCtx sloti[%d][%d] ==> ctx:%p | session-id:%d\n",
+				q_id, i, ctx, ctx->session_id);
+			return ctx;
+		}
+	}
+
+	return NULL;
+}
+
+/*
+ * sde_hw_rotator_map_vaddr - map the debug buffer to kernel space
+ * @dbgbuf: Pointer to debug buffer
+ * @buf: Pointer to layer buffer structure
+ * @data: Pointer to h/w mapped buffer structure
+ */
+static void sde_hw_rotator_map_vaddr(struct sde_dbg_buf *dbgbuf,
+		struct sde_layer_buffer *buf, struct sde_mdp_data *data)
+{
+	dbgbuf->dmabuf = data->p[0].srcp_dma_buf;
+	dbgbuf->buflen = data->p[0].srcp_dma_buf->size;
+
+	dbgbuf->vaddr  = NULL;
+	dbgbuf->width  = buf->width;
+	dbgbuf->height = buf->height;
+
+	if (dbgbuf->dmabuf && (dbgbuf->buflen > 0)) {
+		dma_buf_begin_cpu_access(dbgbuf->dmabuf, 0, dbgbuf->buflen,
+				DMA_FROM_DEVICE);
+		dbgbuf->vaddr = dma_buf_kmap(dbgbuf->dmabuf, 0);
+		SDEROT_DBG("vaddr mapping: 0x%p/%ld w:%d/h:%d\n",
+				dbgbuf->vaddr, dbgbuf->buflen,
+				dbgbuf->width, dbgbuf->height);
+	}
+}
+
+/*
+ * sde_hw_rotator_unmap_vaddr - unmap the debug buffer from kernel space
+ * @dbgbuf: Pointer to debug buffer
+ */
+static void sde_hw_rotator_unmap_vaddr(struct sde_dbg_buf *dbgbuf)
+{
+	if (dbgbuf->vaddr) {
+		dma_buf_kunmap(dbgbuf->dmabuf, 0, dbgbuf->vaddr);
+		dma_buf_end_cpu_access(dbgbuf->dmabuf, 0, dbgbuf->buflen,
+				DMA_FROM_DEVICE);
+	}
+
+	dbgbuf->vaddr  = NULL;
+	dbgbuf->dmabuf = NULL;
+	dbgbuf->buflen = 0;
+	dbgbuf->width  = 0;
+	dbgbuf->height = 0;
+}
+
+/*
+ * sde_hw_rotator_setup_timestamp_packet - setup timestamp writeback command
+ * @ctx: Pointer to rotator context
+ * @mask: Bit mask location of the timestamp
+ * @swts: Software timestamp
+ */
+static void sde_hw_rotator_setup_timestamp_packet(
+		struct sde_hw_rotator_context *ctx, u32 mask, u32 swts)
+{
+	u32 *wrptr;
+
+	wrptr = sde_hw_rotator_get_regdma_segment(ctx);
+
+	/*
+	 * Create a dummy packet write out to 1 location for timestamp
+	 * generation.
+	 */
+	SDE_REGDMA_BLKWRITE_INC(wrptr, ROT_SSPP_SRC_SIZE, 6);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x00010001);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x00010001);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, ctx->ts_addr);
+	SDE_REGDMA_WRITE(wrptr, ROT_SSPP_SRC_YSTRIDE0, 4);
+	SDE_REGDMA_BLKWRITE_INC(wrptr, ROT_SSPP_SRC_FORMAT, 4);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x004037FF);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x03020100);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x80000000);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, ctx->timestamp);
+	SDE_REGDMA_BLKWRITE_INC(wrptr, ROT_WB_DST_FORMAT, 4);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x000037FF);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0x03020100);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, ctx->ts_addr);
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_DST_YSTRIDE0, 4);
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_SIZE, 0x00010001);
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_IMG_SIZE, 0x00010001);
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_XY, 0);
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_DNSC, 0);
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_OP_MODE, 1);
+	SDE_REGDMA_MODIFY(wrptr, REGDMA_TIMESTAMP_REG, mask, swts);
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_START_CTRL, 1);
+
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+}
+
+/*
+ * sde_hw_rotator_setup_fetchengine - setup fetch engine
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ * @cfg: Fetch configuration
+ * @danger_lut: real-time QoS LUT for danger setting (not used)
+ * @safe_lut: real-time QoS LUT for safe setting (not used)
+ * @flags: Control flag
+ */
+static void sde_hw_rotator_setup_fetchengine(struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id,
+		struct sde_hw_rot_sspp_cfg *cfg, u32 danger_lut, u32 safe_lut,
+		u32 flags)
+{
+	struct sde_hw_rotator *rot = ctx->rot;
+	struct sde_mdp_format_params *fmt;
+	struct sde_mdp_data *data;
+	u32 *wrptr;
+	u32 opmode = 0;
+	u32 chroma_samp = 0;
+	u32 src_format = 0;
+	u32 unpack = 0;
+	u32 width = cfg->img_width;
+	u32 height = cfg->img_height;
+	u32 fetch_blocksize = 0;
+	int i;
+
+	if (ctx->rot->mode == ROT_REGDMA_ON) {
+		SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_EN,
+				REGDMA_INT_MASK);
+		SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_OP_MODE,
+				REGDMA_EN);
+	}
+
+	wrptr = sde_hw_rotator_get_regdma_segment(ctx);
+
+	/* source image setup */
+	if ((flags & SDE_ROT_FLAG_DEINTERLACE)
+			&& !(flags & SDE_ROT_FLAG_SOURCE_ROTATED_90)) {
+		for (i = 0; i < cfg->src_plane.num_planes; i++)
+			cfg->src_plane.ystride[i] *= 2;
+		width *= 2;
+		height /= 2;
+	}
+
+	/*
+	 * REGDMA BLK write from SRC_SIZE to OP_MODE, total 15 registers
+	 */
+	SDE_REGDMA_BLKWRITE_INC(wrptr, ROT_SSPP_SRC_SIZE, 15);
+
+	/* SRC_SIZE, SRC_IMG_SIZE, SRC_XY, OUT_SIZE, OUT_XY */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr,
+			cfg->src_rect->w | (cfg->src_rect->h << 16));
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0); /* SRC_IMG_SIZE unused */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr,
+			cfg->src_rect->x | (cfg->src_rect->y << 16));
+	SDE_REGDMA_BLKWRITE_DATA(wrptr,
+			cfg->src_rect->w | (cfg->src_rect->h << 16));
+	SDE_REGDMA_BLKWRITE_DATA(wrptr,
+			cfg->src_rect->x | (cfg->src_rect->y << 16));
+
+	/* SRC_ADDR [0-3], SRC_YSTRIDE [0-1] */
+	data = cfg->data;
+	for (i = 0; i < SDE_ROT_MAX_PLANES; i++)
+		SDE_REGDMA_BLKWRITE_DATA(wrptr, data->p[i].addr);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, cfg->src_plane.ystride[0] |
+			(cfg->src_plane.ystride[1] << 16));
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, cfg->src_plane.ystride[2] |
+			(cfg->src_plane.ystride[3] << 16));
+
+	/* UNUSED, write 0 */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+
+	/* setup source format */
+	fmt = cfg->fmt;
+
+	chroma_samp = fmt->chroma_sample;
+	if (flags & SDE_ROT_FLAG_SOURCE_ROTATED_90) {
+		if (chroma_samp == SDE_MDP_CHROMA_H2V1)
+			chroma_samp = SDE_MDP_CHROMA_H1V2;
+		else if (chroma_samp == SDE_MDP_CHROMA_H1V2)
+			chroma_samp = SDE_MDP_CHROMA_H2V1;
+	}
+
+	src_format = (chroma_samp << 23)   |
+		(fmt->fetch_planes << 19)  |
+		(fmt->bits[C3_ALPHA] << 6) |
+		(fmt->bits[C2_R_Cr] << 4)  |
+		(fmt->bits[C1_B_Cb] << 2)  |
+		(fmt->bits[C0_G_Y] << 0);
+
+	if (fmt->alpha_enable &&
+			(fmt->fetch_planes == SDE_MDP_PLANE_INTERLEAVED))
+		src_format |= BIT(8); /* SRCC3_EN */
+
+	src_format |= ((fmt->unpack_count - 1) << 12) |
+			(fmt->unpack_tight << 17)       |
+			(fmt->unpack_align_msb << 18)   |
+			((fmt->bpp - 1) << 9)           |
+			((fmt->frame_format & 3) << 30);
+
+	if (flags & SDE_ROT_FLAG_ROT_90)
+		src_format |= BIT(11);	/* ROT90 */
+
+	if (sde_mdp_is_ubwc_format(fmt))
+		opmode |= BIT(0); /* BWC_DEC_EN */
+
+	/* if this is YUV pixel format, enable CSC */
+	if (sde_mdp_is_yuv_format(fmt))
+		src_format |= BIT(15); /* SRC_COLOR_SPACE */
+
+	if (fmt->pixel_mode == SDE_MDP_PIXEL_10BIT)
+		src_format |= BIT(14); /* UNPACK_DX_FORMAT */
+
+	/* SRC_FORMAT */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, src_format);
+
+	/* setup source unpack pattern */
+	unpack = (fmt->element[3] << 24) | (fmt->element[2] << 16) |
+		 (fmt->element[1] << 8)  | (fmt->element[0] << 0);
+
+	/* SRC_UNPACK_PATTERN */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, unpack);
+
+	/* setup source op mode */
+	if (flags & SDE_ROT_FLAG_FLIP_LR)
+		opmode |= BIT(13); /* FLIP_MODE L/R horizontal flip */
+	if (flags & SDE_ROT_FLAG_FLIP_UD)
+		opmode |= BIT(14); /* FLIP_MODE U/D vertical flip */
+	opmode |= BIT(31); /* MDSS_MDP_OP_PE_OVERRIDE */
+
+	/* SRC_OP_MODE */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, opmode);
+
+	/* setup source fetch config, TP10 uses different block size */
+	if (sde_mdp_is_tp10_format(fmt))
+		fetch_blocksize = SDE_ROT_SSPP_FETCH_BLOCKSIZE_96;
+	else
+		fetch_blocksize = SDE_ROT_SSPP_FETCH_BLOCKSIZE_128;
+	SDE_REGDMA_WRITE(wrptr, ROT_SSPP_FETCH_CONFIG,
+			fetch_blocksize |
+			SDE_ROT_SSPP_FETCH_CONFIG_RESET_VALUE |
+			((rot->highest_bank & 0x3) << 18));
+
+	/* setup source buffer plane security status */
+	if (flags & SDE_ROT_FLAG_SECURE_OVERLAY_SESSION) {
+		SDE_REGDMA_WRITE(wrptr, ROT_SSPP_SRC_ADDR_SW_STATUS, 0xF);
+		ctx->is_secure = true;
+	}
+
+	/* Update command queue write ptr */
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+}
+
+/*
+ * sde_hw_rotator_setup_wbengine - setup writeback engine
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ * @cfg: Writeback configuration
+ * @flags: Control flag
+ */
+static void sde_hw_rotator_setup_wbengine(struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id,
+		struct sde_hw_rot_wb_cfg *cfg,
+		u32 flags)
+{
+	struct sde_mdp_format_params *fmt;
+	u32 *wrptr;
+	u32 pack = 0;
+	u32 dst_format = 0;
+	int i;
+
+	wrptr = sde_hw_rotator_get_regdma_segment(ctx);
+
+	fmt = cfg->fmt;
+
+	/* setup WB DST format */
+	dst_format |= (fmt->chroma_sample << 23) |
+			(fmt->fetch_planes << 19)  |
+			(fmt->bits[C3_ALPHA] << 6) |
+			(fmt->bits[C2_R_Cr] << 4)  |
+			(fmt->bits[C1_B_Cb] << 2)  |
+			(fmt->bits[C0_G_Y] << 0);
+
+	/* alpha control */
+	if (fmt->bits[C3_ALPHA] || fmt->alpha_enable) {
+		dst_format |= BIT(8);
+		if (!fmt->alpha_enable) {
+			dst_format |= BIT(14);
+			SDE_REGDMA_WRITE(wrptr, ROT_WB_DST_ALPHA_X_VALUE, 0);
+		}
+	}
+
+	dst_format |= ((fmt->unpack_count - 1) << 12)	|
+			(fmt->unpack_tight << 17)	|
+			(fmt->unpack_align_msb << 18)	|
+			((fmt->bpp - 1) << 9)		|
+			((fmt->frame_format & 3) << 30);
+
+	if (sde_mdp_is_yuv_format(fmt))
+		dst_format |= BIT(15);
+
+	if (fmt->pixel_mode == SDE_MDP_PIXEL_10BIT)
+		dst_format |= BIT(21); /* PACK_DX_FORMAT */
+
+	/*
+	 * REGDMA BLK write, from DST_FORMAT to DST_YSTRIDE 1, total 9 regs
+	 */
+	SDE_REGDMA_BLKWRITE_INC(wrptr, ROT_WB_DST_FORMAT, 9);
+
+	/* DST_FORMAT */
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, dst_format);
+
+	/* DST_OP_MODE */
+	if (sde_mdp_is_ubwc_format(fmt))
+		SDE_REGDMA_BLKWRITE_DATA(wrptr, BIT(0));
+	else
+		SDE_REGDMA_BLKWRITE_DATA(wrptr, 0);
+
+	/* DST_PACK_PATTERN */
+	pack = (fmt->element[3] << 24) | (fmt->element[2] << 16) |
+		(fmt->element[1] << 8) | (fmt->element[0] << 0);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, pack);
+
+	/* DST_ADDR [0-3], DST_YSTRIDE [0-1] */
+	for (i = 0; i < SDE_ROT_MAX_PLANES; i++)
+		SDE_REGDMA_BLKWRITE_DATA(wrptr, cfg->data->p[i].addr);
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, cfg->dst_plane.ystride[0] |
+			(cfg->dst_plane.ystride[1] << 16));
+	SDE_REGDMA_BLKWRITE_DATA(wrptr, cfg->dst_plane.ystride[2] |
+			(cfg->dst_plane.ystride[3] << 16));
+
+	/* setup WB out image size and ROI */
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_IMG_SIZE,
+			cfg->img_width | (cfg->img_height << 16));
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_SIZE,
+			cfg->dst_rect->w | (cfg->dst_rect->h << 16));
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_OUT_XY,
+			cfg->dst_rect->x | (cfg->dst_rect->y << 16));
+
+	/*
+	 * setup Downscale factor
+	 */
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_DNSC,
+			cfg->v_downscale_factor |
+			(cfg->h_downscale_factor << 16));
+
+	/* write config setup for bank configration */
+	SDE_REGDMA_WRITE(wrptr, ROT_WB_DST_WRITE_CONFIG,
+			(ctx->rot->highest_bank & 0x3) << 8);
+
+	if (flags & SDE_ROT_FLAG_ROT_90)
+		SDE_REGDMA_WRITE(wrptr, ROTTOP_OP_MODE, 0x3);
+	else
+		SDE_REGDMA_WRITE(wrptr, ROTTOP_OP_MODE, 0x1);
+
+	/* Update command queue write ptr */
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+}
+
+/*
+ * sde_hw_rotator_start_no_regdma - start non-regdma operation
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ */
+static u32 sde_hw_rotator_start_no_regdma(struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id)
+{
+	struct sde_hw_rotator *rot = ctx->rot;
+	u32 *wrptr;
+	u32 *rdptr;
+	u8 *addr;
+	u32 mask;
+	u32 blksize;
+
+	rdptr = sde_hw_rotator_get_regdma_segment_base(ctx);
+	wrptr = sde_hw_rotator_get_regdma_segment(ctx);
+
+	if (rot->irq_num >= 0) {
+		SDE_REGDMA_WRITE(wrptr, ROTTOP_INTR_EN, 1);
+		SDE_REGDMA_WRITE(wrptr, ROTTOP_INTR_CLEAR, 1);
+		reinit_completion(&ctx->rot_comp);
+		enable_irq(rot->irq_num);
+	}
+
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_START_CTRL, 1);
+
+	/* Update command queue write ptr */
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+
+	SDEROT_DBG("BEGIN %d\n", ctx->timestamp);
+	/* Write all command stream to Rotator blocks */
+	/* Rotator will start right away after command stream finish writing */
+	while (rdptr < wrptr) {
+		u32 op = REGDMA_OP_MASK & *rdptr;
+
+		switch (op) {
+		case REGDMA_OP_NOP:
+			SDEROT_DBG("NOP\n");
+			rdptr++;
+			break;
+		case REGDMA_OP_REGWRITE:
+			SDEROT_DBG("REGW %6.6x %8.8x\n",
+					rdptr[0] & REGDMA_ADDR_OFFSET_MASK,
+					rdptr[1]);
+			addr =  rot->mdss_base +
+				(*rdptr++ & REGDMA_ADDR_OFFSET_MASK);
+			writel_relaxed(*rdptr++, addr);
+			break;
+		case REGDMA_OP_REGMODIFY:
+			SDEROT_DBG("REGM %6.6x %8.8x %8.8x\n",
+					rdptr[0] & REGDMA_ADDR_OFFSET_MASK,
+					rdptr[1], rdptr[2]);
+			addr =  rot->mdss_base +
+				(*rdptr++ & REGDMA_ADDR_OFFSET_MASK);
+			mask = *rdptr++;
+			writel_relaxed((readl_relaxed(addr) & mask) | *rdptr++,
+					addr);
+			break;
+		case REGDMA_OP_BLKWRITE_SINGLE:
+			SDEROT_DBG("BLKWS %6.6x %6.6x\n",
+					rdptr[0] & REGDMA_ADDR_OFFSET_MASK,
+					rdptr[1]);
+			addr =  rot->mdss_base +
+				(*rdptr++ & REGDMA_ADDR_OFFSET_MASK);
+			blksize = *rdptr++;
+			while (blksize--) {
+				SDEROT_DBG("DATA %8.8x\n", rdptr[0]);
+				writel_relaxed(*rdptr++, addr);
+			}
+			break;
+		case REGDMA_OP_BLKWRITE_INC:
+			SDEROT_DBG("BLKWI %6.6x %6.6x\n",
+					rdptr[0] & REGDMA_ADDR_OFFSET_MASK,
+					rdptr[1]);
+			addr =  rot->mdss_base +
+				(*rdptr++ & REGDMA_ADDR_OFFSET_MASK);
+			blksize = *rdptr++;
+			while (blksize--) {
+				SDEROT_DBG("DATA %8.8x\n", rdptr[0]);
+				writel_relaxed(*rdptr++, addr);
+				addr += 4;
+			}
+			break;
+		default:
+			/* Other not supported OP mode
+			 * Skip data for now for unregonized OP mode
+			 */
+			SDEROT_DBG("UNDEFINED\n");
+			rdptr++;
+			break;
+		}
+	}
+	SDEROT_DBG("END %d\n", ctx->timestamp);
+
+	return ctx->timestamp;
+}
+
+/*
+ * sde_hw_rotator_start_regdma - start regdma operation
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ */
+static u32 sde_hw_rotator_start_regdma(struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id)
+{
+	struct sde_hw_rotator *rot = ctx->rot;
+	u32 *wrptr;
+	u32  regdmaSlot;
+	u32  offset;
+	long length;
+	long ts_length;
+	u32  enableInt;
+	u32  swts = 0;
+	u32  mask = 0;
+
+	wrptr = sde_hw_rotator_get_regdma_segment(ctx);
+
+	if (rot->irq_num >= 0)
+		reinit_completion(&ctx->regdma_comp);
+
+	/* enable IRQ for first regdma submission from idle */
+	if (atomic_read(&rot->regdma_submit_count) ==
+				atomic_read(&rot->regdma_done_count)) {
+		SDEROT_DBG("Enable IRQ! regdma submitcnt==donecnt -> %d\n",
+				atomic_read(&rot->regdma_submit_count));
+		enable_irq(rot->irq_num);
+	}
+
+	/*
+	 * Last ROT command must be ROT_START before REGDMA start
+	 */
+	SDE_REGDMA_WRITE(wrptr, ROTTOP_START_CTRL, 1);
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+
+	/*
+	 * Start REGDMA with command offset and size
+	 */
+	regdmaSlot = sde_hw_rotator_get_regdma_ctxidx(ctx);
+	length = ((long)wrptr - (long)ctx->regdma_base) / 4;
+	offset = (u32)(ctx->regdma_base - (u32 *)(rot->mdss_base +
+				REGDMA_RAM_REGDMA_CMD_RAM));
+	enableInt = ((ctx->timestamp & 1) + 1) << 30;
+
+	SDEROT_DBG(
+		"regdma(%d)[%d] <== INT:0x%X|length:%ld|offset:0x%X, ts:%X\n",
+		queue_id, regdmaSlot, enableInt, length, offset,
+		ctx->timestamp);
+
+	/* ensure the command packet is issued before the submit command */
+	wmb();
+
+	/* REGDMA submission for current context */
+	if (queue_id == ROT_QUEUE_HIGH_PRIORITY) {
+		SDE_ROTREG_WRITE(rot->mdss_base,
+				REGDMA_CSR_REGDMA_QUEUE_0_SUBMIT,
+				(length << 14) | offset);
+		swts = ctx->timestamp;
+		mask = ~SDE_REGDMA_SWTS_MASK;
+	} else {
+		SDE_ROTREG_WRITE(rot->mdss_base,
+				REGDMA_CSR_REGDMA_QUEUE_1_SUBMIT,
+				(length << 14) | offset);
+		swts = ctx->timestamp << SDE_REGDMA_SWTS_SHIFT;
+		mask = ~(SDE_REGDMA_SWTS_MASK << SDE_REGDMA_SWTS_SHIFT);
+	}
+
+	/* Write timestamp after previous rotator job finished */
+	sde_hw_rotator_setup_timestamp_packet(ctx, mask, swts);
+	offset += length;
+	ts_length = sde_hw_rotator_get_regdma_segment(ctx) - wrptr;
+	WARN_ON((length + ts_length) > SDE_HW_ROT_REGDMA_SEG_SIZE);
+
+	/* ensure command packet is issue before the submit command */
+	wmb();
+
+	if (queue_id == ROT_QUEUE_HIGH_PRIORITY) {
+		SDE_ROTREG_WRITE(rot->mdss_base,
+				REGDMA_CSR_REGDMA_QUEUE_0_SUBMIT,
+				enableInt | (ts_length << 14) | offset);
+	} else {
+		SDE_ROTREG_WRITE(rot->mdss_base,
+				REGDMA_CSR_REGDMA_QUEUE_1_SUBMIT,
+				enableInt | (ts_length << 14) | offset);
+	}
+
+	/* Update REGDMA submit count */
+	atomic_inc(&rot->regdma_submit_count);
+
+	/* Update command queue write ptr */
+	sde_hw_rotator_put_regdma_segment(ctx, wrptr);
+
+	return ctx->timestamp;
+}
+
+/*
+ * sde_hw_rotator_wait_done_no_regdma - wait for non-regdma completion
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ * @flags: Option flag
+ */
+static u32 sde_hw_rotator_wait_done_no_regdma(
+		struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id, u32 flag)
+{
+	struct sde_hw_rotator *rot = ctx->rot;
+	int rc = 0;
+	u32 sts = 0;
+	u32 status;
+	unsigned long flags;
+
+	if (rot->irq_num >= 0) {
+		SDEROT_DBG("Wait for Rotator completion\n");
+		rc = wait_for_completion_timeout(&ctx->rot_comp,
+					KOFF_TIMEOUT);
+
+		spin_lock_irqsave(&rot->rotisr_lock, flags);
+		status = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_STATUS);
+		if (rc == 0) {
+			/*
+			 * Timeout, there might be error,
+			 * or rotator still busy
+			 */
+			if (status & ROT_BUSY_BIT)
+				SDEROT_ERR(
+					"Timeout waiting for rotator done\n");
+			else if (status & ROT_ERROR_BIT)
+				SDEROT_ERR(
+					"Rotator report error status\n");
+			else
+				SDEROT_WARN(
+					"Timeout waiting, but rotator job is done!!\n");
+
+			disable_irq_nosync(rot->irq_num);
+		}
+		spin_unlock_irqrestore(&rot->rotisr_lock, flags);
+	} else {
+		int cnt = 200;
+
+		do {
+			udelay(500);
+			status = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_STATUS);
+			cnt--;
+		} while ((cnt > 0) && (status & ROT_BUSY_BIT)
+				&& ((status & ROT_ERROR_BIT) == 0));
+
+		if (status & ROT_ERROR_BIT)
+			SDEROT_ERR("Rotator error\n");
+		else if (status & ROT_BUSY_BIT)
+			SDEROT_ERR("Rotator busy\n");
+
+		SDE_ROTREG_WRITE(rot->mdss_base, ROTTOP_INTR_CLEAR,
+				ROT_DONE_CLEAR);
+	}
+
+	sts = (status & ROT_ERROR_BIT) ? -ENODEV : 0;
+
+	return sts;
+}
+
+/*
+ * sde_hw_rotator_wait_done_regdma - wait for regdma completion
+ * @ctx: Pointer to rotator context
+ * @queue_id: Priority queue identifier
+ * @flags: Option flag
+ */
+static u32 sde_hw_rotator_wait_done_regdma(
+		struct sde_hw_rotator_context *ctx,
+		enum sde_rot_queue_prio queue_id, u32 flag)
+{
+	struct sde_hw_rotator *rot = ctx->rot;
+	int rc = 0;
+	u32 status;
+	u32 last_isr;
+	u32 last_ts;
+	u32 int_id;
+	u32 sts = 0;
+	u32 d_count;
+	unsigned long flags;
+
+	if (rot->irq_num >= 0) {
+		SDEROT_DBG("Wait for REGDMA completion, ctx:%p, ts:%X\n",
+				ctx, ctx->timestamp);
+		rc = wait_for_completion_timeout(&ctx->regdma_comp,
+				KOFF_TIMEOUT);
+
+		spin_lock_irqsave(&rot->rotisr_lock, flags);
+
+		last_isr = ctx->last_regdma_isr_status;
+		last_ts  = ctx->last_regdma_timestamp;
+		status   = last_isr & REGDMA_INT_MASK;
+		int_id   = last_ts & 1;
+		SDEROT_DBG("INT status:0x%X, INT id:%d, timestamp:0x%X\n",
+				status, int_id, last_ts);
+
+		if (rc == 0 || (status & REGDMA_INT_ERR_MASK)) {
+			SDEROT_ERR(
+				"Timeout wait for regdma interrupt status, ts:%X\n",
+				ctx->timestamp);
+
+			if (status & REGDMA_WATCHDOG_INT)
+				SDEROT_ERR("REGDMA watchdog interrupt\n");
+			else if (status & REGDMA_INVALID_DESCRIPTOR)
+				SDEROT_ERR("REGDMA invalid descriptor\n");
+			else if (status & REGDMA_INCOMPLETE_CMD)
+				SDEROT_ERR("REGDMA incomplete command\n");
+			else if (status & REGDMA_INVALID_CMD)
+				SDEROT_ERR("REGDMA invalid command\n");
+
+			status = ROT_ERROR_BIT;
+		} else if (queue_id == ROT_QUEUE_HIGH_PRIORITY) {
+			/* Got to match exactly with interrupt ID */
+			int_id = REGDMA_QUEUE0_INT0 << int_id;
+
+			SDE_ROTREG_WRITE(rot->mdss_base,
+					REGDMA_CSR_REGDMA_INT_CLEAR,
+					int_id);
+
+			status = 0;
+		} else if (queue_id == ROT_QUEUE_LOW_PRIORITY) {
+			/* Matching interrupt ID */
+			int_id = REGDMA_QUEUE1_INT0 << int_id;
+
+			SDE_ROTREG_WRITE(rot->mdss_base,
+					REGDMA_CSR_REGDMA_INT_CLEAR,
+					int_id);
+
+			status = 0;
+		}
+
+		/* regardless success or timeout, update done count */
+		d_count = atomic_inc_return(&rot->regdma_done_count);
+
+		/* disable IRQ if no more regdma submission in queue */
+		if (d_count == atomic_read(&rot->regdma_submit_count)) {
+			SDEROT_DBG(
+				"Disable IRQ!! regdma donecnt==submitcnt -> %d\n",
+				d_count);
+			disable_irq_nosync(rot->irq_num);
+		}
+
+		spin_unlock_irqrestore(&rot->rotisr_lock, flags);
+	} else {
+		int cnt = 200;
+
+		do {
+			udelay(500);
+			status = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_STATUS);
+			cnt--;
+		} while ((cnt > 0) && (status & ROT_BUSY_BIT)
+				&& ((status & ROT_ERROR_BIT) == 0));
+
+		if (status & ROT_ERROR_BIT)
+			SDEROT_ERR("Rotator error\n");
+		else if (status & ROT_BUSY_BIT)
+			SDEROT_ERR("Rotator busy\n");
+
+		SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR,
+				0xFFFF);
+	}
+
+	sts = (status & ROT_ERROR_BIT) ? -ENODEV : 0;
+
+	return sts;
+}
+
+/*
+ * setup_rotator_ops - setup callback functions for the low-level HAL
+ * @ops: Pointer to low-level ops callback
+ * @mode: Operation mode (non-regdma or regdma)
+ */
+static void setup_rotator_ops(struct sde_hw_rotator_ops *ops,
+		enum sde_rotator_regdma_mode mode)
+{
+	ops->setup_rotator_fetchengine = sde_hw_rotator_setup_fetchengine;
+	ops->setup_rotator_wbengine = sde_hw_rotator_setup_wbengine;
+	if (mode == ROT_REGDMA_ON) {
+		ops->start_rotator = sde_hw_rotator_start_regdma;
+		ops->wait_rotator_done = sde_hw_rotator_wait_done_regdma;
+	} else {
+		ops->start_rotator = sde_hw_rotator_start_no_regdma;
+		ops->wait_rotator_done = sde_hw_rotator_wait_done_no_regdma;
+	}
+}
+
+/*
+ * sde_hw_rotator_swts_create - create software timestamp buffer
+ * @rot: Pointer to rotator hw
+ *
+ * This buffer is used by regdma to keep track of last completed command.
+ */
+static int sde_hw_rotator_swts_create(struct sde_hw_rotator *rot)
+{
+	int rc = 0;
+	struct ion_handle *handle;
+	struct sde_mdp_img_data *data;
+	u32 bufsize = sizeof(int) * SDE_HW_ROT_REGDMA_TOTAL_CTX * 2;
+
+	rot->iclient = msm_ion_client_create(rot->pdev->name);
+	if (IS_ERR_OR_NULL(rot->iclient)) {
+		SDEROT_ERR("msm_ion_client_create() return error (%p)\n",
+				rot->iclient);
+		return -EINVAL;
+	}
+
+	handle = ion_alloc(rot->iclient, bufsize, SZ_4K,
+			ION_HEAP(ION_SYSTEM_HEAP_ID), 0);
+	if (IS_ERR_OR_NULL(handle)) {
+		SDEROT_ERR("ion memory allocation failed\n");
+		return -ENOMEM;
+	}
+
+	data = &rot->swts_buf;
+	data->len = bufsize;
+	data->srcp_dma_buf = ion_share_dma_buf(rot->iclient, handle);
+	if (IS_ERR(data->srcp_dma_buf)) {
+		SDEROT_ERR("ion_dma_buf setup failed\n");
+		rc = -ENOMEM;
+		goto imap_err;
+	}
+
+	sde_smmu_ctrl(1);
+
+	data->srcp_attachment = sde_smmu_dma_buf_attach(data->srcp_dma_buf,
+			&rot->pdev->dev, SDE_IOMMU_DOMAIN_ROT_UNSECURE);
+	if (IS_ERR_OR_NULL(data->srcp_attachment)) {
+		SDEROT_ERR("sde_smmu_dma_buf_attach error\n");
+		rc = -ENOMEM;
+		goto err_put;
+	}
+
+	data->srcp_table = dma_buf_map_attachment(data->srcp_attachment,
+			DMA_BIDIRECTIONAL);
+	if (IS_ERR_OR_NULL(data->srcp_table)) {
+		SDEROT_ERR("dma_buf_map_attachment error\n");
+		rc = -ENOMEM;
+		goto err_detach;
+	}
+
+	rc = sde_smmu_map_dma_buf(data->srcp_dma_buf, data->srcp_table,
+			SDE_IOMMU_DOMAIN_ROT_UNSECURE, &data->addr,
+			&data->len, DMA_BIDIRECTIONAL);
+	if (IS_ERR_VALUE(rc)) {
+		SDEROT_ERR("smmu_map_dma_buf failed: (%d)\n", rc);
+		goto err_unmap;
+	}
+
+	dma_buf_begin_cpu_access(data->srcp_dma_buf, 0, data->len,
+			DMA_FROM_DEVICE);
+	rot->swts_buffer = dma_buf_kmap(data->srcp_dma_buf, 0);
+	if (IS_ERR_OR_NULL(rot->swts_buffer)) {
+		SDEROT_ERR("ion kernel memory mapping failed\n");
+		rc = IS_ERR(rot->swts_buffer);
+		goto kmap_err;
+	}
+
+	data->mapped = true;
+	SDEROT_DBG("swts buffer mapped: %pad/%lx va:%p\n", &data->addr,
+			data->len, rot->swts_buffer);
+
+	ion_free(rot->iclient, handle);
+
+	sde_smmu_ctrl(0);
+
+	return rc;
+kmap_err:
+	sde_smmu_unmap_dma_buf(data->srcp_table, SDE_IOMMU_DOMAIN_ROT_UNSECURE,
+			DMA_FROM_DEVICE, data->srcp_dma_buf);
+err_unmap:
+	dma_buf_unmap_attachment(data->srcp_attachment, data->srcp_table,
+			DMA_FROM_DEVICE);
+err_detach:
+	dma_buf_detach(data->srcp_dma_buf, data->srcp_attachment);
+err_put:
+	dma_buf_put(data->srcp_dma_buf);
+	data->srcp_dma_buf = NULL;
+imap_err:
+	ion_free(rot->iclient, handle);
+
+	return rc;
+}
+
+/*
+ * sde_hw_rotator_swtc_destroy - destroy software timestamp buffer
+ * @rot: Pointer to rotator hw
+ */
+static void sde_hw_rotator_swtc_destroy(struct sde_hw_rotator *rot)
+{
+	struct sde_mdp_img_data *data;
+
+	data = &rot->swts_buf;
+
+	dma_buf_end_cpu_access(data->srcp_dma_buf, 0, data->len,
+			DMA_FROM_DEVICE);
+	dma_buf_kunmap(data->srcp_dma_buf, 0, rot->swts_buffer);
+
+	sde_smmu_unmap_dma_buf(data->srcp_table, SDE_IOMMU_DOMAIN_ROT_UNSECURE,
+			DMA_FROM_DEVICE, data->srcp_dma_buf);
+	dma_buf_unmap_attachment(data->srcp_attachment, data->srcp_table,
+			DMA_FROM_DEVICE);
+	dma_buf_detach(data->srcp_dma_buf, data->srcp_attachment);
+	dma_buf_put(data->srcp_dma_buf);
+	data->srcp_dma_buf = NULL;
+}
+
+/*
+ * sde_hw_rotator_destroy - Destroy hw rotator and free allocated resources
+ * @mgr: Pointer to rotator manager
+ */
+static void sde_hw_rotator_destroy(struct sde_rot_mgr *mgr)
+{
+	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
+	struct sde_hw_rotator *rot;
+
+	if (!mgr || !mgr->pdev || !mgr->hw_data) {
+		SDEROT_ERR("null parameters\n");
+		return;
+	}
+
+	rot = mgr->hw_data;
+	if (rot->irq_num >= 0)
+		devm_free_irq(&mgr->pdev->dev, rot->irq_num, mdata);
+
+	if (rot->mode == ROT_REGDMA_ON)
+		sde_hw_rotator_swtc_destroy(rot);
+
+	devm_kfree(&mgr->pdev->dev, mgr->hw_data);
+	mgr->hw_data = NULL;
+}
+
+/*
+ * sde_hw_rotator_alloc_ext - allocate rotator resource from rotator hw
+ * @mgr: Pointer to rotator manager
+ * @pipe_id: pipe identifier (not used)
+ * @wb_id: writeback identifier/priority queue identifier
+ *
+ * This function allocates a new hw rotator resource for the given priority.
+ */
+static struct sde_rot_hw_resource *sde_hw_rotator_alloc_ext(
+		struct sde_rot_mgr *mgr, u32 pipe_id, u32 wb_id)
+{
+	struct sde_hw_rotator_resource_info *resinfo;
+
+	if (!mgr || !mgr->hw_data) {
+		SDEROT_ERR("null parameters\n");
+		return NULL;
+	}
+
+	/*
+	 * Allocate rotator resource info. Each allocation is per
+	 * HW priority queue
+	 */
+	resinfo = devm_kzalloc(&mgr->pdev->dev, sizeof(*resinfo), GFP_KERNEL);
+	if (!resinfo) {
+		SDEROT_ERR("Failed allocation HW rotator resource info\n");
+		return NULL;
+	}
+
+	resinfo->rot = mgr->hw_data;
+	resinfo->hw.wb_id = wb_id;
+	atomic_set(&resinfo->hw.num_active, 0);
+	init_waitqueue_head(&resinfo->hw.wait_queue);
+
+	/* For non-regdma, only support one active session */
+	if (resinfo->rot->mode == ROT_REGDMA_OFF)
+		resinfo->hw.max_active = 1;
+	else {
+		resinfo->hw.max_active = SDE_HW_ROT_REGDMA_TOTAL_CTX - 1;
+
+		if (resinfo->rot->iclient == NULL)
+			sde_hw_rotator_swts_create(resinfo->rot);
+	}
+
+	SDEROT_DBG("New rotator resource:%p, priority:%d\n",
+			resinfo, wb_id);
+
+	return &resinfo->hw;
+}
+
+/*
+ * sde_hw_rotator_free_ext - free the given rotator resource
+ * @mgr: Pointer to rotator manager
+ * @hw: Pointer to rotator resource
+ */
+static void sde_hw_rotator_free_ext(struct sde_rot_mgr *mgr,
+		struct sde_rot_hw_resource *hw)
+{
+	struct sde_hw_rotator_resource_info *resinfo;
+
+	if (!mgr || !mgr->hw_data)
+		return;
+
+	resinfo = container_of(hw, struct sde_hw_rotator_resource_info, hw);
+
+	SDEROT_DBG(
+		"Free rotator resource:%p, priority:%d, active:%d, pending:%d\n",
+		resinfo, hw->wb_id, atomic_read(&hw->num_active),
+		hw->pending_count);
+
+	devm_kfree(&mgr->pdev->dev, resinfo);
+}
+
+/*
+ * sde_hw_rotator_alloc_rotctx - allocate rotator context
+ * @rot: Pointer to rotator hw
+ * @hw: Pointer to rotator resource
+ * @session_id: Session identifier of this context
+ *
+ * This function allocates a new rotator context for the given session id.
+ */
+static struct sde_hw_rotator_context *sde_hw_rotator_alloc_rotctx(
+		struct sde_hw_rotator *rot,
+		struct sde_rot_hw_resource *hw,
+		u32    session_id)
+{
+	struct sde_hw_rotator_context *ctx;
+
+	/* Allocate rotator context */
+	ctx = devm_kzalloc(&rot->pdev->dev, sizeof(*ctx), GFP_KERNEL);
+	if (!ctx) {
+		SDEROT_ERR("Failed allocation HW rotator context\n");
+		return NULL;
+	}
+
+	ctx->rot        = rot;
+	ctx->q_id       = hw->wb_id;
+	ctx->session_id = session_id;
+	ctx->hwres      = hw;
+	ctx->timestamp  = atomic_add_return(1, &rot->timestamp[ctx->q_id]);
+	ctx->timestamp &= SDE_REGDMA_SWTS_MASK;
+	ctx->is_secure  = false;
+
+	ctx->regdma_base  = rot->cmd_wr_ptr[ctx->q_id]
+		[sde_hw_rotator_get_regdma_ctxidx(ctx)];
+	ctx->regdma_wrptr = ctx->regdma_base;
+	ctx->ts_addr      = (dma_addr_t)((u32 *)rot->swts_buf.addr +
+		ctx->q_id * SDE_HW_ROT_REGDMA_TOTAL_CTX +
+		sde_hw_rotator_get_regdma_ctxidx(ctx));
+
+	init_completion(&ctx->rot_comp);
+	init_completion(&ctx->regdma_comp);
+
+	/* Store rotator context for lookup purpose */
+	sde_hw_rotator_put_ctx(ctx);
+
+	SDEROT_DBG(
+		"New rot CTX:%p, ctxidx:%d, session-id:%d, prio:%d, timestamp:%X, active:%d\n",
+		ctx, sde_hw_rotator_get_regdma_ctxidx(ctx), ctx->session_id,
+		ctx->q_id, ctx->timestamp,
+		atomic_read(&ctx->hwres->num_active));
+
+	return ctx;
+}
+
+/*
+ * sde_hw_rotator_free_rotctx - free the given rotator context
+ * @rot: Pointer to rotator hw
+ * @ctx: Pointer to rotator context
+ */
+static void sde_hw_rotator_free_rotctx(struct sde_hw_rotator *rot,
+		struct sde_hw_rotator_context *ctx)
+{
+	if (!rot || !ctx)
+		return;
+
+	SDEROT_DBG(
+		"Free rot CTX:%p, ctxidx:%d, session-id:%d, prio:%d, timestamp:%X, active:%d\n",
+		ctx, sde_hw_rotator_get_regdma_ctxidx(ctx), ctx->session_id,
+		ctx->q_id, ctx->timestamp,
+		atomic_read(&ctx->hwres->num_active));
+
+	rot->rotCtx[ctx->q_id][sde_hw_rotator_get_regdma_ctxidx(ctx)] = NULL;
+
+	devm_kfree(&rot->pdev->dev, ctx);
+}
+
+/*
+ * sde_hw_rotator_config - configure hw for the given rotation entry
+ * @hw: Pointer to rotator resource
+ * @entry: Pointer to rotation entry
+ *
+ * This function setup the fetch/writeback/rotator blocks, as well as VBIF
+ * based on the given rotation entry.
+ */
+static int sde_hw_rotator_config(struct sde_rot_hw_resource *hw,
+		struct sde_rot_entry *entry)
+{
+	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
+	struct sde_hw_rotator *rot;
+	struct sde_hw_rotator_resource_info *resinfo;
+	struct sde_hw_rotator_context *ctx;
+	struct sde_hw_rot_sspp_cfg sspp_cfg;
+	struct sde_hw_rot_wb_cfg wb_cfg;
+	u32 danger_lut = 0;	/* applicable for realtime client only */
+	u32 safe_lut = 0;	/* applicable for realtime client only */
+	u32 flags = 0;
+	struct sde_rotation_item *item;
+
+	if (!hw || !entry) {
+		SDEROT_ERR("null hw resource/entry\n");
+		return -EINVAL;
+	}
+
+	resinfo = container_of(hw, struct sde_hw_rotator_resource_info, hw);
+	rot = resinfo->rot;
+	item = &entry->item;
+
+	ctx = sde_hw_rotator_alloc_rotctx(rot, hw, item->session_id);
+	if (!ctx) {
+		SDEROT_ERR("Failed allocating rotator context!!\n");
+		return -EINVAL;
+	}
+
+	flags = (item->flags & SDE_ROTATION_FLIP_LR) ?
+			SDE_ROT_FLAG_FLIP_LR : 0;
+	flags |= (item->flags & SDE_ROTATION_FLIP_UD) ?
+			SDE_ROT_FLAG_FLIP_UD : 0;
+	flags |= (item->flags & SDE_ROTATION_90) ?
+			SDE_ROT_FLAG_ROT_90 : 0;
+	flags |= (item->flags & SDE_ROTATION_DEINTERLACE) ?
+			SDE_ROT_FLAG_DEINTERLACE : 0;
+	flags |= (item->flags & SDE_ROTATION_SECURE) ?
+			SDE_ROT_FLAG_SECURE_OVERLAY_SESSION : 0;
+
+	sspp_cfg.img_width = item->input.width;
+	sspp_cfg.img_height = item->input.height;
+	sspp_cfg.fmt = sde_get_format_params(item->input.format);
+	if (!sspp_cfg.fmt) {
+		SDEROT_ERR("null format\n");
+		return -EINVAL;
+	}
+	sspp_cfg.src_rect = &item->src_rect;
+	sspp_cfg.data = &entry->src_buf;
+	sde_mdp_get_plane_sizes(sspp_cfg.fmt, item->input.width,
+			item->input.height, &sspp_cfg.src_plane,
+			0, /* No bwc_mode */
+			(flags & SDE_ROT_FLAG_SOURCE_ROTATED_90) ?
+					true : false);
+
+	rot->ops.setup_rotator_fetchengine(ctx, ctx->q_id,
+			&sspp_cfg, danger_lut, safe_lut, flags);
+
+	wb_cfg.img_width = item->output.width;
+	wb_cfg.img_height = item->output.height;
+	wb_cfg.fmt = sde_get_format_params(item->output.format);
+	wb_cfg.dst_rect = &item->dst_rect;
+	wb_cfg.data = &entry->dst_buf;
+	sde_mdp_get_plane_sizes(wb_cfg.fmt, item->output.width,
+			item->output.height, &wb_cfg.dst_plane,
+			0, /* No bwc_mode */
+			(flags & SDE_ROT_FLAG_ROT_90) ? true : false);
+
+	wb_cfg.v_downscale_factor = entry->dnsc_factor_h;
+	wb_cfg.h_downscale_factor = entry->dnsc_factor_w;
+
+	rot->ops.setup_rotator_wbengine(ctx, ctx->q_id, &wb_cfg, flags);
+
+	/* setup VA mapping for debugfs */
+	if (rot->dbgmem) {
+		sde_hw_rotator_map_vaddr(&ctx->src_dbgbuf,
+				&item->input,
+				&entry->src_buf);
+
+		sde_hw_rotator_map_vaddr(&ctx->dst_dbgbuf,
+				&item->output,
+				&entry->dst_buf);
+	}
+
+	if (mdata->default_ot_rd_limit) {
+		struct sde_mdp_set_ot_params ot_params;
+
+		memset(&ot_params, 0, sizeof(struct sde_mdp_set_ot_params));
+		ot_params.xin_id = XIN_SSPP;
+		ot_params.num = 0; /* not used */
+		ot_params.width = sspp_cfg.img_width;
+		ot_params.height = sspp_cfg.img_height;
+		ot_params.reg_off_vbif_lim_conf = MMSS_VBIF_RD_LIM_CONF;
+		ot_params.reg_off_mdp_clk_ctrl =
+				MMSS_VBIF_NRT_VBIF_CLK_FORCE_CTRL0;
+		ot_params.bit_off_mdp_clk_ctrl =
+				MMSS_VBIF_NRT_VBIF_CLK_FORCE_CTRL0_XIN0;
+		ot_params.is_rot = true;
+		ot_params.is_wb = true;
+		ot_params.is_yuv = sde_mdp_is_yuv_format(sspp_cfg.fmt);
+		sde_mdp_set_ot_limit(&ot_params);
+	}
+
+	if (mdata->default_ot_wr_limit) {
+		struct sde_mdp_set_ot_params ot_params;
+
+		memset(&ot_params, 0, sizeof(struct sde_mdp_set_ot_params));
+		ot_params.xin_id = XIN_WRITEBACK;
+		ot_params.num = 0; /* not used */
+		ot_params.width = wb_cfg.img_width;
+		ot_params.height = wb_cfg.img_height;
+		ot_params.reg_off_vbif_lim_conf = MMSS_VBIF_WR_LIM_CONF;
+		ot_params.reg_off_mdp_clk_ctrl =
+				MMSS_VBIF_NRT_VBIF_CLK_FORCE_CTRL0;
+		ot_params.bit_off_mdp_clk_ctrl =
+				MMSS_VBIF_NRT_VBIF_CLK_FORCE_CTRL0_XIN1;
+		ot_params.is_rot = true;
+		ot_params.is_wb = true;
+		ot_params.is_yuv = sde_mdp_is_yuv_format(wb_cfg.fmt);
+		sde_mdp_set_ot_limit(&ot_params);
+	}
+
+	if (test_bit(SDE_QOS_PER_PIPE_LUT, mdata->sde_qos_map))	{
+		u32 qos_lut = 0; /* low priority for nrt read client */
+
+		trace_rot_perf_set_qos_luts(XIN_SSPP, sspp_cfg.fmt->format,
+			qos_lut, sde_mdp_is_linear_format(sspp_cfg.fmt));
+
+		SDE_ROTREG_WRITE(rot->mdss_base, ROT_SSPP_CREQ_LUT, qos_lut);
+	}
+
+	if (mdata->npriority_lvl > 0) {
+		u32 mask, reg_val, i, vbif_qos;
+
+		for (i = 0; i < mdata->npriority_lvl; i++) {
+			reg_val = SDE_VBIF_READ(mdata,
+					MMSS_VBIF_NRT_VBIF_QOS_REMAP_00 + i*4);
+			mask = 0x3 << (XIN_SSPP * 2);
+			reg_val &= ~(mask);
+			vbif_qos = mdata->vbif_nrt_qos[i];
+			reg_val |= vbif_qos << (XIN_SSPP * 2);
+			/* ensure write is issued after the read operation */
+			mb();
+			SDE_VBIF_WRITE(mdata,
+					MMSS_VBIF_NRT_VBIF_QOS_REMAP_00 + i*4,
+					reg_val);
+		}
+	}
+
+	/* Enable write gather for writeback to remove write gaps, which
+	 * may hang AXI/BIMC/SDE.
+	 */
+	SDE_VBIF_WRITE(mdata, MMSS_VBIF_NRT_VBIF_WRITE_GATHTER_EN,
+			BIT(XIN_WRITEBACK));
+
+	return 0;
+}
+
+/*
+ * sde_hw_rotator_kickoff - kickoff processing on the given entry
+ * @hw: Pointer to rotator resource
+ * @entry: Pointer to rotation entry
+ */
+static int sde_hw_rotator_kickoff(struct sde_rot_hw_resource *hw,
+		struct sde_rot_entry *entry)
+{
+	struct sde_hw_rotator *rot;
+	struct sde_hw_rotator_resource_info *resinfo;
+	struct sde_hw_rotator_context *ctx;
+	int ret = 0;
+
+	if (!hw || !entry) {
+		SDEROT_ERR("null hw resource/entry\n");
+		return -EINVAL;
+	}
+
+	resinfo = container_of(hw, struct sde_hw_rotator_resource_info, hw);
+	rot = resinfo->rot;
+
+	/* Lookup rotator context from session-id */
+	ctx = sde_hw_rotator_get_ctx(rot, entry->item.session_id, hw->wb_id);
+	if (!ctx) {
+		SDEROT_ERR("Cannot locate rotator ctx from sesison id:%d\n",
+				entry->item.session_id);
+	}
+	WARN_ON(ctx == NULL);
+
+	ret = sde_smmu_ctrl(1);
+	if (IS_ERR_VALUE(ret)) {
+		SDEROT_ERR("IOMMU attach failed\n");
+		return ret;
+	}
+
+	rot->ops.start_rotator(ctx, ctx->q_id);
+
+	return 0;
+}
+
+/*
+ * sde_hw_rotator_wait4done - wait for completion notification
+ * @hw: Pointer to rotator resource
+ * @entry: Pointer to rotation entry
+ *
+ * This function blocks until the given entry is complete, error
+ * is detected, or timeout.
+ */
+static int sde_hw_rotator_wait4done(struct sde_rot_hw_resource *hw,
+		struct sde_rot_entry *entry)
+{
+	struct sde_hw_rotator *rot;
+	struct sde_hw_rotator_resource_info *resinfo;
+	struct sde_hw_rotator_context *ctx;
+	int ret;
+
+	if (!hw || !entry) {
+		SDEROT_ERR("null hw resource/entry\n");
+		return -EINVAL;
+	}
+
+	resinfo = container_of(hw, struct sde_hw_rotator_resource_info, hw);
+	rot = resinfo->rot;
+
+	/* Lookup rotator context from session-id */
+	ctx = sde_hw_rotator_get_ctx(rot, entry->item.session_id, hw->wb_id);
+	if (!ctx) {
+		SDEROT_ERR("Cannot locate rotator ctx from sesison id:%d\n",
+				entry->item.session_id);
+	}
+	WARN_ON(ctx == NULL);
+
+	ret = rot->ops.wait_rotator_done(ctx, ctx->q_id, 0);
+
+	sde_smmu_ctrl(0);
+
+	if (rot->dbgmem) {
+		sde_hw_rotator_unmap_vaddr(&ctx->src_dbgbuf);
+		sde_hw_rotator_unmap_vaddr(&ctx->dst_dbgbuf);
+	}
+
+	/* Current rotator context job is finished, time to free up*/
+	sde_hw_rotator_free_rotctx(rot, ctx);
+
+	return ret;
+}
+
+/*
+ * sde_rotator_hw_rev_init - setup feature and/or capability bitmask
+ * @rot: Pointer to hw rotator
+ *
+ * This function initializes feature and/or capability bitmask based on
+ * h/w version read from the device.
+ */
+static int sde_rotator_hw_rev_init(struct sde_hw_rotator *rot)
+{
+	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
+	u32 hw_version;
+
+	if (!mdata) {
+		SDEROT_ERR("null rotator data\n");
+		return -EINVAL;
+	}
+
+	hw_version = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_HW_VERSION);
+	SDEROT_DBG("hw version %8.8x\n", hw_version);
+
+	clear_bit(SDE_QOS_PER_PIPE_IB, mdata->sde_qos_map);
+	set_bit(SDE_QOS_OVERHEAD_FACTOR, mdata->sde_qos_map);
+	clear_bit(SDE_QOS_CDP, mdata->sde_qos_map);
+	set_bit(SDE_QOS_OTLIM, mdata->sde_qos_map);
+	set_bit(SDE_QOS_PER_PIPE_LUT, mdata->sde_qos_map);
+	clear_bit(SDE_QOS_SIMPLIFIED_PREFILL, mdata->sde_qos_map);
+
+	set_bit(SDE_CAPS_R3_WB, mdata->sde_caps_map);
+
+	return 0;
+}
+
+/*
+ * sde_hw_rotator_rotirq_handler - non-regdma interrupt handler
+ * @irq: Interrupt number
+ * @ptr: Pointer to private handle provided during registration
+ *
+ * This function services rotator interrupt and wakes up waiting client
+ * with pending rotation requests already submitted to h/w.
+ */
+static irqreturn_t sde_hw_rotator_rotirq_handler(int irq, void *ptr)
+{
+	struct sde_hw_rotator *rot = ptr;
+	struct sde_hw_rotator_context *ctx;
+	irqreturn_t ret = IRQ_NONE;
+	u32 isr;
+
+	isr = SDE_ROTREG_READ(rot->mdss_base, ROTTOP_INTR_STATUS);
+
+	SDEROT_DBG("intr_status = %8.8x\n", isr);
+
+	if (isr & ROT_DONE_MASK) {
+		if (rot->irq_num >= 0)
+			disable_irq_nosync(rot->irq_num);
+		SDEROT_DBG("Notify rotator complete\n");
+
+		/* Normal rotator only 1 session, no need to lookup */
+		ctx = rot->rotCtx[0][0];
+		WARN_ON(ctx == NULL);
+		complete_all(&ctx->rot_comp);
+
+		spin_lock(&rot->rotisr_lock);
+		SDE_ROTREG_WRITE(rot->mdss_base, ROTTOP_INTR_CLEAR,
+				ROT_DONE_CLEAR);
+		spin_unlock(&rot->rotisr_lock);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+/*
+ * sde_hw_rotator_regdmairq_handler - regdma interrupt handler
+ * @irq: Interrupt number
+ * @ptr: Pointer to private handle provided during registration
+ *
+ * This function services rotator interrupt, decoding the source of
+ * events (high/low priority queue), and wakes up all waiting clients
+ * with pending rotation requests already submitted to h/w.
+ */
+static irqreturn_t sde_hw_rotator_regdmairq_handler(int irq, void *ptr)
+{
+	struct sde_hw_rotator *rot = ptr;
+	struct sde_hw_rotator_context *ctx;
+	irqreturn_t ret = IRQ_NONE;
+	u32 isr;
+	u32 ts;
+	u32 q_id;
+
+	isr = SDE_ROTREG_READ(rot->mdss_base, REGDMA_CSR_REGDMA_INT_STATUS);
+	ts  = SDE_ROTREG_READ(rot->mdss_base, REGDMA_TIMESTAMP_REG);
+
+	SDEROT_DBG("intr_status = %8.8x, sw_TS:%X\n", isr, ts);
+
+	/* Any REGDMA status, including error and watchdog timer, should
+	 * trigger and wake up waiting thread
+	 */
+	if (isr & (REGDMA_INT_HIGH_MASK | REGDMA_INT_LOW_MASK)) {
+		spin_lock(&rot->rotisr_lock);
+
+		/*
+		 * Obtain rotator context based on timestamp from regdma
+		 * and low/high interrupt status
+		 */
+		if (isr & REGDMA_INT_HIGH_MASK) {
+			q_id = ROT_QUEUE_HIGH_PRIORITY;
+			ts   = ts & SDE_REGDMA_SWTS_MASK;
+		} else if (isr & REGDMA_INT_LOW_MASK) {
+			q_id = ROT_QUEUE_LOW_PRIORITY;
+			ts   = (ts >> SDE_REGDMA_SWTS_SHIFT) &
+				SDE_REGDMA_SWTS_MASK;
+		}
+
+		ctx = rot->rotCtx[q_id][ts & SDE_HW_ROT_REGDMA_SEG_MASK];
+		WARN_ON(ctx == NULL);
+
+		/*
+		 * Wake up all waiting context from the current and previous
+		 * SW Timestamp.
+		 */
+		do {
+			ctx->last_regdma_isr_status = isr;
+			ctx->last_regdma_timestamp  = ts;
+			SDEROT_DBG(
+				"regdma complete: ctx:%p, ts:%X, dcount:%X\n",
+				ctx, ts, atomic_read(&rot->regdma_done_count));
+			complete_all(&ctx->regdma_comp);
+
+			ts  = (ts - 1) & SDE_REGDMA_SWTS_MASK;
+			ctx = rot->rotCtx[q_id]
+				[ts & SDE_HW_ROT_REGDMA_SEG_MASK];
+		} while (ctx && (ctx->last_regdma_timestamp == 0));
+
+		/*
+		 * Clear corresponding regdma interrupt because it is a level
+		 * interrupt
+		 */
+		SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR,
+				isr);
+
+		spin_unlock(&rot->rotisr_lock);
+		ret = IRQ_HANDLED;
+	} else if (isr & REGDMA_INT_ERR_MASK) {
+		/*
+		 * For REGDMA Err, we save the isr info and wake up
+		 * all waiting contexts
+		 */
+		int i, j;
+
+		SDEROT_ERR(
+			"regdma err isr:%X, wake up all waiting contexts\n",
+			isr);
+
+		spin_lock(&rot->rotisr_lock);
+
+		for (i = 0; i < ROT_QUEUE_MAX; i++) {
+			for (j = 0; j < SDE_HW_ROT_REGDMA_TOTAL_CTX; j++) {
+				ctx = rot->rotCtx[i][j];
+				if (ctx && ctx->last_regdma_isr_status == 0) {
+					ctx->last_regdma_isr_status = isr;
+					ctx->last_regdma_timestamp  = ts;
+					complete_all(&ctx->regdma_comp);
+					SDEROT_DBG("Wakeup rotctx[%d][%d]:%p\n",
+							i, j, ctx);
+				}
+			}
+		}
+
+		SDE_ROTREG_WRITE(rot->mdss_base, REGDMA_CSR_REGDMA_INT_CLEAR,
+				isr);
+
+		spin_unlock(&rot->rotisr_lock);
+		ret = IRQ_HANDLED;
+	}
+
+	return ret;
+}
+
+/*
+ * sde_hw_rotator_validate_entry - validate rotation entry
+ * @mgr: Pointer to rotator manager
+ * @entry: Pointer to rotation entry
+ *
+ * This function validates the given rotation entry and provides possible
+ * fixup (future improvement) if available.  This function returns 0 if
+ * the entry is valid, and returns error code otherwise.
+ */
+static int sde_hw_rotator_validate_entry(struct sde_rot_mgr *mgr,
+		struct sde_rot_entry *entry)
+{
+	int ret = 0;
+	u16 src_w, src_h, dst_w, dst_h;
+	struct sde_rotation_item *item = &entry->item;
+	struct sde_mdp_format_params *fmt;
+
+	src_w = item->src_rect.w;
+	src_h = item->src_rect.h;
+
+	if (item->flags & SDE_ROTATION_90) {
+		dst_w = item->dst_rect.h;
+		dst_h = item->dst_rect.w;
+	} else {
+		dst_w = item->dst_rect.w;
+		dst_h = item->dst_rect.h;
+	}
+
+	entry->dnsc_factor_w = 0;
+	entry->dnsc_factor_h = 0;
+
+	if ((src_w != dst_w) || (src_h != dst_h)) {
+		if ((src_w % dst_w) || (src_h % dst_h)) {
+			SDEROT_DBG("non integral scale not support\n");
+			ret = -EINVAL;
+			goto dnsc_err;
+		}
+		entry->dnsc_factor_w = src_w / dst_w;
+		if ((entry->dnsc_factor_w & (entry->dnsc_factor_w - 1)) ||
+				(entry->dnsc_factor_w > 64)) {
+			SDEROT_DBG("non power-of-2 w_scale not support\n");
+			ret = -EINVAL;
+			goto dnsc_err;
+		}
+		entry->dnsc_factor_h = src_h / dst_h;
+		if ((entry->dnsc_factor_h & (entry->dnsc_factor_h - 1)) ||
+				(entry->dnsc_factor_h > 64)) {
+			SDEROT_DBG("non power-of-2 h_scale not support\n");
+			ret = -EINVAL;
+			goto dnsc_err;
+		}
+	}
+
+	fmt = sde_get_format_params(item->output.format);
+	/* Tiled format downscale support not applied to AYUV tiled */
+	if (sde_mdp_is_tilea5x_format(fmt) && (entry->dnsc_factor_h > 4)) {
+		SDEROT_DBG("max downscale for tiled format is 4\n");
+		ret = -EINVAL;
+		goto dnsc_err;
+	}
+	if (sde_mdp_is_ubwc_format(fmt)	&& (entry->dnsc_factor_h > 2)) {
+		SDEROT_DBG("downscale with ubwc cannot be more than 2\n");
+		ret = -EINVAL;
+	}
+
+dnsc_err:
+	/* Downscaler does not support asymmetrical dnsc */
+	if (entry->dnsc_factor_w != entry->dnsc_factor_h) {
+		SDEROT_DBG("asymmetric downscale not support\n");
+		ret = -EINVAL;
+	}
+
+	if (ret) {
+		entry->dnsc_factor_w = 0;
+		entry->dnsc_factor_h = 0;
+	}
+	return ret;
+}
+
+/*
+ * sde_hw_rotator_show_caps - output capability info to sysfs 'caps' file
+ * @mgr: Pointer to rotator manager
+ * @attr: Pointer to device attribute interface
+ * @buf: Pointer to output buffer
+ * @len: Length of output buffer
+ */
+static ssize_t sde_hw_rotator_show_caps(struct sde_rot_mgr *mgr,
+		struct device_attribute *attr, char *buf, ssize_t len)
+{
+	struct sde_hw_rotator *hw_data;
+	int cnt = 0;
+
+	if (!mgr || !buf)
+		return 0;
+
+	hw_data = mgr->hw_data;
+
+#define SPRINT(fmt, ...) \
+		(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
+
+	/* insert capabilities here */
+
+#undef SPRINT
+	return cnt;
+}
+
+/*
+ * sde_hw_rotator_show_state - output state info to sysfs 'state' file
+ * @mgr: Pointer to rotator manager
+ * @attr: Pointer to device attribute interface
+ * @buf: Pointer to output buffer
+ * @len: Length of output buffer
+ */
+static ssize_t sde_hw_rotator_show_state(struct sde_rot_mgr *mgr,
+		struct device_attribute *attr, char *buf, ssize_t len)
+{
+	struct sde_hw_rotator *rot;
+	struct sde_hw_rotator_context *ctx;
+	int cnt = 0;
+	int num_active = 0;
+	int i, j;
+
+	if (!mgr || !buf) {
+		SDEROT_ERR("null parameters\n");
+		return 0;
+	}
+
+	rot = mgr->hw_data;
+
+#define SPRINT(fmt, ...) \
+		(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))
+
+	if (rot) {
+		SPRINT("rot_mode=%d\n", rot->mode);
+		SPRINT("irq_num=%d\n", rot->irq_num);
+
+		if (rot->mode == ROT_REGDMA_OFF) {
+			SPRINT("max_active=1\n");
+			SPRINT("num_active=%d\n", rot->rotCtx[0][0] ? 1 : 0);
+		} else {
+			for (i = 0; i < ROT_QUEUE_MAX; i++) {
+				for (j = 0; j < SDE_HW_ROT_REGDMA_TOTAL_CTX;
+						j++) {
+					ctx = rot->rotCtx[i][j];
+
+					if (ctx) {
+						SPRINT(
+							"rotCtx[%d][%d]:%p\n",
+							i, j, ctx);
+						++num_active;
+					}
+				}
+			}
+
+			SPRINT("max_active=%d\n", SDE_HW_ROT_REGDMA_TOTAL_CTX);
+			SPRINT("num_active=%d\n", num_active);
+		}
+	}
+
+#undef SPRINT
+	return cnt;
+}
+
+/*
+ * sde_hw_rotator_parse_dt - parse r3 specific device tree settings
+ * @hw_data: Pointer to rotator hw
+ * @dev: Pointer to platform device
+ */
+static int sde_hw_rotator_parse_dt(struct sde_hw_rotator *hw_data,
+		struct platform_device *dev)
+{
+	int ret = 0;
+	u32 data;
+
+	if (!hw_data || !dev)
+		return -EINVAL;
+
+	ret = of_property_read_u32(dev->dev.of_node, "qcom,mdss-rot-mode",
+			&data);
+	if (ret) {
+		SDEROT_DBG("default to regdma off\n");
+		ret = 0;
+		hw_data->mode = ROT_REGDMA_OFF;
+	} else if (data < ROT_REGDMA_MAX) {
+		SDEROT_DBG("set to regdma mode %d\n", data);
+		hw_data->mode = data;
+	} else {
+		SDEROT_ERR("regdma mode out of range. default to regdma off\n");
+		hw_data->mode = ROT_REGDMA_OFF;
+	}
+
+	ret = of_property_read_u32(dev->dev.of_node,
+			"qcom,mdss-highest-bank-bit", &data);
+	if (ret) {
+		SDEROT_DBG("default to A5X bank\n");
+		ret = 0;
+		hw_data->highest_bank = 2;
+	} else {
+		SDEROT_DBG("set highest bank bit to %d\n", data);
+		hw_data->highest_bank = data;
+	}
+
+	return ret;
+}
+
+/*
+ * sde_rotator_r3_init - initialize the r3 module
+ * @mgr: Pointer to rotator manager
+ *
+ * This function setup r3 callback functions, parses r3 specific
+ * device tree settings, installs r3 specific interrupt handler,
+ * as well as initializes r3 internal data structure.
+ */
+int sde_rotator_r3_init(struct sde_rot_mgr *mgr)
+{
+	struct sde_hw_rotator *rot;
+	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
+	int i;
+	int ret;
+
+	rot = devm_kzalloc(&mgr->pdev->dev, sizeof(*rot), GFP_KERNEL);
+	if (!rot)
+		return -ENOMEM;
+
+	mgr->hw_data = rot;
+	mgr->queue_count = ROT_QUEUE_MAX;
+
+	rot->mdss_base = mdata->sde_io.base;
+	rot->pdev      = mgr->pdev;
+
+	/* Assign ops */
+	mgr->ops_hw_destroy = sde_hw_rotator_destroy;
+	mgr->ops_hw_alloc = sde_hw_rotator_alloc_ext;
+	mgr->ops_hw_free = sde_hw_rotator_free_ext;
+	mgr->ops_config_hw = sde_hw_rotator_config;
+	mgr->ops_kickoff_entry = sde_hw_rotator_kickoff;
+	mgr->ops_wait_for_entry = sde_hw_rotator_wait4done;
+	mgr->ops_hw_validate_entry = sde_hw_rotator_validate_entry;
+	mgr->ops_hw_show_caps = sde_hw_rotator_show_caps;
+	mgr->ops_hw_show_state = sde_hw_rotator_show_state;
+	mgr->ops_hw_create_debugfs = sde_rotator_r3_create_debugfs;
+
+	ret = sde_hw_rotator_parse_dt(mgr->hw_data, mgr->pdev);
+	if (ret)
+		goto error_parse_dt;
+
+	rot->irq_num = platform_get_irq(mgr->pdev, 0);
+	if (rot->irq_num < 0) {
+		SDEROT_ERR("fail to get rotator irq\n");
+	} else {
+		if (rot->mode == ROT_REGDMA_OFF)
+			ret = devm_request_threaded_irq(&mgr->pdev->dev,
+					rot->irq_num,
+					sde_hw_rotator_rotirq_handler,
+					NULL, 0, "sde_rotator_r3", rot);
+		else
+			ret = devm_request_threaded_irq(&mgr->pdev->dev,
+					rot->irq_num,
+					sde_hw_rotator_regdmairq_handler,
+					NULL, 0, "sde_rotator_r3", rot);
+		if (ret) {
+			SDEROT_ERR("fail to request irq r:%d\n", ret);
+			rot->irq_num = -1;
+		} else {
+			disable_irq(rot->irq_num);
+		}
+	}
+
+	setup_rotator_ops(&rot->ops, rot->mode);
+
+	spin_lock_init(&rot->rotctx_lock);
+	spin_lock_init(&rot->rotisr_lock);
+
+	/* REGDMA initialization */
+	if (rot->mode == ROT_REGDMA_OFF) {
+		for (i = 0; i < SDE_HW_ROT_REGDMA_TOTAL_CTX; i++)
+			rot->cmd_wr_ptr[0][i] = &rot->cmd_queue[
+				SDE_HW_ROT_REGDMA_SEG_SIZE * i];
+	} else {
+		for (i = 0; i < SDE_HW_ROT_REGDMA_TOTAL_CTX; i++)
+			rot->cmd_wr_ptr[ROT_QUEUE_HIGH_PRIORITY][i] =
+				(u32 *)(rot->mdss_base +
+					REGDMA_RAM_REGDMA_CMD_RAM +
+					SDE_HW_ROT_REGDMA_SEG_SIZE * 4 * i);
+
+		for (i = 0; i < SDE_HW_ROT_REGDMA_TOTAL_CTX; i++)
+			rot->cmd_wr_ptr[ROT_QUEUE_LOW_PRIORITY][i] =
+				(u32 *)(rot->mdss_base +
+					REGDMA_RAM_REGDMA_CMD_RAM +
+					SDE_HW_ROT_REGDMA_SEG_SIZE * 4 *
+					(i + SDE_HW_ROT_REGDMA_TOTAL_CTX));
+	}
+
+	atomic_set(&rot->timestamp[0], 0);
+	atomic_set(&rot->timestamp[1], 0);
+	atomic_set(&rot->regdma_submit_count, 0);
+	atomic_set(&rot->regdma_done_count, 0);
+
+	ret = sde_rotator_hw_rev_init(rot);
+	if (ret)
+		goto error_hw_rev_init;
+
+	return 0;
+error_hw_rev_init:
+	if (rot->irq_num >= 0)
+		devm_free_irq(&mgr->pdev->dev, rot->irq_num, mdata);
+	devm_kfree(&mgr->pdev->dev, mgr->hw_data);
+error_parse_dt:
+	return ret;
+}