// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
 * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved.
 */
#define pr_fmt(fmt)	"%s:%d: " fmt, __func__, __LINE__

#include <linux/vmalloc.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/regulator/consumer.h>
#include <linux/delay.h>
#include <linux/wait.h>
#include <linux/of.h>
#include <linux/dma-mapping.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-v4l2.h>
#include <media/v4l2-mem2mem.h>

#include "sde_rotator_inline.h"
#include "sde_rotator_base.h"
#include "sde_rotator_core.h"
#include "sde_rotator_dev.h"
#include "sde_rotator_debug.h"
#include "sde_rotator_trace.h"

/* Start v4l2 device number (default allocation) */
#define SDE_ROTATOR_BASE_DEVICE_NUMBER	-1

/* Default value for early_submit flag */
#define SDE_ROTATOR_EARLY_SUBMIT	1

/* Timeout (msec) waiting for stream to turn off. */
#define SDE_ROTATOR_STREAM_OFF_TIMEOUT	500

/* acquire fence time out, following other driver fence time out practice */
#define SDE_ROTATOR_FENCE_TIMEOUT	MSEC_PER_SEC

/* Timeout (msec) waiting for ctx open */
#define SDE_ROTATOR_CTX_OPEN_TIMEOUT	500

/* Rotator default fps */
#define SDE_ROTATOR_DEFAULT_FPS	60

/* Rotator rotation angles */
#define SDE_ROTATOR_DEGREE_270		270
#define SDE_ROTATOR_DEGREE_180		180
#define SDE_ROTATOR_DEGREE_90		90

/* Inline rotator qos request */
#define SDE_ROTATOR_ADD_REQUEST		1
#define SDE_ROTATOR_REMOVE_REQUEST		0

#ifndef CONFIG_MSM_SDE_ROTATOR_INIT_ONLY
static void sde_rotator_submit_handler(struct kthread_work *work);
static void sde_rotator_retire_handler(struct kthread_work *work);
#if IS_ENABLED(CONFIG_COMPAT)
static long sde_rotator_compat_ioctl32(struct file *file,
	unsigned int cmd, unsigned long arg);
#endif /* CONFIG_COMPAT */

/*
 * sde_rotator_ctx_from_fh - Get rotator context from v4l2 fh.
 * @fh: Pointer to v4l2 fh.
 */
static inline struct sde_rotator_ctx *sde_rotator_ctx_from_fh(
		struct v4l2_fh *fh)
{
	return container_of(fh, struct sde_rotator_ctx, fh);
}

/*
 * sde_rotator_get_flags_from_ctx - Get low-level command flag
 * @ctx: Pointer to rotator context.
 */
static uint32_t sde_rotator_get_flags_from_ctx(struct sde_rotator_ctx *ctx)
{
	uint32_t ret_flags = 0;

	if (ctx->rotate == SDE_ROTATOR_DEGREE_270)
		ret_flags |= SDE_ROTATION_270;
	else if (ctx->rotate == SDE_ROTATOR_DEGREE_180)
		ret_flags |= SDE_ROTATION_180;
	else if (ctx->rotate == SDE_ROTATOR_DEGREE_90)
		ret_flags |= SDE_ROTATION_90;
	if (ctx->hflip)
		ret_flags ^= SDE_ROTATION_FLIP_LR;
	if (ctx->vflip)
		ret_flags ^= SDE_ROTATION_FLIP_UD;
	if (ctx->secure)
		ret_flags |= SDE_ROTATION_SECURE;
	if (ctx->secure_camera)
		ret_flags |= SDE_ROTATION_SECURE_CAMERA;
	if (ctx->format_out.fmt.pix.field == V4L2_FIELD_INTERLACED &&
			ctx->format_cap.fmt.pix.field == V4L2_FIELD_NONE)
		ret_flags |= SDE_ROTATION_DEINTERLACE;

	return ret_flags;
}

/*
 * sde_rotator_get_config_from_ctx - Fill rotator configure structure.
 * @ctx: Pointer to rotator ctx.
 * @config: Pointer to config structure.
 */
static void sde_rotator_get_config_from_ctx(struct sde_rotator_ctx *ctx,
		struct sde_rotation_config *config)
{
	memset(config, 0, sizeof(struct sde_rotation_config));
	config->flags = sde_rotator_get_flags_from_ctx(ctx);
	config->frame_rate = (ctx->timeperframe.numerator) ?
				ctx->timeperframe.denominator
					/ ctx->timeperframe.numerator :	0;
	config->session_id = ctx->session_id;
	config->input.width = ctx->crop_out.width;
	config->input.height = ctx->crop_out.height;
	config->input.format = ctx->format_out.fmt.pix.pixelformat;
	config->input.comp_ratio.numer = 1;
	config->input.comp_ratio.denom = 1;
	config->output.width = ctx->crop_cap.width;
	config->output.height = ctx->crop_cap.height;
	config->output.format = ctx->format_cap.fmt.pix.pixelformat;
	config->output.comp_ratio.numer = 1;
	config->output.comp_ratio.denom = 1;

	/*
	 * Use compression ratio of the first buffer to estimate
	 * performance requirement of the session. If core layer does
	 * not support dynamic per buffer compression ratio recalculation,
	 * this configuration will determine the overall static compression
	 * ratio of the session.
	 */
	if (ctx->vbinfo_out)
		config->input.comp_ratio = ctx->vbinfo_out[0].comp_ratio;
	if (ctx->vbinfo_cap)
		config->output.comp_ratio = ctx->vbinfo_cap[0].comp_ratio;

	SDEDEV_DBG(ctx->rot_dev->dev, "config s:%d out_cr:%u/%u cap_cr:%u/%u\n",
			ctx->session_id,
			config->input.comp_ratio.numer,
			config->input.comp_ratio.denom,
			config->output.comp_ratio.numer,
			config->output.comp_ratio.denom);
}

/*
 * sde_rotator_get_item_from_ctx - Fill rotator item structure.
 * @ctx: Pointer to rotator ctx.
 * @item: Pointer to item structure.
 */
static void sde_rotator_get_item_from_ctx(struct sde_rotator_ctx *ctx,
		struct sde_rotation_item *item)
{
	memset(item, 0, sizeof(struct sde_rotation_item));
	item->flags = sde_rotator_get_flags_from_ctx(ctx);
	item->session_id = ctx->session_id;
	item->sequence_id = 0;
	/* assign high/low priority */
	item->wb_idx = (ctx->fh.prio >= V4L2_PRIORITY_DEFAULT) ? 0 : 1;
	item->src_rect.x = ctx->crop_out.left;
	item->src_rect.y = ctx->crop_out.top;
	item->src_rect.w = ctx->crop_out.width;
	item->src_rect.h = ctx->crop_out.height;
	item->input.width = ctx->format_out.fmt.pix.width;
	item->input.height = ctx->format_out.fmt.pix.height;
	item->input.format = ctx->format_out.fmt.pix.pixelformat;
	item->input.planes[0].fd = -1;
	item->input.planes[0].offset = 0;
	item->input.planes[0].stride = ctx->format_out.fmt.pix.bytesperline;
	item->input.plane_count = 1;
	item->input.fence = NULL;
	item->input.comp_ratio.numer = 1;
	item->input.comp_ratio.denom = 1;

	item->dst_rect.x = ctx->crop_cap.left;
	item->dst_rect.y = ctx->crop_cap.top;
	item->dst_rect.w = ctx->crop_cap.width;
	item->dst_rect.h = ctx->crop_cap.height;
	item->output.width = ctx->format_cap.fmt.pix.width;
	item->output.height = ctx->format_cap.fmt.pix.height;
	item->output.format = ctx->format_cap.fmt.pix.pixelformat;
	item->output.planes[0].fd = -1;
	item->output.planes[0].offset = 0;
	item->output.planes[0].stride = ctx->format_cap.fmt.pix.bytesperline;
	item->output.plane_count = 1;
	item->output.fence = NULL;
	item->output.comp_ratio.numer = 1;
	item->output.comp_ratio.denom = 1;
}

/*
 * sde_rotator_format_recalc - Recalculate format parameters.
 * @f: v4l2 format.
 */
static void sde_rotator_format_recalc(struct v4l2_format *f)
{
	int ret;
	struct sde_mdp_format_params *fmt;
	struct sde_mdp_plane_sizes ps;

	fmt = sde_get_format_params(f->fmt.pix.pixelformat);
	if (!fmt) {
		SDEROT_ERR("invalid format\n");
		goto error_fmt;
	}

	ret = sde_mdp_get_plane_sizes(fmt,
		f->fmt.pix.width, f->fmt.pix.height, &ps, 0, 0);
	if (ret) {
		SDEROT_ERR("invalid plane size\n");
		goto error_fmt;
	}

	f->fmt.pix.bytesperline = ps.ystride[0];
	f->fmt.pix.sizeimage = ps.total_size;

	return;
error_fmt:
	f->fmt.pix.bytesperline = 0;
	f->fmt.pix.sizeimage = 0;
}

/*
 * sde_rotator_validate_item - Check if rotator item is valid for processing.
 * @ctx: Pointer to rotator ctx.
 * @item: Pointer to item structure
 */
static int sde_rotator_validate_item(struct sde_rotator_ctx *ctx,
		struct sde_rotation_item *item)
{
	int ret;
	struct sde_rot_entry_container *req;
	struct sde_rotator_device *rot_dev = ctx->rot_dev;

	sde_rot_mgr_lock(rot_dev->mgr);
	req = sde_rotator_req_init(rot_dev->mgr, ctx->private, item, 1, 0);
	if (IS_ERR_OR_NULL(req)) {
		SDEDEV_ERR(rot_dev->dev, "fail allocate item\n");
		return -ENOMEM;
	}

	ret = sde_rotator_validate_request(rot_dev->mgr, ctx->private, req);
	sde_rot_mgr_unlock(rot_dev->mgr);
	devm_kfree(rot_dev->dev, req);
	return ret;
}

/*
 * sde_rotator_queue_setup - vb2_ops queue_setup callback.
 * @q: Pointer to vb2 queue struct.
 * @num_buffers: Pointer of number of buffers requested.
 * @num_planes: Pointer to number of planes requested.
 * @sizes: Array containing sizes of planes.
 * @alloc_ctxs: Array of allocated contexts for each plane.
 */
static int sde_rotator_queue_setup(struct vb2_queue *q,
	unsigned int *num_buffers, unsigned int *num_planes,
	unsigned int sizes[], struct device *alloc_devs[])
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
	int i;

	if (!num_buffers)
		return -EINVAL;

	switch (q->type) {
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		sizes[0] = ctx->format_out.fmt.pix.sizeimage;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		sizes[0] = ctx->format_cap.fmt.pix.sizeimage;
		break;
	default:
		return -EINVAL;
	}

	*num_planes = 1;
	alloc_devs[0] = (struct device *)ctx;

	switch (q->type) {
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		ctx->nbuf_out = *num_buffers;
		kfree(ctx->vbinfo_out);
		ctx->vbinfo_out = kcalloc(ctx->nbuf_out,
				sizeof(struct sde_rotator_vbinfo), GFP_KERNEL);
		if (!ctx->vbinfo_out)
			return -ENOMEM;
		for (i = 0; i < ctx->nbuf_out; i++) {
			ctx->vbinfo_out[i].fd = -1;
			ctx->vbinfo_out[i].comp_ratio.numer = 1;
			ctx->vbinfo_out[i].comp_ratio.denom = 1;
		}
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		ctx->nbuf_cap = *num_buffers;
		kfree(ctx->vbinfo_cap);
		ctx->vbinfo_cap = kcalloc(ctx->nbuf_cap,
				sizeof(struct sde_rotator_vbinfo), GFP_KERNEL);
		if (!ctx->vbinfo_cap)
			return -ENOMEM;
		for (i = 0; i < ctx->nbuf_cap; i++) {
			ctx->vbinfo_cap[i].fd = -1;
			ctx->vbinfo_cap[i].comp_ratio.numer = 1;
			ctx->vbinfo_cap[i].comp_ratio.denom = 1;
		}
		break;
	default:
		return -EINVAL;
	}

	return 0;
}

/*
 * sde_rotator_buf_queue - vb2_ops buf_queue callback.
 * @vb: Pointer to vb2 buffer struct.
 */
static void sde_rotator_buf_queue(struct vb2_buffer *vb)
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);

	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
}

/*
 * sde_rotator_buf_finish - vb2_ops buf_finish to finalize buffer before going
 *				back to user space
 * @vb: Pointer to vb2 buffer struct.
 */
static void sde_rotator_buf_finish(struct vb2_buffer *vb)
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
	int i;

	SDEDEV_DBG(ctx->rot_dev->dev,
			"buf_finish t:%d i:%d s:%d m:%u np:%d up:%lu\n",
			vb->type, vb->index, vb->state,
			vb->vb2_queue->memory,
			vb->num_planes,
			vb->planes[0].m.userptr);

	if (vb->vb2_queue->memory != VB2_MEMORY_USERPTR)
		return;

	/*
	 * We use userptr to tunnel fd, and fd can be the same across qbuf
	 * even though the underlying buffer is different.  Since vb2 layer
	 * optimizes memory mapping for userptr by first checking if userptr
	 * has changed, it will not trigger put_userptr if fd value does
	 * not change.  In order to force buffer release, we need to clear
	 * userptr when the current buffer is done and ready to go back to
	 * user mode. Since 0 is a valid fd, reset userptr to -1 instead.
	 */
	for (i = 0; i < vb->num_planes; i++)
		vb->planes[i].m.userptr = ~0;
}

/*
 * sde_rotator_return_all_buffers - Return all buffers with the given status.
 * @q: Pointer to vb2 buffer queue struct.
 * @state: State of the buffer
 */
static void sde_rotator_return_all_buffers(struct vb2_queue *q,
		enum vb2_buffer_state state)
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;

	SDEDEV_DBG(rot_dev->dev,
			"return q t:%d c:%d dc:%d s:%d\n",
			q->type, q->queued_count,
			atomic_read(&q->owned_by_drv_count),
			state);

	/* return buffers according videobuffer2-core.h */
	if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
		struct vb2_v4l2_buffer *buf;

		while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) {
			SDEDEV_DBG(rot_dev->dev,
					"return vb t:%d i:%d\n",
					buf->vb2_buf.type,
					buf->vb2_buf.index);
			v4l2_m2m_buf_done(buf, state);
		}
	} else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
		struct vb2_v4l2_buffer *buf;

		while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) {
			SDEDEV_DBG(rot_dev->dev,
					"return vb t:%d i:%d\n",
					buf->vb2_buf.type,
					buf->vb2_buf.index);
			v4l2_m2m_buf_done(buf, state);
		}
	} else {
		SDEDEV_ERR(rot_dev->dev, "unsupported vb t:%d\n", q->type);
	}
}

/*
 * sde_rotator_start_streaming - vb2_ops start_streaming callback.
 * @q: Pointer to vb2 queue struct.
 * @count: Number of buffer queued before stream on call.
 */
static int sde_rotator_start_streaming(struct vb2_queue *q, unsigned int count)
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;

	SDEDEV_DBG(rot_dev->dev, "start streaming s:%d t:%d\n",
			ctx->session_id, q->type);

	if (!list_empty(&ctx->pending_list)) {
		SDEDEV_ERR(rot_dev->dev,
				"command pending error s:%d t:%d p:%d\n",
				ctx->session_id, q->type,
				!list_empty(&ctx->pending_list));
		return -EINVAL;
	}

	ctx->abort_pending = 0;

	return 0;
}

/*
 * sde_rotator_stop_streaming - vb2_ops stop_streaming callback.
 * @q: Pointer to vb2 queue struct.
 *
 * This function will block waiting for stream to stop.  Unlock queue
 * lock to avoid deadlock.
 */
static void sde_rotator_stop_streaming(struct vb2_queue *q)
{
	struct sde_rotator_ctx *ctx = vb2_get_drv_priv(q);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotator_request *request;
	struct list_head *curr, *next;
	int i;
	int ret;

	SDEDEV_DBG(rot_dev->dev, "stop streaming s:%d t:%d p:%d\n",
			ctx->session_id, q->type,
			!list_empty(&ctx->pending_list));
	ctx->abort_pending = 1;
	mutex_unlock(q->lock);
	ret = wait_event_timeout(ctx->wait_queue,
			list_empty(&ctx->pending_list),
			msecs_to_jiffies(rot_dev->streamoff_timeout));
	mutex_lock(q->lock);
	if (!ret) {
		SDEDEV_ERR(rot_dev->dev,
				"timeout to stream off s:%d t:%d p:%d\n",
				ctx->session_id, q->type,
				!list_empty(&ctx->pending_list));
		SDEROT_EVTLOG(ctx->session_id, q->type,
				!list_empty(&ctx->pending_list),
				SDE_ROT_EVTLOG_ERROR);
		sde_rot_mgr_lock(rot_dev->mgr);
		sde_rotator_cancel_all_requests(rot_dev->mgr, ctx->private);
		sde_rot_mgr_unlock(rot_dev->mgr);
		list_for_each_safe(curr, next, &ctx->pending_list) {
			request = container_of(curr, struct sde_rotator_request,
						list);

			SDEDEV_DBG(rot_dev->dev, "cancel request s:%d\n",
					ctx->session_id);
			mutex_unlock(q->lock);
			kthread_cancel_work_sync(&request->submit_work);
			kthread_cancel_work_sync(&request->retire_work);
			mutex_lock(q->lock);
			spin_lock(&ctx->list_lock);
			list_del_init(&request->list);
			list_add_tail(&request->list, &ctx->retired_list);
			spin_unlock(&ctx->list_lock);
		}
	}

	sde_rotator_return_all_buffers(q, VB2_BUF_STATE_ERROR);

	/* clear fence for buffer */
	sde_rotator_resync_timeline(ctx->work_queue.timeline);
	if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
		for (i = 0; i < ctx->nbuf_cap; i++) {
			struct sde_rotator_vbinfo *vbinfo =
					&ctx->vbinfo_cap[i];

			if (vbinfo->fence) {
				/* fence is not used */
				SDEDEV_DBG(rot_dev->dev,
						"put fence s:%d t:%d i:%d\n",
						ctx->session_id, q->type, i);
				sde_rotator_put_sync_fence(vbinfo->fence);
			}
			vbinfo->fence = NULL;
			vbinfo->fd = -1;
		}
	} else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
		for (i = 0; i < ctx->nbuf_out; i++) {
			struct sde_rotator_vbinfo *vbinfo =
					&ctx->vbinfo_out[i];

			if (vbinfo->fence) {
				SDEDEV_DBG(rot_dev->dev,
						"put fence s:%d t:%d i:%d\n",
						ctx->session_id, q->type, i);
				sde_rotator_put_sync_fence(vbinfo->fence);
			}
			vbinfo->fence = NULL;
			vbinfo->fd = -1;
		}
	}
}

/* Videobuf2 queue callbacks. */
static const struct vb2_ops sde_rotator_vb2_q_ops = {
	.queue_setup     = sde_rotator_queue_setup,
	.buf_queue       = sde_rotator_buf_queue,
	.start_streaming = sde_rotator_start_streaming,
	.stop_streaming  = sde_rotator_stop_streaming,
	.wait_prepare	 = vb2_ops_wait_prepare,
	.wait_finish	 = vb2_ops_wait_finish,
	.buf_finish      = sde_rotator_buf_finish,
};

/*
 * sde_rotator_get_userptr - Map and get buffer handler for user pointer buffer.
 * @dev: device allocated in buf_setup.
 * @vaddr: Virtual addr passed from userpsace (in our case ion fd)
 * @size: Size of the buffer
 * @dma_dir: DMA data direction of the given buffer.
 */
static void *sde_rotator_get_userptr(struct device *dev,
	unsigned long vaddr, unsigned long size,
	enum dma_data_direction dma_dir)
{
	struct sde_rotator_ctx *ctx = (struct sde_rotator_ctx *)dev;
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotator_buf_handle *buf;

	buf = kzalloc(sizeof(*buf), GFP_KERNEL);
	if (!buf)
		return ERR_PTR(-ENOMEM);

	buf->fd = vaddr;
	buf->secure = ctx->secure || ctx->secure_camera;
	buf->ctx = ctx;
	buf->rot_dev = rot_dev;
	buf->size = size;
	buf->buffer = dma_buf_get(buf->fd);
	if (IS_ERR_OR_NULL(buf->buffer)) {
		SDEDEV_ERR(rot_dev->dev,
			"fail get dmabuf fd:%d r:%ld\n",
			buf->fd, PTR_ERR(buf->buffer));
		goto error_buf_get;
	}
	SDEDEV_DBG(rot_dev->dev,
			"get dmabuf s:%d fd:%d buf:%pad\n",
			buf->ctx->session_id,
			buf->fd, &buf->buffer);

	return buf;
error_buf_get:
	kfree(buf);
	return ERR_PTR(-ENOMEM);
}

/*
 * sde_rotator_put_userptr - Unmap and free buffer handler.
 * @buf_priv: Buffer handler allocated get_userptr callback.
 */
static void sde_rotator_put_userptr(void *buf_priv)
{
	struct sde_rotator_buf_handle *buf = buf_priv;

	if (IS_ERR_OR_NULL(buf))
		return;

	if (!buf->rot_dev || !buf->ctx) {
		WARN_ON(!buf->rot_dev || !buf->ctx);
		SDEROT_ERR("null rotator device/context\n");
		return;
	}

	SDEDEV_DBG(buf->rot_dev->dev, "put dmabuf s:%d fd:%d buf:%pad\n",
			buf->ctx->session_id,
			buf->fd, &buf->buffer);

	if (buf->buffer) {
		dma_buf_put(buf->buffer);
		buf->buffer = NULL;
	}

	kfree(buf_priv);
}

/* Videobuf2 memory callbacks. */
static struct vb2_mem_ops sde_rotator_vb2_mem_ops = {
	.get_userptr = sde_rotator_get_userptr,
	.put_userptr = sde_rotator_put_userptr,
};

/*
 * sde_rotator_s_ctx_ctrl - set context control variable to v4l2 control
 * @ctx: Pointer to rotator context.
 * @ctx_ctrl: Pointer to context control variable
 * @ctrl: Pointer to v4l2 control variable
 */
static int sde_rotator_s_ctx_ctrl(struct sde_rotator_ctx *ctx,
		s32 *ctx_ctrl, struct v4l2_ctrl *ctrl)
{
	*ctx_ctrl = ctrl->val;
	return 0;
}

/*
 * sde_rotator_s_ctrl - Set control.
 * @ctrl: Pointer to v4l2 control structure.
 */
static int sde_rotator_s_ctrl(struct v4l2_ctrl *ctrl)
{
	struct sde_rotator_ctx *ctx =
		container_of(ctrl->handler,
				struct sde_rotator_ctx, ctrl_handler);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	int ret;

	SDEDEV_DBG(rot_dev->dev, "set %s:%d s:%d\n", ctrl->name, ctrl->val,
			ctx->session_id);

	sde_rot_mgr_lock(rot_dev->mgr);

	switch (ctrl->id) {
	case V4L2_CID_HFLIP:
		ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->hflip, ctrl);
		break;

	case V4L2_CID_VFLIP:
		ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->vflip, ctrl);
		break;

	case V4L2_CID_ROTATE:
		ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->rotate, ctrl);
		break;

	case V4L2_CID_SDE_ROTATOR_SECURE:
		ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->secure, ctrl);
		break;

	case V4L2_CID_SDE_ROTATOR_SECURE_CAMERA:
		ret = sde_rotator_s_ctx_ctrl(ctx, &ctx->secure_camera, ctrl);
		break;
	default:
		v4l2_warn(&rot_dev->v4l2_dev, "invalid control %d\n", ctrl->id);
		ret = -EINVAL;
	}

	sde_rot_mgr_unlock(rot_dev->mgr);
	return ret;
}

/*
 * sde_rotator_ctrl_ops - Control operations.
 */
static const struct v4l2_ctrl_ops sde_rotator_ctrl_ops = {
	.s_ctrl = sde_rotator_s_ctrl,
};

/*
 * sde_rotator_ctrl_secure - Non-secure/Secure.
 */
static const struct v4l2_ctrl_config sde_rotator_ctrl_secure = {
	.ops = &sde_rotator_ctrl_ops,
	.id = V4L2_CID_SDE_ROTATOR_SECURE,
	.name = "Non-secure/Secure Domain",
	.type = V4L2_CTRL_TYPE_INTEGER,
	.def = 0,
	.min = 0,
	.max = 1,
	.step = 1,
};

static const struct v4l2_ctrl_config sde_rotator_ctrl_secure_camera = {
	.ops = &sde_rotator_ctrl_ops,
	.id = V4L2_CID_SDE_ROTATOR_SECURE_CAMERA,
	.name = "Secure Camera content",
	.type = V4L2_CTRL_TYPE_INTEGER,
	.def = 0,
	.min = 0,
	.max = 1,
	.step = 1,
};

/*
 * sde_rotator_ctx_show - show context state.
 */
static ssize_t sde_rotator_ctx_show(struct kobject *kobj,
	struct kobj_attribute *attr, char *buf)
{
	size_t len = PAGE_SIZE;
	int cnt = 0;
	struct sde_rotator_ctx *ctx =
		container_of(kobj, struct sde_rotator_ctx, kobj);

	if (!ctx)
		return cnt;

#define SPRINT(fmt, ...) \
		(cnt += scnprintf(buf + cnt, len - cnt, fmt, ##__VA_ARGS__))

	SPRINT("rotate=%d\n", ctx->rotate);
	SPRINT("hflip=%d\n", ctx->hflip);
	SPRINT("vflip=%d\n", ctx->vflip);
	SPRINT("priority=%d\n", ctx->fh.prio);
	SPRINT("secure=%d\n", ctx->secure);
	SPRINT("timeperframe=%u %u\n", ctx->timeperframe.numerator,
			ctx->timeperframe.denominator);
	SPRINT("nbuf_out=%d\n", ctx->nbuf_out);
	SPRINT("nbuf_cap=%d\n", ctx->nbuf_cap);
	SPRINT("crop_out=%u %u %u %u\n",
			ctx->crop_out.left, ctx->crop_out.top,
			ctx->crop_out.width, ctx->crop_out.height);
	SPRINT("crop_cap=%u %u %u %u\n",
			ctx->crop_cap.left, ctx->crop_cap.top,
			ctx->crop_cap.width, ctx->crop_cap.height);
	SPRINT("fmt_out=%c%c%c%c %u %u %u %u\n",
			(ctx->format_out.fmt.pix.pixelformat>>0)&0xff,
			(ctx->format_out.fmt.pix.pixelformat>>8)&0xff,
			(ctx->format_out.fmt.pix.pixelformat>>16)&0xff,
			(ctx->format_out.fmt.pix.pixelformat>>24)&0xff,
			ctx->format_out.fmt.pix.width,
			ctx->format_out.fmt.pix.height,
			ctx->format_out.fmt.pix.bytesperline,
			ctx->format_out.fmt.pix.sizeimage);
	SPRINT("fmt_cap=%c%c%c%c %u %u %u %u\n",
			(ctx->format_cap.fmt.pix.pixelformat>>0)&0xff,
			(ctx->format_cap.fmt.pix.pixelformat>>8)&0xff,
			(ctx->format_cap.fmt.pix.pixelformat>>16)&0xff,
			(ctx->format_cap.fmt.pix.pixelformat>>24)&0xff,
			ctx->format_cap.fmt.pix.width,
			ctx->format_cap.fmt.pix.height,
			ctx->format_cap.fmt.pix.bytesperline,
			ctx->format_cap.fmt.pix.sizeimage);
	SPRINT("abort_pending=%d\n", ctx->abort_pending);
	SPRINT("command_pending=%d\n", !list_empty(&ctx->pending_list));
	SPRINT("sequence=%u\n",
		sde_rotator_get_timeline_commit_ts(ctx->work_queue.timeline));
	SPRINT("timestamp=%u\n",
		sde_rotator_get_timeline_retire_ts(ctx->work_queue.timeline));
	return cnt;
}

static struct kobj_attribute sde_rotator_ctx_attr =
	__ATTR(state, 0664, sde_rotator_ctx_show, NULL);

static struct attribute *sde_rotator_fs_attrs[] = {
	&sde_rotator_ctx_attr.attr,
	NULL
};

static struct attribute_group sde_rotator_fs_attr_group = {
	.attrs = sde_rotator_fs_attrs
};

/*
 * sde_rotator_ctx_show - sysfs show callback.
 */
static ssize_t sde_rotator_fs_show(struct kobject *kobj,
	struct attribute *attr, char *buf)
{
	ssize_t ret = -EIO;
	struct kobj_attribute *kattr =
			container_of(attr, struct kobj_attribute, attr);
	if (kattr->show)
		ret = kattr->show(kobj, kattr, buf);
	return ret;
}

/*
 * sde_rotator_fs_store - sysfs store callback.
 */
static ssize_t sde_rotator_fs_store(struct kobject *kobj,
	struct attribute *attr, const char *buf, size_t count)
{
	ssize_t ret = -EIO;
	struct kobj_attribute *kattr =
			container_of(attr, struct kobj_attribute, attr);
	if (kattr->store)
		ret = kattr->store(kobj, kattr, buf, count);
	return ret;
}

static const struct sysfs_ops sde_rotator_fs_ops = {
	.show = sde_rotator_fs_show,
	.store = sde_rotator_fs_store,
};

static struct kobj_type sde_rotator_fs_ktype = {
	.sysfs_ops = &sde_rotator_fs_ops,
};

/*
 * sde_rotator_queue_init - m2m_ops queue_setup callback.
 * @priv: Pointer to rotator ctx.
 * @src_vq: vb2 source queue.
 * @dst_vq: vb2 destination queue.
 */
static int sde_rotator_queue_init(void *priv, struct vb2_queue *src_vq,
	struct vb2_queue *dst_vq)
{
	struct sde_rotator_ctx *ctx = priv;
	int ret;

	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	src_vq->io_modes = VB2_USERPTR;
	src_vq->drv_priv = ctx;
	src_vq->mem_ops = &sde_rotator_vb2_mem_ops;
	src_vq->ops = &sde_rotator_vb2_q_ops;
	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
	src_vq->lock = &ctx->rot_dev->lock;
	src_vq->min_buffers_needed = 1;
	src_vq->dev = ctx->rot_dev->dev;

	ret = vb2_queue_init(src_vq);
	if (ret) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail init src queue r:%d\n", ret);
		return ret;
	}

	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	dst_vq->io_modes = VB2_USERPTR;
	dst_vq->drv_priv = ctx;
	dst_vq->mem_ops = &sde_rotator_vb2_mem_ops;
	dst_vq->ops = &sde_rotator_vb2_q_ops;
	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
	dst_vq->lock = &ctx->rot_dev->lock;
	dst_vq->min_buffers_needed = 1;
	src_vq->dev = ctx->rot_dev->dev;

	ret = vb2_queue_init(dst_vq);
	if (ret) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail init dst queue r:%d\n", ret);
		return ret;
	}

	return 0;
}

/*
 * sde_rotator_ctx_open - Rotator device open method.
 * @rot_dev: Pointer to rotator device structure
 * @file: Pointer to file struct (optional)
 * return: Pointer rotator context if success; ptr error code, otherwise.
 */
struct sde_rotator_ctx *sde_rotator_ctx_open(
		struct sde_rotator_device *rot_dev, struct file *file)
{
	struct video_device *video = file ? video_devdata(file) : NULL;
	struct sde_rotator_ctx *ctx;
	struct v4l2_ctrl_handler *ctrl_handler;
	char name[32];
	int i, ret;

	if (atomic_read(&rot_dev->mgr->device_suspended))
		return ERR_PTR(-EPERM);

	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
	if (!ctx)
		return ERR_PTR(-ENOMEM);

	if (mutex_lock_interruptible(&rot_dev->lock)) {
		ret = -ERESTARTSYS;
		goto error_lock;
	}

	/* wait until exclusive ctx, if exists, finishes or timeout */
	while (rot_dev->excl_ctx) {
		SDEROT_DBG("waiting to open %s session %d ...\n",
				file ? "v4l2" : "excl",	rot_dev->session_id);
		mutex_unlock(&rot_dev->lock);
		ret = wait_event_interruptible_timeout(rot_dev->open_wq,
				!rot_dev->excl_ctx,
				msecs_to_jiffies(rot_dev->open_timeout));
		if (ret < 0) {
			goto error_lock;
		} else if (!ret) {
			SDEROT_WARN("timeout to open session %d\n",
					rot_dev->session_id);
			SDEROT_EVTLOG(rot_dev->session_id,
					SDE_ROT_EVTLOG_ERROR);
			ret = -EBUSY;
			goto error_lock;
		} else if (mutex_lock_interruptible(&rot_dev->lock)) {
			ret = -ERESTARTSYS;
			goto error_lock;
		}
	}

	ctx->rot_dev = rot_dev;
	ctx->file = file;

	/* Set context defaults */
	ctx->session_id = rot_dev->session_id++;
	SDEDEV_DBG(ctx->rot_dev->dev, "open %d\n", ctx->session_id);
	ctx->timeperframe.numerator = 1;
	ctx->timeperframe.denominator = SDE_ROTATOR_DEFAULT_FPS;
	ctx->hflip = 0;
	ctx->vflip = 0;
	ctx->rotate = 0;
	ctx->secure = 0;
	ctx->abort_pending = 0;
	ctx->kthread_id = -1;
	ctx->format_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ctx->format_cap.fmt.pix.pixelformat = SDE_PIX_FMT_Y_CBCR_H2V2;
	ctx->format_cap.fmt.pix.width = 640;
	ctx->format_cap.fmt.pix.height = 480;
	ctx->crop_cap.width = 640;
	ctx->crop_cap.height = 480;
	ctx->format_out.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
	ctx->format_out.fmt.pix.pixelformat = SDE_PIX_FMT_Y_CBCR_H2V2;
	ctx->format_out.fmt.pix.width = 640;
	ctx->format_out.fmt.pix.height = 480;
	ctx->crop_out.width = 640;
	ctx->crop_out.height = 480;
	init_waitqueue_head(&ctx->wait_queue);
	spin_lock_init(&ctx->list_lock);
	INIT_LIST_HEAD(&ctx->pending_list);
	INIT_LIST_HEAD(&ctx->retired_list);

	for (i = 0 ; i < ARRAY_SIZE(ctx->requests); i++) {
		struct sde_rotator_request *request = &ctx->requests[i];

		kthread_init_work(&request->submit_work,
				sde_rotator_submit_handler);
		kthread_init_work(&request->retire_work,
				sde_rotator_retire_handler);
		request->ctx = ctx;
		INIT_LIST_HEAD(&request->list);
		list_add_tail(&request->list, &ctx->retired_list);
	}

	if (ctx->file) {
		v4l2_fh_init(&ctx->fh, video);
		file->private_data = &ctx->fh;
		v4l2_fh_add(&ctx->fh);

		ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rot_dev->m2m_dev,
			ctx, sde_rotator_queue_init);
		if (IS_ERR_OR_NULL(ctx->fh.m2m_ctx)) {
			ret = PTR_ERR(ctx->fh.m2m_ctx);
			ctx->fh.m2m_ctx = NULL;
			goto error_m2m_init;
		}
	}

	ret = kobject_init_and_add(&ctx->kobj, &sde_rotator_fs_ktype,
			&rot_dev->dev->kobj, "session_%d", ctx->session_id);
	if (ret) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail initialize context kobject\n");
		goto error_kobj_init;
	}

	ret = sysfs_create_group(&ctx->kobj, &sde_rotator_fs_attr_group);
	if (ret) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail register rotator sysfs nodes\n");
		goto error_create_sysfs;
	}

	for (i = 0; i < MAX_ROT_OPEN_SESSION; i++) {
		if (rot_dev->kthread_free[i]) {
			rot_dev->kthread_free[i] = false;
			ctx->kthread_id = i;
			ctx->work_queue.rot_kw = &rot_dev->rot_kw[i];
			ctx->work_queue.rot_thread = rot_dev->rot_thread[i];
			break;
		}
	}

	if (ctx->kthread_id < 0) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail to acquire the kthread\n");
		ret = -EINVAL;
		goto error_alloc_kthread;
	}

	snprintf(name, sizeof(name), "%d_%d", rot_dev->dev->id,
			ctx->session_id);
	ctx->work_queue.timeline = sde_rotator_create_timeline(name);
	if (!ctx->work_queue.timeline)
		SDEDEV_DBG(ctx->rot_dev->dev, "timeline is not available\n");

	sde_rot_mgr_lock(rot_dev->mgr);
	ret = sde_rotator_session_open(rot_dev->mgr, &ctx->private,
			ctx->session_id, &ctx->work_queue);
	if (ret < 0) {
		SDEDEV_ERR(ctx->rot_dev->dev, "fail open session\n");
		goto error_open_session;
	}
	sde_rot_mgr_unlock(rot_dev->mgr);

	if (ctx->file) {
		/* Create control */
		ctrl_handler = &ctx->ctrl_handler;
		v4l2_ctrl_handler_init(ctrl_handler, 4);
		v4l2_ctrl_new_std(ctrl_handler,
			&sde_rotator_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
		v4l2_ctrl_new_std(ctrl_handler,
			&sde_rotator_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
		v4l2_ctrl_new_std(ctrl_handler,
			&sde_rotator_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0);
		v4l2_ctrl_new_custom(ctrl_handler,
			&sde_rotator_ctrl_secure, NULL);
		v4l2_ctrl_new_custom(ctrl_handler,
			&sde_rotator_ctrl_secure_camera, NULL);
		if (ctrl_handler->error) {
			ret = ctrl_handler->error;
			v4l2_ctrl_handler_free(ctrl_handler);
			goto error_ctrl_handler;
		}
		ctx->fh.ctrl_handler = ctrl_handler;
		v4l2_ctrl_handler_setup(ctrl_handler);
	} else {
		/* acquire exclusive context */
		SDEDEV_DBG(rot_dev->dev, "acquire exclusive session id:%u\n",
				ctx->session_id);
		SDEROT_EVTLOG(ctx->session_id);
		rot_dev->excl_ctx = ctx;
	}

	mutex_unlock(&rot_dev->lock);

	SDEDEV_DBG(ctx->rot_dev->dev, "SDE v4l2 rotator open success\n");

	ATRACE_BEGIN(ctx->kobj.name);

	return ctx;
error_ctrl_handler:
error_open_session:
	sde_rot_mgr_unlock(rot_dev->mgr);
	sde_rotator_destroy_timeline(ctx->work_queue.timeline);
	rot_dev->kthread_free[ctx->kthread_id] = true;
	ctx->kthread_id = -1;
error_alloc_kthread:
	sysfs_remove_group(&ctx->kobj, &sde_rotator_fs_attr_group);
error_create_sysfs:
	kobject_put(&ctx->kobj);
error_kobj_init:
	if (ctx->file) {
		v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
		ctx->fh.m2m_ctx = NULL;
	}
error_m2m_init:
	if (ctx->file) {
		v4l2_fh_del(&ctx->fh);
		v4l2_fh_exit(&ctx->fh);
	}
	mutex_unlock(&rot_dev->lock);
error_lock:
	kfree(ctx);
	ctx = NULL;
	return ERR_PTR(ret);
}

/*
 * sde_rotator_ctx_release - Rotator device release method.
 * @ctx: Pointer rotator context.
 * @file: Pointer to file struct (optional)
 * return: 0 if success; error code, otherwise
 */
static int sde_rotator_ctx_release(struct sde_rotator_ctx *ctx,
		struct file *file)
{
	struct sde_rotator_device *rot_dev;
	u32 session_id;
	struct list_head *curr, *next;

	if (!ctx) {
		SDEROT_DBG("ctx is NULL\n");
		return -EINVAL;
	}

	rot_dev = ctx->rot_dev;
	session_id = ctx->session_id;

	ATRACE_END(ctx->kobj.name);

	SDEDEV_DBG(rot_dev->dev, "release s:%d\n", session_id);
	mutex_lock(&rot_dev->lock);
	if (rot_dev->excl_ctx == ctx) {
		SDEDEV_DBG(rot_dev->dev, "release exclusive session id:%u\n",
				session_id);
		SDEROT_EVTLOG(session_id);
		rot_dev->excl_ctx = NULL;
	}
	if (ctx->file) {
		v4l2_ctrl_handler_free(&ctx->ctrl_handler);
		SDEDEV_DBG(rot_dev->dev, "release streams s:%d\n", session_id);
		if (ctx->fh.m2m_ctx) {
			v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx,
				V4L2_BUF_TYPE_VIDEO_OUTPUT);
			v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx,
				V4L2_BUF_TYPE_VIDEO_CAPTURE);
		}
	}
	mutex_unlock(&rot_dev->lock);
	SDEDEV_DBG(rot_dev->dev, "release submit work s:%d\n", session_id);
	list_for_each_safe(curr, next, &ctx->pending_list) {
		struct sde_rotator_request *request =
			container_of(curr, struct sde_rotator_request, list);

		SDEDEV_DBG(rot_dev->dev, "release submit work s:%d\n",
				session_id);
		kthread_cancel_work_sync(&request->submit_work);
	}
	SDEDEV_DBG(rot_dev->dev, "release session s:%d\n", session_id);
	sde_rot_mgr_lock(rot_dev->mgr);
	sde_rotator_session_close(rot_dev->mgr, ctx->private, session_id);
	sde_rot_mgr_unlock(rot_dev->mgr);
	SDEDEV_DBG(rot_dev->dev, "release retire work s:%d\n", session_id);
	list_for_each_safe(curr, next, &ctx->pending_list) {
		struct sde_rotator_request *request =
			container_of(curr, struct sde_rotator_request, list);

		SDEDEV_DBG(rot_dev->dev, "release retire work s:%d\n",
				session_id);
		kthread_cancel_work_sync(&request->retire_work);
	}
	mutex_lock(&rot_dev->lock);
	SDEDEV_DBG(rot_dev->dev, "release context s:%d\n", session_id);
	sde_rotator_destroy_timeline(ctx->work_queue.timeline);
	if (ctx->kthread_id >= 0 && ctx->work_queue.rot_kw) {
		kthread_flush_worker(ctx->work_queue.rot_kw);
		rot_dev->kthread_free[ctx->kthread_id] = true;
	}
	sysfs_remove_group(&ctx->kobj, &sde_rotator_fs_attr_group);
	kobject_put(&ctx->kobj);
	if (ctx->file) {
		if (ctx->fh.m2m_ctx)
			v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
		if (ctx->fh.vdev) {
			v4l2_fh_del(&ctx->fh);
			v4l2_fh_exit(&ctx->fh);
		}
	}
	kfree(ctx->vbinfo_out);
	kfree(ctx->vbinfo_cap);
	kfree(ctx);
	wake_up_interruptible(&rot_dev->open_wq);
	mutex_unlock(&rot_dev->lock);
	SDEDEV_DBG(rot_dev->dev, "release complete s:%d\n", session_id);
	return 0;
}

/*
 * sde_rotator_update_retire_sequence - update retired sequence of the context
 *	referenced in the request, and wake up any waiting for update event
 * @request: Pointer to rotator request
 */
static void sde_rotator_update_retire_sequence(
		struct sde_rotator_request *request)
{
	struct sde_rotator_ctx *ctx;

	if (!request || !request->ctx) {
		SDEROT_ERR("invalid parameters\n");
		return;
	}

	ctx = request->ctx;
	ctx->retired_sequence_id = request->sequence_id;

	wake_up(&ctx->wait_queue);

	SDEROT_DBG("update sequence s:%d.%d\n",
				ctx->session_id, ctx->retired_sequence_id);
}

/*
 * sde_rotator_retire_request - retire the given rotator request with
 *	device mutex locked
 * @request: Pointer to rotator request
 */
static void sde_rotator_retire_request(struct sde_rotator_request *request)
{
	struct sde_rotator_ctx *ctx;

	if (!request || !request->ctx) {
		SDEROT_ERR("invalid parameters\n");
		return;
	}

	ctx = request->ctx;

	request->req = NULL;
	request->sequence_id = 0;
	request->committed = false;
	spin_lock(&ctx->list_lock);
	list_del_init(&request->list);
	list_add_tail(&request->list, &ctx->retired_list);
	spin_unlock(&ctx->list_lock);

	wake_up(&ctx->wait_queue);

	SDEROT_DBG("retire request s:%d.%d\n",
				ctx->session_id, ctx->retired_sequence_id);
}

/*
 * sde_rotator_is_request_retired - Return true if given request already expired
 * @request: Pointer to rotator request
 */
static bool sde_rotator_is_request_retired(struct sde_rotator_request *request)
{
	struct sde_rotator_ctx *ctx;
	u32 sequence_id;
	s32 retire_delta;

	if (!request || !request->ctx)
		return true;

	ctx = request->ctx;
	sequence_id = request->sequence_id;

	retire_delta = (s32) (ctx->retired_sequence_id - sequence_id);

	SDEROT_DBG("sequence:%u/%u\n", sequence_id, ctx->retired_sequence_id);

	return retire_delta >= 0;
}

/*
 * sde_rotator_inline_open - open inline rotator session
 * @pdev: Pointer to rotator platform device
 * @video_mode: true if video mode is requested
 * return: Pointer to new rotator session context
 */
void *sde_rotator_inline_open(struct platform_device *pdev)
{
	struct sde_rotator_device *rot_dev;
	struct sde_rotator_ctx *ctx;
	int rc;

	if (!pdev) {
		SDEROT_ERR("invalid platform device\n");
		return ERR_PTR(-EINVAL);
	}

	rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
	if (!rot_dev) {
		SDEROT_ERR("invalid rotator device\n");
		return ERR_PTR(-EINVAL);
	}

	ctx = sde_rotator_ctx_open(rot_dev, NULL);
	if (IS_ERR_OR_NULL(ctx)) {
		rc = PTR_ERR(ctx);
		SDEROT_ERR("failed to open rotator context %d\n", rc);
		goto rotator_open_error;
	}

	ctx->slice = llcc_slice_getd(LLCC_ROTATOR);
	if (IS_ERR(ctx->slice)) {
		rc = PTR_ERR(ctx->slice);
		SDEROT_ERR("failed to get system cache %d\n", rc);
		goto slice_getd_error;
	}

	if (!rot_dev->disable_syscache) {
		rc = llcc_slice_activate(ctx->slice);
		if (rc) {
			SDEROT_ERR("failed to activate slice %d\n", rc);
			goto activate_error;
		}
		SDEROT_DBG("scid %d size %zukb\n",
				llcc_get_slice_id(ctx->slice),
				llcc_get_slice_size(ctx->slice));
	} else {
		SDEROT_DBG("syscache bypassed\n");
	}

	SDEROT_EVTLOG(ctx->session_id, llcc_get_slice_id(ctx->slice),
			llcc_get_slice_size(ctx->slice),
			rot_dev->disable_syscache);

	return ctx;

activate_error:
	llcc_slice_putd(ctx->slice);
	ctx->slice = NULL;
slice_getd_error:
	sde_rotator_ctx_release(ctx, NULL);
rotator_open_error:
	return ERR_PTR(rc);
}
EXPORT_SYMBOL(sde_rotator_inline_open);

int sde_rotator_inline_release(void *handle)
{
	struct sde_rotator_device *rot_dev;
	struct sde_rotator_ctx *ctx;

	if (!handle) {
		SDEROT_ERR("invalid rotator ctx\n");
		return -EINVAL;
	}

	ctx = handle;
	rot_dev = ctx->rot_dev;

	if (!rot_dev) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	if (ctx->slice) {
		if (!rot_dev->disable_syscache)
			llcc_slice_deactivate(ctx->slice);
		llcc_slice_putd(ctx->slice);
		ctx->slice = NULL;
	}

	SDEROT_EVTLOG(ctx->session_id);

	return sde_rotator_ctx_release(ctx, NULL);
}
EXPORT_SYMBOL(sde_rotator_inline_release);

/*
 * sde_rotator_inline_get_dst_pixfmt - determine output pixel format
 * @pdev: Pointer to platform device
 * @src_pixfmt: input pixel format
 * @dst_pixfmt: Pointer to output pixel format (output)
 * return: 0 if success; error code otherwise
 */
int sde_rotator_inline_get_dst_pixfmt(struct platform_device *pdev,
		u32 src_pixfmt, u32 *dst_pixfmt)
{
	int rc;

	if (!src_pixfmt || !dst_pixfmt)
		return -EINVAL;

	rc = sde_rot_get_base_tilea5x_pixfmt(src_pixfmt, dst_pixfmt);
	if (rc)
		return rc;

	/*
	 * Currently, NV21 tile is not supported as output; hence,
	 * override with NV12 tile.
	 */
	if (*dst_pixfmt == SDE_PIX_FMT_Y_CRCB_H2V2_TILE)
		*dst_pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_TILE;

	return 0;
}
EXPORT_SYMBOL(sde_rotator_inline_get_dst_pixfmt);

/*
 * sde_rotator_inline_get_downscale_caps - get scaling capability
 * @pdev: Pointer to platform device
 * @caps: string buffer for capability
 * @len: length of string buffer
 * return: length of capability string
 */
int sde_rotator_inline_get_downscale_caps(struct platform_device *pdev,
		char *caps, int len)
{
	struct sde_rotator_device *rot_dev;
	int rc;

	if (!pdev) {
		SDEROT_ERR("invalid platform device\n");
		return -EINVAL;
	}

	rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	rc = sde_rotator_get_downscale_caps(rot_dev->mgr, caps, len);
	sde_rot_mgr_unlock(rot_dev->mgr);

	return rc;
}
EXPORT_SYMBOL(sde_rotator_inline_get_downscale_caps);

/*
 * sde_rotator_inline_get_maxlinewidth - get maximum line width of rotator
 * @pdev: Pointer to platform device
 * return: maximum line width
 */
int sde_rotator_inline_get_maxlinewidth(struct platform_device *pdev)
{
	struct sde_rotator_device *rot_dev;
	int maxlinewidth;

	if (!pdev) {
		SDEROT_ERR("invalid platform device\n");
		return -EINVAL;
	}

	rot_dev = (struct sde_rotator_device *)platform_get_drvdata(pdev);
	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	maxlinewidth = sde_rotator_get_maxlinewidth(rot_dev->mgr);
	sde_rot_mgr_unlock(rot_dev->mgr);

	return maxlinewidth;
}
EXPORT_SYMBOL(sde_rotator_inline_get_maxlinewidth);

/*
 * sde_rotator_inline_get_pixfmt_caps - get pixel format capability
 * @pdev: Pointer to platform device
 * @pixfmt: array of pixel format buffer
 * @len: length of pixel format buffer
 * return: length of pixel format capability if success; error code otherwise
 */
int sde_rotator_inline_get_pixfmt_caps(struct platform_device *pdev,
		bool input, u32 *pixfmts, int len)
{
	struct sde_rotator_device *rot_dev;
	u32 i, pixfmt;

	if (!pdev) {
		SDEROT_ERR("invalid platform device\n");
		return -EINVAL;
	}

	rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	for (i = 0;; i++) {
		pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, input,
				SDE_ROTATOR_MODE_SBUF);
		if (!pixfmt)
			break;
		if (pixfmts && i < len)
			pixfmts[i] = pixfmt;
	}
	sde_rot_mgr_unlock(rot_dev->mgr);

	return i;
}
EXPORT_SYMBOL(sde_rotator_inline_get_pixfmt_caps);

/*
 * _sde_rotator_inline_cleanup - perform inline related request cleanup
 *	This function assumes rot_dev->mgr lock has been taken when called.
 * @handle: Pointer to rotator context
 * @request: Pointer to rotation request
 * return: 0 if success; -EAGAIN if cleanup should be retried
 */
static int _sde_rotator_inline_cleanup(void *handle,
		struct sde_rotator_request *request)
{
	struct sde_rotator_ctx *ctx;
	struct sde_rotator_device *rot_dev;
	int ret;

	if (!handle || !request) {
		SDEROT_ERR("invalid rotator handle/request\n");
		return -EINVAL;
	}

	ctx = handle;
	rot_dev = ctx->rot_dev;

	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	if (request->committed) {
		/* wait until request is finished */
		sde_rot_mgr_unlock(rot_dev->mgr);
		mutex_unlock(&rot_dev->lock);
		ret = wait_event_timeout(ctx->wait_queue,
			sde_rotator_is_request_retired(request),
			msecs_to_jiffies(rot_dev->streamoff_timeout));
		mutex_lock(&rot_dev->lock);
		sde_rot_mgr_lock(rot_dev->mgr);

		if (!ret) {
			SDEROT_ERR("timeout w/o retire s:%d\n",
					ctx->session_id);
			SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
			sde_rotator_abort_inline_request(rot_dev->mgr,
					ctx->private, request->req);
			return -EAGAIN;
		} else if (ret == 1) {
			SDEROT_ERR("timeout w/ retire s:%d\n", ctx->session_id);
			SDEROT_EVTLOG(ctx->session_id, SDE_ROT_EVTLOG_ERROR);
		}
	}

	sde_rotator_req_finish(rot_dev->mgr, ctx->private, request->req);
	sde_rotator_retire_request(request);
	return 0;
}

/*
 * sde_rotator_inline_commit - commit given rotator command
 * @handle: Pointer to rotator context
 * @cmd: Pointer to rotator command
 * @cmd_type: command type - validate/prepare/commit/cleanup
 * return: 0 if success; error code otherwise
 */
int sde_rotator_inline_commit(void *handle, struct sde_rotator_inline_cmd *cmd,
		enum sde_rotator_inline_cmd_type cmd_type)
{
	struct sde_rotator_ctx *ctx;
	struct sde_rotator_device *rot_dev;
	struct sde_rotator_request *request = NULL;
	struct sde_rot_entry_container *req = NULL;
	struct sde_rotation_config rotcfg;
	struct sde_rot_trace_entry rot_trace;
	ktime_t *ts;
	u32 flags = 0;
	int i, ret = 0;

	if (!handle || !cmd) {
		SDEROT_ERR("invalid rotator handle/cmd\n");
		return -EINVAL;
	}

	ctx = handle;
	rot_dev = ctx->rot_dev;

	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return -EINVAL;
	}

	SDEROT_DBG(
		"s:%d.%u src:(%u,%u,%u,%u)/%ux%u/%c%c%c%c dst:(%u,%u,%u,%u)/%c%c%c%c r:%d f:%d/%d s:%d fps:%u clk:%llu bw:%llu prefill:%llu wb:%d vid:%d cmd:%d\n",
		ctx->session_id, cmd->sequence_id,
		cmd->src_rect_x, cmd->src_rect_y,
		cmd->src_rect_w, cmd->src_rect_h,
		cmd->src_width, cmd->src_height,
		cmd->src_pixfmt >> 0, cmd->src_pixfmt >> 8,
		cmd->src_pixfmt >> 16, cmd->src_pixfmt >> 24,
		cmd->dst_rect_x, cmd->dst_rect_y,
		cmd->dst_rect_w, cmd->dst_rect_h,
		cmd->dst_pixfmt >> 0, cmd->dst_pixfmt >> 8,
		cmd->dst_pixfmt >> 16, cmd->dst_pixfmt >> 24,
		cmd->rot90, cmd->hflip, cmd->vflip, cmd->secure, cmd->fps,
		cmd->clkrate, cmd->data_bw, cmd->prefill_bw,
		cmd->dst_writeback, cmd->video_mode, cmd_type);
	SDEROT_EVTLOG(ctx->session_id, cmd->sequence_id,
		cmd->src_rect_x, cmd->src_rect_y,
		cmd->src_rect_w, cmd->src_rect_h,
		cmd->src_pixfmt,
		cmd->dst_rect_w, cmd->dst_rect_h,
		cmd->dst_pixfmt,
		cmd->fps, cmd->clkrate, cmd->data_bw, cmd->prefill_bw,
		(cmd->rot90 << 0) | (cmd->hflip << 1) | (cmd->vflip << 2) |
		(cmd->secure << 3) | (cmd->dst_writeback << 4) |
		(cmd->video_mode << 5) |
		(cmd_type << 24));

	mutex_lock(&rot_dev->lock);
	sde_rot_mgr_lock(rot_dev->mgr);

	if (cmd_type == SDE_ROTATOR_INLINE_CMD_VALIDATE ||
			cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT) {

		struct sde_rotation_item item;
		struct sde_rotator_statistics *stats = &rot_dev->stats;
		int scid = llcc_get_slice_id(ctx->slice);

		/* allocate slot for timestamp */
		ts = stats->ts[stats->count % SDE_ROTATOR_NUM_EVENTS];
		if (cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT)
			stats->count++;

		if (cmd->rot90)
			flags |= SDE_ROTATION_90;
		if (cmd->hflip)
			flags |= SDE_ROTATION_FLIP_LR;
		if (cmd->vflip)
			flags |= SDE_ROTATION_FLIP_UD;
		if (cmd->secure)
			flags |= SDE_ROTATION_SECURE;

		flags |= SDE_ROTATION_EXT_PERF;

		/* fill in item work structure */
		memset(&item, 0, sizeof(struct sde_rotation_item));
		item.flags = flags | SDE_ROTATION_EXT_IOVA;
		item.trigger = cmd->video_mode ? SDE_ROTATOR_TRIGGER_VIDEO :
				SDE_ROTATOR_TRIGGER_COMMAND;
		item.prefill_bw = cmd->prefill_bw;
		item.session_id = ctx->session_id;
		item.sequence_id = cmd->sequence_id;
		item.src_rect.x = cmd->src_rect_x;
		item.src_rect.y = cmd->src_rect_y;
		item.src_rect.w = cmd->src_rect_w;
		item.src_rect.h = cmd->src_rect_h;
		item.input.width = cmd->src_width;
		item.input.height = cmd->src_height;
		item.input.format = cmd->src_pixfmt;

		for (i = 0; i < SDE_ROTATOR_INLINE_PLANE_MAX; i++) {
			item.input.planes[i].addr = cmd->src_addr[i];
			item.input.planes[i].len = cmd->src_len[i];
			item.input.planes[i].fd = -1;
		}
		item.input.plane_count = cmd->src_planes;
		item.input.comp_ratio.numer = 1;
		item.input.comp_ratio.denom = 1;

		item.output.width = cmd->dst_rect_x + cmd->dst_rect_w;
		item.output.height = cmd->dst_rect_y + cmd->dst_rect_h;
		item.dst_rect.x = cmd->dst_rect_x;
		item.dst_rect.y = cmd->dst_rect_y;
		item.dst_rect.w = cmd->dst_rect_w;
		item.dst_rect.h = cmd->dst_rect_h;
		item.output.sbuf = true;
		item.output.scid = scid;
		item.output.writeback = cmd->dst_writeback;
		item.output.format = cmd->dst_pixfmt;

		for (i = 0; i < SDE_ROTATOR_INLINE_PLANE_MAX; i++) {
			item.output.planes[i].addr = cmd->dst_addr[i];
			item.output.planes[i].len = cmd->dst_len[i];
			item.output.planes[i].fd = -1;
		}
		item.output.plane_count = cmd->dst_planes;
		item.output.comp_ratio.numer = 1;
		item.output.comp_ratio.denom = 1;
		item.sequence_id = ++(ctx->commit_sequence_id);
		item.ts = ts;

		req = sde_rotator_req_init(rot_dev->mgr, ctx->private,
				&item, 1, 0);
		if (IS_ERR_OR_NULL(req)) {
			SDEROT_ERR("fail allocate request s:%d\n",
					ctx->session_id);
			ret = -ENOMEM;
			goto error_init_request;
		}

		/* initialize session configuration */
		memset(&rotcfg, 0, sizeof(struct sde_rotation_config));
		rotcfg.flags = flags;
		rotcfg.frame_rate = cmd->fps;
		rotcfg.clk_rate = cmd->clkrate;
		rotcfg.data_bw = cmd->data_bw;
		rotcfg.session_id = ctx->session_id;
		rotcfg.input.width = cmd->src_rect_w;
		rotcfg.input.height = cmd->src_rect_h;
		rotcfg.input.format = cmd->src_pixfmt;
		rotcfg.input.comp_ratio.numer = 1;
		rotcfg.input.comp_ratio.denom = 1;
		rotcfg.output.width = cmd->dst_rect_w;
		rotcfg.output.height = cmd->dst_rect_h;
		rotcfg.output.format = cmd->dst_pixfmt;
		rotcfg.output.comp_ratio.numer = 1;
		rotcfg.output.comp_ratio.denom = 1;
		rotcfg.output.sbuf = true;
	}

	if (cmd_type == SDE_ROTATOR_INLINE_CMD_VALIDATE) {

		ret = sde_rotator_session_validate(rot_dev->mgr,
				ctx->private, &rotcfg);
		if (ret) {
			SDEROT_WARN("fail session validation s:%d\n",
					ctx->session_id);
			goto error_session_validate;
		}

		devm_kfree(rot_dev->dev, req);
		req = NULL;

	} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_COMMIT) {

		if (memcmp(&rotcfg, &ctx->rotcfg, sizeof(rotcfg))) {
			ret = sde_rotator_session_config(rot_dev->mgr,
					ctx->private, &rotcfg);
			if (ret) {
				SDEROT_ERR("fail session config s:%d\n",
						ctx->session_id);
				goto error_session_config;
			}

			ctx->rotcfg = rotcfg;
		}

		request = list_first_entry_or_null(&ctx->retired_list,
				struct sde_rotator_request, list);
		if (!request) {
			/* should not happen */
			ret = -ENOMEM;
			SDEROT_ERR("no free request s:%d\n", ctx->session_id);
			goto error_retired_list;
		}

		request->req = req;
		request->sequence_id = req->entries[0].item.sequence_id;

		spin_lock(&ctx->list_lock);
		list_del_init(&request->list);
		list_add_tail(&request->list, &ctx->pending_list);
		spin_unlock(&ctx->list_lock);

		ts = req->entries[0].item.ts;
		if (ts) {
			ts[SDE_ROTATOR_TS_SRCQB] = ktime_get();
			ts[SDE_ROTATOR_TS_DSTQB] = ktime_get();
			ts[SDE_ROTATOR_TS_FENCE] = ktime_get();
		} else {
			SDEROT_ERR("invalid stats timestamp\n");
		}
		req->retire_kw = ctx->work_queue.rot_kw;
		req->retire_work = &request->retire_work;

		/* Set values to pass to trace */
		rot_trace.wb_idx = req->entries[0].item.wb_idx;
		rot_trace.flags = req->entries[0].item.flags;
		rot_trace.input_format = req->entries[0].item.input.format;
		rot_trace.input_width = req->entries[0].item.input.width;
		rot_trace.input_height = req->entries[0].item.input.height;
		rot_trace.src_x = req->entries[0].item.src_rect.x;
		rot_trace.src_y = req->entries[0].item.src_rect.y;
		rot_trace.src_w = req->entries[0].item.src_rect.w;
		rot_trace.src_h = req->entries[0].item.src_rect.h;
		rot_trace.output_format = req->entries[0].item.output.format;
		rot_trace.output_width = req->entries[0].item.output.width;
		rot_trace.output_height = req->entries[0].item.output.height;
		rot_trace.dst_x = req->entries[0].item.dst_rect.x;
		rot_trace.dst_y = req->entries[0].item.dst_rect.y;
		rot_trace.dst_w = req->entries[0].item.dst_rect.w;
		rot_trace.dst_h = req->entries[0].item.dst_rect.h;


		trace_rot_entry_fence(
			ctx->session_id, cmd->sequence_id, &rot_trace);

		ret = sde_rotator_handle_request_common(
				rot_dev->mgr, ctx->private, req);
		if (ret) {
			SDEROT_ERR("fail handle request s:%d\n",
					ctx->session_id);
			goto error_handle_request;
		}

		sde_rotator_req_reset_start(rot_dev->mgr, req);

		sde_rotator_queue_request(rot_dev->mgr, ctx->private, req);

		request->committed = true;

		/* save request in private handle */
		cmd->priv_handle = request;

	} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_START) {
		if (!cmd->priv_handle) {
			ret = -EINVAL;
			SDEROT_ERR("invalid private handle\n");
			goto error_invalid_handle;
		}

		request = cmd->priv_handle;
		sde_rotator_req_set_start(rot_dev->mgr, request->req);
	} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_CLEANUP) {
		if (!cmd->priv_handle) {
			ret = -EINVAL;
			SDEROT_ERR("invalid private handle\n");
			goto error_invalid_handle;
		}

		request = cmd->priv_handle;

		/* attempt single retry if first cleanup attempt failed */
		if (_sde_rotator_inline_cleanup(handle, request) == -EAGAIN)
			_sde_rotator_inline_cleanup(handle, request);

		cmd->priv_handle = NULL;
	} else if (cmd_type == SDE_ROTATOR_INLINE_CMD_ABORT) {
		if (!cmd->priv_handle) {
			ret = -EINVAL;
			SDEROT_ERR("invalid private handle\n");
			goto error_invalid_handle;
		}

		request = cmd->priv_handle;
		if (!sde_rotator_is_request_retired(request))
			sde_rotator_abort_inline_request(rot_dev->mgr,
					ctx->private, request->req);
	}

	sde_rot_mgr_unlock(rot_dev->mgr);
	mutex_unlock(&rot_dev->lock);
	return 0;

error_handle_request:
	sde_rotator_update_retire_sequence(request);
	sde_rotator_retire_request(request);
error_retired_list:
error_session_validate:
error_session_config:
	devm_kfree(rot_dev->dev, req);
error_invalid_handle:
error_init_request:
	sde_rot_mgr_unlock(rot_dev->mgr);
	mutex_unlock(&rot_dev->lock);
	return ret;
}
EXPORT_SYMBOL(sde_rotator_inline_commit);

void sde_rotator_inline_reg_dump(struct platform_device *pdev)
{
	struct sde_rotator_device *rot_dev;

	if (!pdev) {
		SDEROT_ERR("invalid platform device\n");
		return;
	}

	rot_dev = (struct sde_rotator_device *) platform_get_drvdata(pdev);
	if (!rot_dev || !rot_dev->mgr) {
		SDEROT_ERR("invalid rotator device\n");
		return;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	sde_rotator_core_dump(rot_dev->mgr);
	sde_rot_mgr_unlock(rot_dev->mgr);
}
EXPORT_SYMBOL(sde_rotator_inline_reg_dump);

/*
 * sde_rotator_open - Rotator device open method.
 * @file: Pointer to file struct.
 */
static int sde_rotator_open(struct file *file)
{
	struct sde_rotator_device *rot_dev = video_drvdata(file);
	struct sde_rotator_ctx *ctx;
	int ret = 0;

	ctx = sde_rotator_ctx_open(rot_dev, file);
	if (IS_ERR_OR_NULL(ctx)) {
		SDEDEV_DBG(rot_dev->dev, "failed to open %d\n", ret);
		ret = PTR_ERR(ctx);
	}

	return ret;
}

/*
 * sde_rotator_release - Rotator device release method.
 * @file: Pointer to file struct.
 */
static int sde_rotator_release(struct file *file)
{
	struct sde_rotator_ctx *ctx =
			sde_rotator_ctx_from_fh(file->private_data);

	return sde_rotator_ctx_release(ctx, file);
}

/*
 * sde_rotator_poll - rotator device pool method.
 * @file: Pointer to file struct.
 * @wait: Pointer to poll table struct.
 */
static unsigned int sde_rotator_poll(struct file *file,
	struct poll_table_struct *wait)
{
	struct sde_rotator_device *rot_dev = video_drvdata(file);
	struct sde_rotator_ctx *ctx =
			sde_rotator_ctx_from_fh(file->private_data);
	int ret;

	mutex_lock(&rot_dev->lock);
	ret = v4l2_m2m_poll(file, ctx->fh.m2m_ctx, wait);
	mutex_unlock(&rot_dev->lock);
	return ret;
}

/* rotator device file operations callbacks */
static const struct v4l2_file_operations sde_rotator_fops = {
	.owner          = THIS_MODULE,
	.open           = sde_rotator_open,
	.release        = sde_rotator_release,
	.poll           = sde_rotator_poll,
	.unlocked_ioctl = video_ioctl2,
#if IS_ENABLED(CONFIG_COMPAT)
	.compat_ioctl32 = sde_rotator_compat_ioctl32,
#endif /* CONFIG_COMPAT */
};

/*
 * sde_rotator_querycap - V4l2 ioctl query capability handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @cap: Pointer to v4l2_capability struct need to be filled.
 */
static int sde_rotator_querycap(struct file *file,
	void *fh, struct v4l2_capability *cap)
{
	cap->bus_info[0] = 0;
	strlcpy(cap->driver, SDE_ROTATOR_DRV_NAME, sizeof(cap->driver));
	strlcpy(cap->card, SDE_ROTATOR_DRV_NAME, sizeof(cap->card));
	cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M |
			V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_CAPTURE;
	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;

	return 0;
}

/*
 * sde_rotator_enum_fmt_vid_cap - V4l2 ioctl enumerate output format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_fmtdesc struct need to be filled.
 */
static int sde_rotator_enum_fmt_vid_cap(struct file *file,
	void *fh, struct v4l2_fmtdesc *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_mdp_format_params *fmt;
	u32 i, index, pixfmt;
	bool found = false;

	for (i = 0, index = 0; index <= f->index; i++) {
		pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, false,
				SDE_ROTATOR_MODE_OFFLINE);
		if (!pixfmt)
			return -EINVAL;

		fmt = sde_get_format_params(pixfmt);
		if (!fmt)
			return -EINVAL;

		if (sde_mdp_is_private_format(fmt))
			continue;

		if (index == f->index) {
			found = true;
			break;
		}

		index++;
	}

	if (!found)
		return -EINVAL;

	f->pixelformat = pixfmt;
	strlcpy(f->description, fmt->description, sizeof(f->description));

	return 0;
}

/*
 * sde_rotator_enum_fmt_vid_out - V4l2 ioctl enumerate capture format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_fmtdesc struct need to be filled.
 */
static int sde_rotator_enum_fmt_vid_out(struct file *file,
	void *fh, struct v4l2_fmtdesc *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_mdp_format_params *fmt;
	u32 i, index, pixfmt;
	bool found = false;

	for (i = 0, index = 0; index <= f->index; i++) {
		pixfmt = sde_rotator_get_pixfmt(rot_dev->mgr, i, true,
				SDE_ROTATOR_MODE_OFFLINE);
		if (!pixfmt)
			return -EINVAL;

		fmt = sde_get_format_params(pixfmt);
		if (!fmt)
			return -EINVAL;

		if (sde_mdp_is_private_format(fmt))
			continue;

		if (index == f->index) {
			found = true;
			break;
		}

		index++;
	}

	if (!found)
		return -EINVAL;

	f->pixelformat = pixfmt;
	strlcpy(f->description, fmt->description, sizeof(f->description));

	return 0;
}

/*
 * sde_rotator_g_fmt_cap - V4l2 ioctl get capture format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct need to be filled.
 */
static int sde_rotator_g_fmt_cap(struct file *file, void *fh,
	struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	*f = ctx->format_cap;

	return 0;
}

/*
 * sde_rotator_g_fmt_out - V4l2 ioctl get output format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct need to be filled.
 */
static int sde_rotator_g_fmt_out(struct file *file, void *fh,
	struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	*f = ctx->format_out;

	return 0;
}

/*
 * sde_rotator_try_fmt_vid_cap - V4l2 ioctl try capture format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct.
 */
static int sde_rotator_try_fmt_vid_cap(struct file *file,
	void *fh, struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotation_config config;
	int ret;

	if ((f->fmt.pix.width == 0) || (f->fmt.pix.height == 0)) {
		SDEDEV_WARN(ctx->rot_dev->dev,
				"Not supporting 0 width/height: %dx%d\n",
				f->fmt.pix.width, f->fmt.pix.height);
		return -EINVAL;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	sde_rotator_get_config_from_ctx(ctx, &config);
	config.output.format = f->fmt.pix.pixelformat;
	config.output.width = f->fmt.pix.width;
	config.output.height = f->fmt.pix.height;
	config.flags |= SDE_ROTATION_VERIFY_INPUT_ONLY;
	ret = sde_rotator_verify_config_output(rot_dev->mgr, &config);
	sde_rot_mgr_unlock(rot_dev->mgr);
	if (ret) {
		if ((config.output.width == f->fmt.pix.width) &&
				(config.output.height == f->fmt.pix.height)) {
			SDEDEV_WARN(ctx->rot_dev->dev,
				"invalid capture format 0x%8.8x %dx%d\n",
				f->fmt.pix.pixelformat,
				f->fmt.pix.width,
				f->fmt.pix.height);
			return -EINVAL;
		}
		f->fmt.pix.width = config.output.width;
		f->fmt.pix.height = config.output.height;
	}

	sde_rotator_format_recalc(f);
	return ret;
}

/*
 * sde_rotator_try_fmt_vid_out - V4l2 ioctl try output format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct.
 */
static int sde_rotator_try_fmt_vid_out(struct file *file,
	void *fh, struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotation_config config;
	int ret;

	if ((f->fmt.pix.width == 0) || (f->fmt.pix.height == 0)) {
		SDEDEV_WARN(ctx->rot_dev->dev,
				"Not supporting 0 width/height: %dx%d\n",
				f->fmt.pix.width, f->fmt.pix.height);
		return -EINVAL;
	}

	sde_rot_mgr_lock(rot_dev->mgr);
	sde_rotator_get_config_from_ctx(ctx, &config);
	config.input.format = f->fmt.pix.pixelformat;
	config.input.width = f->fmt.pix.width;
	config.input.height = f->fmt.pix.height;
	config.flags |= SDE_ROTATION_VERIFY_INPUT_ONLY;
	ret = sde_rotator_verify_config_input(rot_dev->mgr, &config);
	sde_rot_mgr_unlock(rot_dev->mgr);
	if (ret) {
		if ((config.input.width == f->fmt.pix.width) &&
				(config.input.height == f->fmt.pix.height)) {
			SDEDEV_WARN(ctx->rot_dev->dev,
				"invalid output format 0x%8.8x %dx%d\n",
				f->fmt.pix.pixelformat,
				f->fmt.pix.width,
				f->fmt.pix.height);
			return -EINVAL;
		}
		f->fmt.pix.width = config.input.width;
		f->fmt.pix.height = config.input.height;
	}

	sde_rotator_format_recalc(f);
	return ret;
}

/*
 * sde_rotator_s_fmt_vid_cap - V4l2 ioctl set capture format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct.
 */
static int sde_rotator_s_fmt_vid_cap(struct file *file,
	void *fh, struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	int ret;

	ret = sde_rotator_try_fmt_vid_cap(file, fh, f);
	if (ret)
		return -EINVAL;

	/* Initialize crop */
	ctx->crop_cap.top = 0;
	ctx->crop_cap.left = 0;
	ctx->crop_cap.width = f->fmt.pix.width;
	ctx->crop_cap.height = f->fmt.pix.height;

	ctx->format_cap = *f;

	SDEDEV_DBG(rot_dev->dev,
		"s_fmt s:%d t:%d fmt:0x%8.8x field:%u (%u,%u)\n",
		ctx->session_id, f->type,
		f->fmt.pix.pixelformat,
		f->fmt.pix.field,
		f->fmt.pix.width, f->fmt.pix.height);

	return 0;
}

/*
 * sde_rotator_s_fmt_vid_out - V4l2 ioctl set output format handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @f: Pointer to v4l2_format struct.
 */
static int sde_rotator_s_fmt_vid_out(struct file *file,
	void *fh, struct v4l2_format *f)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	int ret;

	ret = sde_rotator_try_fmt_vid_out(file, fh, f);
	if (ret)
		return -EINVAL;

	/* Initialize crop */
	ctx->crop_out.top = 0;
	ctx->crop_out.left = 0;
	ctx->crop_out.width = f->fmt.pix.width;
	ctx->crop_out.height = f->fmt.pix.height;

	ctx->format_out = *f;

	SDEDEV_DBG(rot_dev->dev,
		"s_fmt s:%d t:%d fmt:0x%8.8x field:%u (%u,%u)\n",
		ctx->session_id, f->type,
		f->fmt.pix.pixelformat,
		f->fmt.pix.field,
		f->fmt.pix.width, f->fmt.pix.height);

	return 0;
}

/*
 * sde_rotator_reqbufs - V4l2 ioctl request buffers handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @req: Pointer to v4l2_requestbuffer struct.
 */
static int sde_rotator_reqbufs(struct file *file,
	void *fh, struct v4l2_requestbuffers *req)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	return v4l2_m2m_reqbufs(file, ctx->fh.m2m_ctx, req);
}

/*
 * sde_rotator_qbuf - V4l2 ioctl queue buffer handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @buf: Pointer to v4l2_buffer struct.
 */
static int sde_rotator_qbuf(struct file *file, void *fh,
	struct v4l2_buffer *buf)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	int ret;

	/* create fence for capture buffer */
	if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			&& (buf->index < ctx->nbuf_cap)) {
		int idx = buf->index;

		ctx->vbinfo_cap[idx].fd = -1;
		ctx->vbinfo_cap[idx].fence = sde_rotator_get_sync_fence(
				ctx->work_queue.timeline, NULL,
				&ctx->vbinfo_cap[idx].fence_ts);
		ctx->vbinfo_cap[idx].qbuf_ts = ktime_get();
		ctx->vbinfo_cap[idx].dqbuf_ts = NULL;
		SDEDEV_DBG(ctx->rot_dev->dev,
				"create buffer fence s:%d.%u i:%d f:%pK\n",
				ctx->session_id,
				ctx->vbinfo_cap[idx].fence_ts,
				idx,
				ctx->vbinfo_cap[idx].fence);
	} else if ((buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
			&& (buf->index < ctx->nbuf_out)) {
		int idx = buf->index;

		ctx->vbinfo_out[idx].qbuf_ts = ktime_get();
		ctx->vbinfo_out[idx].dqbuf_ts = NULL;
	}

	ret = v4l2_m2m_qbuf(file, ctx->fh.m2m_ctx, buf);
	if (ret < 0)
		SDEDEV_ERR(ctx->rot_dev->dev, "fail qbuf s:%d t:%d r:%d\n",
				ctx->session_id, buf->type, ret);
	SDEROT_EVTLOG(buf->type, buf->bytesused, buf->length, buf->m.fd, ret);

	return ret;
}

/*
 * sde_rotator_dqbuf - V4l2 ioctl dequeue buffer handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @buf: Pointer to v4l2_buffer struct.
 */
static int sde_rotator_dqbuf(struct file *file,
	void *fh, struct v4l2_buffer *buf)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	int ret;

	ret = v4l2_m2m_dqbuf(file, ctx->fh.m2m_ctx, buf);

	if (ret) {
		SDEDEV_ERR(ctx->rot_dev->dev,
				"fail dqbuf s:%d t:%d i:%d r:%d\n",
				ctx->session_id, buf->type, buf->index, ret);
		return ret;
	}

	/* clear fence for buffer */
	if ((buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
			&& (buf->index < ctx->nbuf_cap)) {
		int idx = buf->index;

		if (ctx->vbinfo_cap[idx].fence) {
			/* fence is not used */
			SDEDEV_DBG(ctx->rot_dev->dev, "put fence s:%d i:%d\n",
					ctx->session_id, idx);
			sde_rotator_put_sync_fence(ctx->vbinfo_cap[idx].fence);
		}
		ctx->vbinfo_cap[idx].fence = NULL;
		ctx->vbinfo_cap[idx].fd = -1;
		if (ctx->vbinfo_cap[idx].dqbuf_ts)
			*(ctx->vbinfo_cap[idx].dqbuf_ts) = ktime_get();
	} else if ((buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
			&& (buf->index < ctx->nbuf_out)) {
		int idx = buf->index;

		ctx->vbinfo_out[idx].fence = NULL;
		ctx->vbinfo_out[idx].fd = -1;
		if (ctx->vbinfo_out[idx].dqbuf_ts)
			*(ctx->vbinfo_out[idx].dqbuf_ts) = ktime_get();
	} else {
		SDEDEV_WARN(ctx->rot_dev->dev, "invalid dq s:%d t:%d i:%d\n",
				ctx->session_id, buf->type, buf->index);
	}

	return 0;
}

/*
 * sde_rotator_querybuf - V4l2 ioctl query buffer handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @buf: Pointer to v4l2_buffer struct.
 */
static int sde_rotator_querybuf(struct file *file,
	void *fh, struct v4l2_buffer *buf)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	return v4l2_m2m_querybuf(file, ctx->fh.m2m_ctx, buf);
}

/*
 * sde_rotator_streamon - V4l2 ioctl stream on handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @buf_type: V4l2 buffer type.
 */
static int sde_rotator_streamon(struct file *file,
	void *fh, enum v4l2_buf_type buf_type)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotation_config config;
	struct vb2_queue *vq;
	int ret;

	SDEDEV_DBG(ctx->rot_dev->dev, "stream on s:%d t:%d\n",
			ctx->session_id, buf_type);

	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
			buf_type == V4L2_BUF_TYPE_VIDEO_OUTPUT ?
			V4L2_BUF_TYPE_VIDEO_CAPTURE :
			V4L2_BUF_TYPE_VIDEO_OUTPUT);

	if (!vq) {
		SDEDEV_ERR(ctx->rot_dev->dev, "fail to get vq on s:%d t:%d\n",
				ctx->session_id, buf_type);
		return -EINVAL;
	}

	if (vb2_is_streaming(vq)) {
		sde_rot_mgr_lock(rot_dev->mgr);
		sde_rotator_get_config_from_ctx(ctx, &config);
		config.flags &= ~SDE_ROTATION_VERIFY_INPUT_ONLY;
		ret = sde_rotator_session_config(rot_dev->mgr, ctx->private,
				&config);
		sde_rot_mgr_unlock(rot_dev->mgr);
		if (ret < 0) {
			SDEDEV_ERR(rot_dev->dev,
				"fail config in stream on s:%d t:%d r:%d\n",
				ctx->session_id, buf_type, ret);
			return ret;
		}
		ctx->rotcfg = config;
	}

	ret = v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, buf_type);
	if (ret < 0)
		SDEDEV_ERR(ctx->rot_dev->dev, "fail stream on s:%d t:%d\n",
				ctx->session_id, buf_type);

	return ret;
}

/*
 * sde_rotator_streamoff - V4l2 ioctl stream off handler.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @buf_type: V4l2 buffer type.
 */
static int sde_rotator_streamoff(struct file *file,
	void *fh, enum v4l2_buf_type buf_type)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	int ret;

	SDEDEV_DBG(ctx->rot_dev->dev, "stream off s:%d t:%d\n",
			ctx->session_id, buf_type);

	ret = v4l2_m2m_streamoff(file, ctx->fh.m2m_ctx, buf_type);
	if (ret < 0)
		SDEDEV_ERR(ctx->rot_dev->dev, "fail stream off s:%d t:%d\n",
				ctx->session_id, buf_type);

	return ret;
}

/*
 * sde_rotator_g_selection - V4l2 ioctl get crop.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @selection: Pointer to v4l2_selection struct need to be set.
 */
static int sde_rotator_g_selection(struct file *file, void *fh,
	struct v4l2_selection *selection)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	switch (selection->type) {
	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
		selection->r = ctx->crop_out;
		break;
	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
		selection->r = ctx->crop_cap;
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

/*
 * sde_rotator_s_selection - V4l2 ioctl set crop.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @selection: Pointer to v4l2_selection struct need to be set.
 */
static int sde_rotator_s_selection(struct file *file, void *fh,
	struct v4l2_selection *selection)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotation_item item;
	struct v4l2_rect rect;

	sde_rotator_get_item_from_ctx(ctx, &item);

	rect.left = max_t(__u32, selection->r.left, 0);
	rect.top = max_t(__u32, selection->r.top, 0);
	rect.height = max_t(__u32, selection->r.height, 0);
	rect.width = max_t(__u32, selection->r.width, 0);

	if (selection->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
		rect.left = min_t(__u32, rect.left,
				ctx->format_out.fmt.pix.width - 1);
		rect.top = min_t(__u32, rect.top,
				ctx->format_out.fmt.pix.height - 1);
		rect.width = min_t(__u32, rect.width,
				(ctx->format_out.fmt.pix.width - rect.left));
		rect.height = min_t(__u32, rect.height,
				(ctx->format_out.fmt.pix.height - rect.top));

		item.src_rect.x = rect.left;
		item.src_rect.y = rect.top;
		item.src_rect.w = rect.width;
		item.src_rect.h = rect.height;

		sde_rotator_validate_item(ctx, &item);

		SDEDEV_DBG(rot_dev->dev,
			"s_crop s:%d t:%d (%u,%u,%u,%u)->(%u,%u,%u,%u)\n",
			ctx->session_id, selection->type,
			selection->r.left, selection->r.top,
			selection->r.width, selection->r.height,
			item.src_rect.x, item.src_rect.y,
			item.src_rect.w, item.src_rect.h);

		ctx->crop_out.left = item.src_rect.x;
		ctx->crop_out.top = item.src_rect.y;
		ctx->crop_out.width = item.src_rect.w;
		ctx->crop_out.height = item.src_rect.h;
	} else if (selection->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
		rect.left = min_t(__u32, rect.left,
				ctx->format_cap.fmt.pix.width - 1);
		rect.top = min_t(__u32, rect.top,
				ctx->format_cap.fmt.pix.height - 1);
		rect.width = min_t(__u32, rect.width,
				(ctx->format_cap.fmt.pix.width - rect.left));
		rect.height = min_t(__u32, rect.height,
				(ctx->format_cap.fmt.pix.height - rect.top));

		item.dst_rect.x = rect.left;
		item.dst_rect.y = rect.top;
		item.dst_rect.w = rect.width;
		item.dst_rect.h = rect.height;

		sde_rotator_validate_item(ctx, &item);

		SDEDEV_DBG(rot_dev->dev,
			"s_crop s:%d t:%d (%u,%u,%u,%u)->(%u,%u,%u,%u)\n",
			ctx->session_id, selection->type,
			selection->r.left, selection->r.top,
			selection->r.width, selection->r.height,
			item.dst_rect.x, item.dst_rect.y,
			item.dst_rect.w, item.dst_rect.h);

		ctx->crop_cap.left = item.dst_rect.x;
		ctx->crop_cap.top = item.dst_rect.y;
		ctx->crop_cap.width = item.dst_rect.w;
		ctx->crop_cap.height = item.dst_rect.h;
	} else {
		return -EINVAL;
	}

	return 0;
}

/*
 * sde_rotator_g_parm - V4l2 ioctl get parm.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @a: Pointer to v4l2_streamparm struct need to be filled.
 */
static int sde_rotator_g_parm(struct file *file, void *fh,
	struct v4l2_streamparm *a)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	/* Get param is supported only for input buffers */
	if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
		return -EINVAL;

	a->parm.output.capability = 0;
	a->parm.output.extendedmode = 0;
	a->parm.output.outputmode = 0;
	a->parm.output.writebuffers = 0;
	a->parm.output.timeperframe = ctx->timeperframe;

	return 0;
}

/*
 * sde_rotator_s_parm - V4l2 ioctl set parm.
 * @file: Pointer to file struct.
 * @fh: V4l2 File handle.
 * @a: Pointer to v4l2_streamparm struct need to be set.
 */
static int sde_rotator_s_parm(struct file *file, void *fh,
	struct v4l2_streamparm *a)
{
	struct sde_rotator_ctx *ctx = sde_rotator_ctx_from_fh(fh);

	/* Set param is supported only for input buffers */
	if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
		return -EINVAL;

	if (!a->parm.output.timeperframe.numerator ||
	    !a->parm.output.timeperframe.denominator)
		return -EINVAL;

	ctx->timeperframe = a->parm.output.timeperframe;
	return 0;
}

/*
 * sde_rotator_private_ioctl - V4l2 private ioctl handler.
 * @file: Pointer to file struct.
 * @fd: V4l2 device file handle.
 * @valid_prio: Priority ioctl valid flag.
 * @cmd: Ioctl command.
 * @arg: Ioctl argument.
 */
static long sde_rotator_private_ioctl(struct file *file, void *fh,
	bool valid_prio, unsigned int cmd, void *arg)
{
	struct sde_rotator_ctx *ctx =
			sde_rotator_ctx_from_fh(file->private_data);
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct msm_sde_rotator_fence *fence = arg;
	struct msm_sde_rotator_comp_ratio *comp_ratio = arg;
	struct sde_rotator_vbinfo *vbinfo;
	int ret;

	switch (cmd) {
	case VIDIOC_S_SDE_ROTATOR_FENCE:
		if (!fence)
			return -EINVAL;

		if (fence->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
			return -EINVAL;

		if (fence->index >= ctx->nbuf_out)
			return -EINVAL;

		SDEDEV_DBG(rot_dev->dev,
				"VIDIOC_S_SDE_ROTATOR_FENCE s:%d i:%d fd:%d\n",
				ctx->session_id, fence->index,
				fence->fd);

		vbinfo = &ctx->vbinfo_out[fence->index];

		if (vbinfo->fd >= 0) {
			if (vbinfo->fence) {
				SDEDEV_DBG(rot_dev->dev,
						"put fence s:%d t:%d i:%d\n",
						ctx->session_id,
						fence->type, fence->index);
				sde_rotator_put_sync_fence(vbinfo->fence);
			}
			vbinfo->fence = NULL;
			vbinfo->fd = -1;
		}

		vbinfo->fd = fence->fd;
		if (vbinfo->fd >= 0) {
			vbinfo->fence =
				sde_rotator_get_fd_sync_fence(vbinfo->fd);
			if (!vbinfo->fence) {
				SDEDEV_WARN(rot_dev->dev,
					"invalid input fence fd s:%d fd:%d\n",
					ctx->session_id, vbinfo->fd);
				vbinfo->fd = -1;
				return -EINVAL;
			}
		} else {
			vbinfo->fence = NULL;
		}
		break;
	case VIDIOC_G_SDE_ROTATOR_FENCE:
		if (!fence)
			return -EINVAL;

		if (fence->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
			return -EINVAL;

		if (fence->index >= ctx->nbuf_cap)
			return -EINVAL;

		vbinfo = &ctx->vbinfo_cap[fence->index];

		if (!vbinfo)
			return -EINVAL;

		if (vbinfo->fence) {
			ret = sde_rotator_get_sync_fence_fd(vbinfo->fence);
			if (ret < 0) {
				SDEDEV_ERR(rot_dev->dev,
						"fail get fence fd s:%d\n",
						ctx->session_id);
				return ret;
			}

			/**
			 * Cache fence descriptor in case user calls this
			 * ioctl multiple times. Cached value would be stale
			 * if user duplicated and closed old descriptor.
			 */
			vbinfo->fd = ret;
		} else if (!sde_rotator_get_fd_sync_fence(vbinfo->fd)) {
			/**
			 * User has closed cached fence descriptor.
			 * Invalidate descriptor cache.
			 */
			vbinfo->fd = -1;
		}
		fence->fd = vbinfo->fd;

		SDEDEV_DBG(rot_dev->dev,
				"VIDIOC_G_SDE_ROTATOR_FENCE s:%d i:%d fd:%d\n",
				ctx->session_id, fence->index,
				fence->fd);
		break;
	case VIDIOC_S_SDE_ROTATOR_COMP_RATIO:
		if (!comp_ratio)
			return -EINVAL;
		else if (!comp_ratio->numer || !comp_ratio->denom)
			return -EINVAL;
		else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
				comp_ratio->index < ctx->nbuf_out)
			vbinfo = &ctx->vbinfo_out[comp_ratio->index];
		else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
				comp_ratio->index < ctx->nbuf_cap)
			vbinfo = &ctx->vbinfo_cap[comp_ratio->index];
		else
			return -EINVAL;

		vbinfo->comp_ratio.numer = comp_ratio->numer;
		vbinfo->comp_ratio.denom = comp_ratio->denom;

		SDEDEV_DBG(rot_dev->dev,
				"VIDIOC_S_SDE_ROTATOR_COMP_RATIO s:%d i:%d t:%d cr:%u/%u\n",
				ctx->session_id, comp_ratio->index,
				comp_ratio->type,
				vbinfo->comp_ratio.numer,
				vbinfo->comp_ratio.denom);
		break;
	case VIDIOC_G_SDE_ROTATOR_COMP_RATIO:
		if (!comp_ratio)
			return -EINVAL;
		else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
				comp_ratio->index < ctx->nbuf_out)
			vbinfo = &ctx->vbinfo_out[comp_ratio->index];
		else if (comp_ratio->type == V4L2_BUF_TYPE_VIDEO_CAPTURE &&
				comp_ratio->index < ctx->nbuf_cap)
			vbinfo = &ctx->vbinfo_cap[comp_ratio->index];
		else
			return -EINVAL;

		comp_ratio->numer = vbinfo->comp_ratio.numer;
		comp_ratio->denom = vbinfo->comp_ratio.denom;

		SDEDEV_DBG(rot_dev->dev,
				"VIDIOC_G_SDE_ROTATOR_COMP_RATIO s:%d i:%d t:%d cr:%u/%u\n",
				ctx->session_id, comp_ratio->index,
				comp_ratio->type,
				comp_ratio->numer,
				comp_ratio->denom);
		break;
	default:
		SDEDEV_WARN(rot_dev->dev, "invalid ioctl type %x\n", cmd);
		return -ENOTTY;
	}

	return 0;
}

#if IS_ENABLED(CONFIG_COMPAT)
/*
 * sde_rotator_compat_ioctl32 - Compat ioctl handler function.
 * @file: Pointer to file struct.
 * @cmd: Ioctl command.
 * @arg: Ioctl argument.
 */
static long sde_rotator_compat_ioctl32(struct file *file,
	unsigned int cmd, unsigned long arg)
{
	struct video_device *vdev = video_devdata(file);
	struct sde_rotator_ctx *ctx =
			sde_rotator_ctx_from_fh(file->private_data);
	long ret;

	mutex_lock(vdev->lock);

	switch (cmd) {
	case VIDIOC_S_SDE_ROTATOR_FENCE:
	case VIDIOC_G_SDE_ROTATOR_FENCE:
	{
		struct msm_sde_rotator_fence fence;

		if (copy_from_user(&fence, (void __user *)arg,
				sizeof(struct msm_sde_rotator_fence)))
			goto ioctl32_error;

		ret = sde_rotator_private_ioctl(file, file->private_data,
			0, cmd, (void *)&fence);

		if (copy_to_user((void __user *)arg, &fence,
				sizeof(struct msm_sde_rotator_fence)))
			goto ioctl32_error;

		break;
	}
	case VIDIOC_S_SDE_ROTATOR_COMP_RATIO:
	case VIDIOC_G_SDE_ROTATOR_COMP_RATIO:
	{
		struct msm_sde_rotator_comp_ratio comp_ratio;

		if (copy_from_user(&comp_ratio, (void __user *)arg,
				sizeof(struct msm_sde_rotator_comp_ratio)))
			goto ioctl32_error;

		ret = sde_rotator_private_ioctl(file, file->private_data,
			0, cmd, (void *)&comp_ratio);

		if (copy_to_user((void __user *)arg, &comp_ratio,
				sizeof(struct msm_sde_rotator_comp_ratio)))
			goto ioctl32_error;

		break;
	}
	default:
		SDEDEV_ERR(ctx->rot_dev->dev, "invalid ioctl32 type:%x\n", cmd);
		ret = -ENOIOCTLCMD;
		break;

	}

	mutex_unlock(vdev->lock);
	return ret;

ioctl32_error:
	mutex_unlock(vdev->lock);
	SDEDEV_ERR(ctx->rot_dev->dev, "error handling ioctl32 cmd:%x\n", cmd);
	return -EFAULT;
}
#endif /* CONFIG_COMPAT */

static int sde_rotator_ctrl_subscribe_event(struct v4l2_fh *fh,
				const struct v4l2_event_subscription *sub)
{
	return -EINVAL;
}

static int sde_rotator_event_unsubscribe(struct v4l2_fh *fh,
			   const struct v4l2_event_subscription *sub)
{
	return -EINVAL;
}

/* V4l2 ioctl handlers */
static const struct v4l2_ioctl_ops sde_rotator_ioctl_ops = {
	.vidioc_querycap          = sde_rotator_querycap,
	.vidioc_enum_fmt_vid_out  = sde_rotator_enum_fmt_vid_out,
	.vidioc_enum_fmt_vid_cap  = sde_rotator_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_out     = sde_rotator_g_fmt_out,
	.vidioc_g_fmt_vid_cap     = sde_rotator_g_fmt_cap,
	.vidioc_try_fmt_vid_out   = sde_rotator_try_fmt_vid_out,
	.vidioc_try_fmt_vid_cap   = sde_rotator_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_out     = sde_rotator_s_fmt_vid_out,
	.vidioc_s_fmt_vid_cap     = sde_rotator_s_fmt_vid_cap,
	.vidioc_reqbufs           = sde_rotator_reqbufs,
	.vidioc_qbuf              = sde_rotator_qbuf,
	.vidioc_dqbuf             = sde_rotator_dqbuf,
	.vidioc_querybuf          = sde_rotator_querybuf,
	.vidioc_streamon          = sde_rotator_streamon,
	.vidioc_streamoff         = sde_rotator_streamoff,
	.vidioc_g_selection       = sde_rotator_g_selection,
	.vidioc_s_selection       = sde_rotator_s_selection,
	.vidioc_g_parm            = sde_rotator_g_parm,
	.vidioc_s_parm            = sde_rotator_s_parm,
	.vidioc_default           = sde_rotator_private_ioctl,
	.vidioc_subscribe_event   = sde_rotator_ctrl_subscribe_event,
	.vidioc_unsubscribe_event = sde_rotator_event_unsubscribe,
};

/*
 * sde_rotator_retire_handler - Invoked by hal when processing is done.
 * @work: Pointer to work structure.
 *
 * This function is scheduled in work queue context.
 */
static void sde_rotator_retire_handler(struct kthread_work *work)
{
	struct vb2_v4l2_buffer *src_buf;
	struct vb2_v4l2_buffer *dst_buf;
	struct sde_rotator_ctx *ctx;
	struct sde_rotator_device *rot_dev;
	struct sde_rotator_request *request;

	request = container_of(work, struct sde_rotator_request, retire_work);
	ctx = request->ctx;

	if (!ctx || !ctx->rot_dev) {
		SDEROT_ERR("null context/device\n");
		return;
	}

	rot_dev = ctx->rot_dev;

	SDEDEV_DBG(rot_dev->dev, "retire handler s:%d\n", ctx->session_id);

	mutex_lock(&rot_dev->lock);
	if (ctx->abort_pending) {
		SDEDEV_DBG(rot_dev->dev, "abort command in retire s:%d\n",
				ctx->session_id);
		sde_rotator_update_retire_sequence(request);
		sde_rotator_retire_request(request);
		mutex_unlock(&rot_dev->lock);
		return;
	}

	if (!ctx->file) {
		sde_rotator_update_retire_sequence(request);
	} else if (rot_dev->early_submit) {
		if (IS_ERR_OR_NULL(request->req)) {
			/* fail pending request or something wrong */
			SDEDEV_ERR(rot_dev->dev,
					"pending request fail in retire s:%d\n",
					ctx->session_id);
		}

		/* pending request. reschedule this context. */
		v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
	} else {
		/* no pending request. acknowledge the usual way. */
		src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
		dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);

		if (!src_buf || !dst_buf) {
			SDEDEV_ERR(rot_dev->dev,
				"null buffer in retire s:%d sb:%pK db:%pK\n",
				ctx->session_id,
				src_buf, dst_buf);
		}

		sde_rotator_update_retire_sequence(request);
		sde_rotator_retire_request(request);
		v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
		v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
		v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
	}
	mutex_unlock(&rot_dev->lock);
}

/*
 * sde_rotator_process_buffers - Start rotator processing.
 * @ctx: Pointer rotator context.
 * @src_buf: Pointer to Vb2 source buffer.
 * @dst_buf: Pointer to Vb2 destination buffer.
 * @request: Pointer to rotator request
 */
static int sde_rotator_process_buffers(struct sde_rotator_ctx *ctx,
	struct vb2_buffer *src_buf, struct vb2_buffer *dst_buf,
	struct sde_rotator_request *request)
{
	struct sde_rotator_device *rot_dev = ctx->rot_dev;
	struct sde_rotation_item item;
	struct sde_rot_entry_container *req = NULL;
	struct sde_rotator_buf_handle *src_handle;
	struct sde_rotator_buf_handle *dst_handle;
	struct sde_rotator_statistics *stats = &rot_dev->stats;
	struct sde_rotator_vbinfo *vbinfo_out;
	struct sde_rotator_vbinfo *vbinfo_cap;
	struct sde_rot_trace_entry rot_trace;
	ktime_t *ts;
	int ret;

	if (!src_buf || !dst_buf) {
		SDEDEV_ERR(rot_dev->dev, "null vb2 buffers\n");
		ret = -EINVAL;
		goto error_null_buffer;
	}

	src_handle = src_buf->planes[0].mem_priv;
	dst_handle = dst_buf->planes[0].mem_priv;

	if (!src_handle || !dst_handle) {
		SDEDEV_ERR(rot_dev->dev, "null buffer handle\n");
		ret = -EINVAL;
		goto error_null_buffer;
	}

	vbinfo_out = &ctx->vbinfo_out[src_buf->index];
	vbinfo_cap = &ctx->vbinfo_cap[dst_buf->index];

	SDEDEV_DBG(rot_dev->dev,
		"process buffer s:%d.%u src:(%u,%u,%u,%u) dst:(%u,%u,%u,%u) rot:%d flip:%d/%d sec:%d src_cr:%u/%u dst_cr:%u/%u\n",
		ctx->session_id, vbinfo_cap->fence_ts,
		ctx->crop_out.left, ctx->crop_out.top,
		ctx->crop_out.width, ctx->crop_out.height,
		ctx->crop_cap.left, ctx->crop_cap.top,
		ctx->crop_cap.width, ctx->crop_cap.height,
		ctx->rotate, ctx->hflip, ctx->vflip, ctx->secure,
		vbinfo_out->comp_ratio.numer, vbinfo_out->comp_ratio.denom,
		vbinfo_cap->comp_ratio.numer, vbinfo_cap->comp_ratio.denom);

	/* allocate slot for timestamp */
	ts = stats->ts[stats->count++ % SDE_ROTATOR_NUM_EVENTS];
	ts[SDE_ROTATOR_TS_SRCQB] = vbinfo_out->qbuf_ts;
	ts[SDE_ROTATOR_TS_DSTQB] = vbinfo_cap->qbuf_ts;
	vbinfo_out->dqbuf_ts = &ts[SDE_ROTATOR_TS_SRCDQB];
	vbinfo_cap->dqbuf_ts = &ts[SDE_ROTATOR_TS_DSTDQB];

	ts[SDE_ROTATOR_TS_FENCE] = ktime_get();

	/* Set values to pass to trace */
	rot_trace.wb_idx = ctx->fh.prio;
	rot_trace.flags = (ctx->rotate << 0) | (ctx->hflip << 8) |
			(ctx->hflip << 9) | (ctx->secure << 10);
	rot_trace.input_format = ctx->format_out.fmt.pix.pixelformat;
	rot_trace.input_width = ctx->format_out.fmt.pix.width;
	rot_trace.input_height = ctx->format_out.fmt.pix.height;
	rot_trace.src_x = ctx->crop_out.left;
	rot_trace.src_y = ctx->crop_out.top;
	rot_trace.src_w = ctx->crop_out.width;
	rot_trace.src_h = ctx->crop_out.height;
	rot_trace.output_format = ctx->format_cap.fmt.pix.pixelformat;
	rot_trace.output_width = ctx->format_cap.fmt.pix.width;
	rot_trace.output_height = ctx->format_cap.fmt.pix.height;
	rot_trace.dst_x = ctx->crop_cap.left;
	rot_trace.dst_y = ctx->crop_cap.top;
	rot_trace.dst_w = ctx->crop_cap.width;
	rot_trace.dst_h = ctx->crop_cap.height;

	trace_rot_entry_fence(
		ctx->session_id, vbinfo_cap->fence_ts, &rot_trace);

	if (vbinfo_out->fence) {
		sde_rot_mgr_unlock(rot_dev->mgr);
		mutex_unlock(&rot_dev->lock);
		SDEDEV_DBG(rot_dev->dev, "fence enter s:%d.%d fd:%d\n",
			ctx->session_id, vbinfo_cap->fence_ts, vbinfo_out->fd);
		ret = sde_rotator_wait_sync_fence(vbinfo_out->fence,
				rot_dev->fence_timeout);
		mutex_lock(&rot_dev->lock);
		sde_rot_mgr_lock(rot_dev->mgr);
		sde_rotator_put_sync_fence(vbinfo_out->fence);
		vbinfo_out->fence = NULL;
		if (ret) {
			SDEDEV_ERR(rot_dev->dev,
				"error waiting for fence s:%d.%d fd:%d r:%d\n",
				ctx->session_id,
				vbinfo_cap->fence_ts, vbinfo_out->fd, ret);
			SDEROT_EVTLOG(ctx->session_id, vbinfo_cap->fence_ts,
					vbinfo_out->fd, ret,
					SDE_ROT_EVTLOG_ERROR);
			goto error_fence_wait;
		} else {
			SDEDEV_DBG(rot_dev->dev, "fence exit s:%d.%d fd:%d\n",
				ctx->session_id,
				vbinfo_cap->fence_ts, vbinfo_out->fd);
		}
	}

	/* fill in item work structure */
	sde_rotator_get_item_from_ctx(ctx, &item);
	item.flags |= SDE_ROTATION_EXT_DMA_BUF;
	item.input.planes[0].fd = src_handle->fd;
	item.input.planes[0].buffer = src_handle->buffer;
	item.input.planes[0].offset = src_handle->addr;
	item.input.planes[0].stride = ctx->format_out.fmt.pix.bytesperline;
	item.input.plane_count = 1;
	item.input.fence = NULL;
	item.input.comp_ratio = vbinfo_out->comp_ratio;
	item.output.planes[0].fd = dst_handle->fd;
	item.output.planes[0].buffer = dst_handle->buffer;
	item.output.planes[0].offset = dst_handle->addr;
	item.output.planes[0].stride = ctx->format_cap.fmt.pix.bytesperline;
	item.output.plane_count = 1;
	item.output.fence = NULL;
	item.output.comp_ratio = vbinfo_cap->comp_ratio;
	item.sequence_id = vbinfo_cap->fence_ts;
	item.ts = ts;

	req = sde_rotator_req_init(rot_dev->mgr, ctx->private, &item, 1, 0);
	if (IS_ERR_OR_NULL(req)) {
		SDEDEV_ERR(rot_dev->dev, "fail allocate rotation request\n");
		ret = -ENOMEM;
		goto error_init_request;
	}

	req->retire_kw = ctx->work_queue.rot_kw;
	req->retire_work = &request->retire_work;

	ret = sde_rotator_handle_request_common(
			rot_dev->mgr, ctx->private, req);
	if (ret) {
		SDEDEV_ERR(rot_dev->dev, "fail handle request\n");
		goto error_handle_request;
	}

	sde_rotator_queue_request(rot_dev->mgr, ctx->private, req);
	request->req = req;
	request->sequence_id = item.sequence_id;
	request->committed = true;

	return 0;
error_handle_request:
	devm_kfree(rot_dev->dev, req);
error_init_request:
error_fence_wait:
error_null_buffer:
	request->req = NULL;
	request->sequence_id = 0;
	request->committed = false;
	return ret;
}

/*
 * sde_rotator_submit_handler - Invoked by m2m to submit job.
 * @work: Pointer to work structure.
 *
 * This function is scheduled in work queue context.
 */
static void sde_rotator_submit_handler(struct kthread_work *work)
{
	struct sde_rotator_ctx *ctx;
	struct sde_rotator_device *rot_dev;
	struct vb2_v4l2_buffer *src_buf;
	struct vb2_v4l2_buffer *dst_buf;
	struct sde_rotator_request *request;
	int ret;

	request = container_of(work, struct sde_rotator_request, submit_work);
	ctx = request->ctx;

	if (!ctx || !ctx->rot_dev) {
		SDEROT_ERR("null device\n");
		return;
	}

	rot_dev = ctx->rot_dev;
	SDEDEV_DBG(rot_dev->dev, "submit handler s:%d\n", ctx->session_id);

	mutex_lock(&rot_dev->lock);
	if (ctx->abort_pending) {
		SDEDEV_DBG(rot_dev->dev, "abort command in submit s:%d\n",
				ctx->session_id);
		sde_rotator_update_retire_sequence(request);
		sde_rotator_retire_request(request);
		mutex_unlock(&rot_dev->lock);
		return;
	}

	/* submit new request */
	dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
	src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
	sde_rot_mgr_lock(rot_dev->mgr);
	ret = sde_rotator_process_buffers(ctx, &src_buf->vb2_buf,
			&dst_buf->vb2_buf, request);
	sde_rot_mgr_unlock(rot_dev->mgr);
	if (ret) {
		SDEDEV_ERR(rot_dev->dev,
			"fail process buffer in submit s:%d\n",
			ctx->session_id);
		/* advance to device run to clean up buffers */
		v4l2_m2m_try_schedule(ctx->fh.m2m_ctx);
	}

	mutex_unlock(&rot_dev->lock);
}

/*
 * sde_rotator_device_run - rotator m2m device run callback
 * @priv: Pointer rotator context.
 */
static void sde_rotator_device_run(void *priv)
{
	struct sde_rotator_ctx *ctx = priv;
	struct sde_rotator_device *rot_dev;
	struct vb2_v4l2_buffer *src_buf;
	struct vb2_v4l2_buffer *dst_buf;
	struct sde_rotator_request *request;
	int ret;

	if (!ctx || !ctx->rot_dev) {
		SDEROT_ERR("null context/device\n");
		return;
	}

	rot_dev = ctx->rot_dev;
	SDEDEV_DBG(rot_dev->dev, "device run s:%d\n", ctx->session_id);

	if (rot_dev->early_submit) {
		request = list_first_entry_or_null(&ctx->pending_list,
				struct sde_rotator_request, list);

		/* pending request mode, check for completion */
		if (!request || IS_ERR_OR_NULL(request->req)) {
			/* pending request fails or something wrong. */
			SDEDEV_ERR(rot_dev->dev,
				"pending request fail in device run s:%d\n",
				ctx->session_id);
			rot_dev->stats.fail_count++;
			ATRACE_INT("fail_count", rot_dev->stats.fail_count);
			goto error_process_buffers;

		} else if (!atomic_read(&request->req->pending_count)) {
			/* pending request completed. signal done. */
			int failed_count =
				atomic_read(&request->req->failed_count);
			SDEDEV_DBG(rot_dev->dev,
				"pending request completed in device run s:%d\n",
				ctx->session_id);

			/* disconnect request (will be freed by core layer) */
			sde_rot_mgr_lock(rot_dev->mgr);
			sde_rotator_req_finish(rot_dev->mgr, ctx->private,
					request->req);
			sde_rot_mgr_unlock(rot_dev->mgr);

			if (failed_count) {
				SDEDEV_ERR(rot_dev->dev,
					"pending request failed in device run s:%d f:%d\n",
					ctx->session_id,
					failed_count);
				rot_dev->stats.fail_count++;
				ATRACE_INT("fail_count",
						rot_dev->stats.fail_count);
				goto error_process_buffers;
			}

			src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
			dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
			if (!src_buf || !dst_buf) {
				SDEDEV_ERR(rot_dev->dev,
					"null buffer in device run s:%d sb:%pK db:%pK\n",
					ctx->session_id,
					src_buf, dst_buf);
				goto error_process_buffers;
			}

			sde_rotator_update_retire_sequence(request);
			sde_rotator_retire_request(request);
			v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE);
			v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE);
			v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
		} else {
			/* pending request not complete. something wrong. */
			SDEDEV_ERR(rot_dev->dev,
				"Incomplete pending request in device run s:%d\n",
				ctx->session_id);

			/* disconnect request (will be freed by core layer) */
			sde_rot_mgr_lock(rot_dev->mgr);
			sde_rotator_req_finish(rot_dev->mgr, ctx->private,
					request->req);
			sde_rot_mgr_unlock(rot_dev->mgr);

			goto error_process_buffers;
		}
	} else {
		request = list_first_entry_or_null(&ctx->retired_list,
				struct sde_rotator_request, list);
		if (!request) {
			SDEDEV_ERR(rot_dev->dev,
				"no free request in device run s:%d\n",
				ctx->session_id);
			goto error_retired_list;
		}

		spin_lock(&ctx->list_lock);
		list_del_init(&request->list);
		list_add_tail(&request->list, &ctx->pending_list);
		spin_unlock(&ctx->list_lock);

		/* no pending request. submit buffer the usual way. */
		dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
		src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
		if (!src_buf || !dst_buf) {
			SDEDEV_ERR(rot_dev->dev,
				"null buffer in device run s:%d sb:%pK db:%pK\n",
				ctx->session_id,
				src_buf, dst_buf);
			goto error_empty_buffer;
		}

		sde_rot_mgr_lock(rot_dev->mgr);
		ret = sde_rotator_process_buffers(ctx, &src_buf->vb2_buf,
				&dst_buf->vb2_buf, request);
		sde_rot_mgr_unlock(rot_dev->mgr);
		if (ret) {
			SDEDEV_ERR(rot_dev->dev,
				"fail process buffer in device run s:%d\n",
				ctx->session_id);
			rot_dev->stats.fail_count++;
			ATRACE_INT("fail_count", rot_dev->stats.fail_count);
			goto error_process_buffers;
		}
	}

	return;
error_process_buffers:
error_empty_buffer:
error_retired_list:
	sde_rotator_update_retire_sequence(request);
	sde_rotator_retire_request(request);
	src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
	dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
	if (src_buf)
		v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
	if (dst_buf)
		v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
	sde_rotator_resync_timeline(ctx->work_queue.timeline);
	v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
}

/*
 * sde_rotator_job_abort - rotator m2m job abort callback
 * @priv: Pointer rotator context.
 */
static void sde_rotator_job_abort(void *priv)
{
	struct sde_rotator_ctx *ctx = priv;
	struct sde_rotator_device *rot_dev;

	if (!ctx || !ctx->rot_dev) {
		SDEROT_ERR("null context/device\n");
		return;
	}

	rot_dev = ctx->rot_dev;
	SDEDEV_DBG(rot_dev->dev, "job abort s:%d\n", ctx->session_id);

	v4l2_m2m_job_finish(rot_dev->m2m_dev, ctx->fh.m2m_ctx);
}

/*
 * sde_rotator_job_ready - rotator m2m job ready callback
 * @priv: Pointer rotator context.
 */
static int sde_rotator_job_ready(void *priv)
{
	struct sde_rotator_ctx *ctx = priv;
	struct sde_rotator_device *rot_dev;
	struct sde_rotator_request *request;
	int ret = 0;

	if (!ctx || !ctx->rot_dev) {
		SDEROT_ERR("null context/device\n");
		return 0;
	}

	rot_dev = ctx->rot_dev;
	SDEDEV_DBG(rot_dev->dev, "job ready s:%d\n", ctx->session_id);

	request = list_first_entry_or_null(&ctx->pending_list,
			struct sde_rotator_request, list);

	if (!rot_dev->early_submit) {
		/* always ready in normal mode. */
		ret = 1;
	} else if (request && IS_ERR_OR_NULL(request->req)) {
		/* if pending request fails, forward to device run state. */
		SDEDEV_DBG(rot_dev->dev,
				"pending request fail in job ready s:%d\n",
				ctx->session_id);
		ret = 1;
	} else if (list_empty(&ctx->pending_list)) {
		/* if no pending request, submit a new request. */
		SDEDEV_DBG(rot_dev->dev,
				"submit job s:%d sc:%d dc:%d p:%d\n",
				ctx->session_id,
				v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx),
				v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx),
				!list_empty(&ctx->pending_list));

		request = list_first_entry_or_null(&ctx->retired_list,
				struct sde_rotator_request, list);
		if (!request) {
			/* should not happen */
			SDEDEV_ERR(rot_dev->dev,
					"no free request in job ready s:%d\n",
					ctx->session_id);
		} else {
			spin_lock(&ctx->list_lock);
			list_del_init(&request->list);
			list_add_tail(&request->list, &ctx->pending_list);
			spin_unlock(&ctx->list_lock);
			kthread_queue_work(ctx->work_queue.rot_kw,
					&request->submit_work);
		}
	} else if (request && !atomic_read(&request->req->pending_count)) {
		/* if pending request completed, forward to device run state */
		SDEDEV_DBG(rot_dev->dev,
				"pending request completed in job ready s:%d\n",
				ctx->session_id);
		ret = 1;
	}

	return ret;
}

/* V4l2 mem2mem handlers */
static struct v4l2_m2m_ops sde_rotator_m2m_ops = {
	.device_run	= sde_rotator_device_run,
	.job_abort	= sde_rotator_job_abort,
	.job_ready	= sde_rotator_job_ready,
};
#endif

/* Device tree match struct */
static const struct of_device_id sde_rotator_dt_match[] = {
	{
		.compatible = "qcom,sde_rotator",
		.data = NULL,
	},
	{}
};

#ifndef CONFIG_MSM_SDE_ROTATOR_INIT_ONLY
/*
 * sde_rotator_get_drv_data - rotator device driver data.
 * @dev: Pointer to device.
 */
static const void *sde_rotator_get_drv_data(struct device *dev)
{
	const struct of_device_id *match;

	match = of_match_node(sde_rotator_dt_match, dev->of_node);

	if (match)
		return match->data;

	return NULL;
}

/*
 * sde_rotator_probe - rotator device probe method.
 * @pdev: Pointer to rotator platform device.
 */
static int sde_rotator_probe(struct platform_device *pdev)
{
	struct sde_rotator_device *rot_dev;
	struct video_device *vdev;
	int ret, i;
	char name[32];

	SDEDEV_DBG(&pdev->dev, "SDE v4l2 rotator probed\n");

	/* sde rotator device struct */
	rot_dev = kzalloc(sizeof(struct sde_rotator_device), GFP_KERNEL);
	if (!rot_dev)
		return -ENOMEM;

	mutex_init(&rot_dev->lock);
	rot_dev->early_submit = SDE_ROTATOR_EARLY_SUBMIT;
	rot_dev->fence_timeout = SDE_ROTATOR_FENCE_TIMEOUT;
	rot_dev->streamoff_timeout = SDE_ROTATOR_STREAM_OFF_TIMEOUT;
	rot_dev->min_rot_clk = 0;
	rot_dev->min_bw = 0;
	rot_dev->min_overhead_us = 0;
	rot_dev->drvdata = sde_rotator_get_drv_data(&pdev->dev);
	rot_dev->open_timeout = SDE_ROTATOR_CTX_OPEN_TIMEOUT;
	init_waitqueue_head(&rot_dev->open_wq);

	rot_dev->pdev = pdev;
	rot_dev->dev = &pdev->dev;
	platform_set_drvdata(pdev, rot_dev);

	ret = sde_rotator_base_init(&rot_dev->mdata, pdev, rot_dev->drvdata);
	if (ret < 0) {
		SDEDEV_ERR(&pdev->dev, "fail init base data %d\n", ret);
		goto error_rotator_base_init;
	}

	ret = sde_rotator_core_init(&rot_dev->mgr, pdev);
	if (ret < 0) {
		if (ret == -EPROBE_DEFER)
			SDEDEV_INFO(&pdev->dev, "probe defer for core init\n");
		else
			SDEDEV_ERR(&pdev->dev, "fail init core %d\n", ret);
		goto error_rotator_core_init;
	}

	/* mem2mem device */
	rot_dev->m2m_dev = v4l2_m2m_init(&sde_rotator_m2m_ops);
	if (IS_ERR(rot_dev->m2m_dev)) {
		ret = PTR_ERR(rot_dev->m2m_dev);
		SDEDEV_ERR(&pdev->dev, "fail init mem2mem device %d\n", ret);
		goto error_m2m_init;
	}

	/* v4l2 device */
	ret = v4l2_device_register(&pdev->dev, &rot_dev->v4l2_dev);
	if (ret < 0) {
		SDEDEV_ERR(&pdev->dev, "fail register v4l2 device %d\n", ret);
		goto error_v4l2_register;
	}

	vdev = video_device_alloc();
	if (!vdev) {
		SDEDEV_ERR(&pdev->dev, "fail allocate video device\n");
		goto error_alloc_video_device;
	}

	vdev->fops = &sde_rotator_fops;
	vdev->ioctl_ops = &sde_rotator_ioctl_ops;
	vdev->lock = &rot_dev->lock;
	vdev->minor = -1;
	vdev->release = video_device_release;
	vdev->v4l2_dev = &rot_dev->v4l2_dev;
	vdev->vfl_dir = VFL_DIR_M2M;
	vdev->vfl_type = VFL_TYPE_GRABBER;
	vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M |
		V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_CAPTURE;

	strlcpy(vdev->name, SDE_ROTATOR_DRV_NAME, sizeof(vdev->name));

	ret = video_register_device(vdev, VFL_TYPE_GRABBER,
			SDE_ROTATOR_BASE_DEVICE_NUMBER);
	if (ret < 0) {
		SDEDEV_ERR(&pdev->dev, "fail register video device %d\n",
				ret);
		goto error_video_register;
	}

	rot_dev->vdev = vdev;
	video_set_drvdata(rot_dev->vdev, rot_dev);

	rot_dev->debugfs_root = sde_rotator_create_debugfs(rot_dev);

	for (i = 0; i < MAX_ROT_OPEN_SESSION; i++) {
		snprintf(name, sizeof(name), "rot_fenceq_%d_%d",
			rot_dev->dev->id, i);
		kthread_init_worker(&rot_dev->rot_kw[i]);
		rot_dev->rot_thread[i] = kthread_run(kthread_worker_fn,
			&rot_dev->rot_kw[i], name);
		if (IS_ERR(rot_dev->rot_thread[i])) {
			SDEDEV_ERR(rot_dev->dev,
				"fail allocate kthread i:%d\n", i);
			ret = -EPERM;
			goto error_kthread_create;
		}
		rot_dev->kthread_free[i] = true;
	}

	SDEDEV_INFO(&pdev->dev, "SDE v4l2 rotator probe success\n");

	return 0;
error_kthread_create:
	for (i--; i >= 0; i--)
		kthread_stop(rot_dev->rot_thread[i]);
	sde_rotator_destroy_debugfs(rot_dev->debugfs_root);
	video_unregister_device(rot_dev->vdev);
error_video_register:
	video_device_release(vdev);
error_alloc_video_device:
	v4l2_device_unregister(&rot_dev->v4l2_dev);
error_v4l2_register:
	v4l2_m2m_release(rot_dev->m2m_dev);
error_m2m_init:
	sde_rotator_core_destroy(rot_dev->mgr);
error_rotator_core_init:
	sde_rotator_base_destroy(rot_dev->mdata);
error_rotator_base_init:
	kfree(rot_dev);
	return ret;
}

/*
 * sde_rotator_remove - rotator device remove method.
 * @pdev: Pointer rotator platform device.
 */
static int sde_rotator_remove(struct platform_device *pdev)
{
	struct sde_rotator_device *rot_dev;
	int i;

	rot_dev = platform_get_drvdata(pdev);
	if (rot_dev == NULL) {
		SDEDEV_ERR(&pdev->dev, "fail get rotator drvdata\n");
		return 0;
	}

	for (i = MAX_ROT_OPEN_SESSION - 1; i >= 0; i--)
		kthread_stop(rot_dev->rot_thread[i]);
	sde_rotator_destroy_debugfs(rot_dev->debugfs_root);
	video_unregister_device(rot_dev->vdev);
	video_device_release(rot_dev->vdev);
	v4l2_device_unregister(&rot_dev->v4l2_dev);
	v4l2_m2m_release(rot_dev->m2m_dev);
	sde_rotator_core_destroy(rot_dev->mgr);
	sde_rotator_base_destroy(rot_dev->mdata);
	kfree(rot_dev);
	return 0;
}

static const struct dev_pm_ops sde_rotator_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(sde_rotator_pm_suspend, sde_rotator_pm_resume)
	SET_RUNTIME_PM_OPS(sde_rotator_runtime_suspend,
			sde_rotator_runtime_resume,
			sde_rotator_runtime_idle)
};

/* SDE Rotator platform driver definition */
static struct platform_driver rotator_driver = {
	.probe = sde_rotator_probe,
	.remove = sde_rotator_remove,
	.suspend = sde_rotator_suspend,
	.resume = sde_rotator_resume,
	.driver = {
		.name = SDE_ROTATOR_DRV_NAME,
		.of_match_table = sde_rotator_dt_match,
		.pm = &sde_rotator_pm_ops,
	},
};

#else
/*
 * sde_rotator_probe - rotator device probe method.
 * @pdev: Pointer to rotator platform device.
 */
static int sde_rotator_probe(struct platform_device *pdev)
{
	return 0;
}

/*
 * sde_rotator_remove - rotator device remove method.
 * @pdev: Pointer rotator platform device.
 */
static int sde_rotator_remove(struct platform_device *pdev)
{
	return 0;
}

/* SDE Rotator platform driver definition */
static struct platform_driver rotator_driver = {
	.probe = sde_rotator_probe,
	.remove = sde_rotator_remove,
	.driver = {
		.name = SDE_ROTATOR_DRV_NAME,
		.of_match_table = sde_rotator_dt_match,
	},
};
#endif

void sde_rotator_register(void)
{
	platform_driver_register(&rotator_driver);
}

void sde_rotator_unregister(void)
{
	platform_driver_unregister(&rotator_driver);
}
