// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2015-2019, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt)	"%s: " fmt, __func__

#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/delay.h>
#include <linux/debugfs.h>
#include <linux/interrupt.h>

#include "sde_rotator_r1_hwio.h"
#include "sde_rotator_core.h"
#include "sde_rotator_util.h"
#include "sde_rotator_r1_internal.h"
#include "sde_rotator_r1.h"
#include "sde_rotator_r1_debug.h"

struct sde_mdp_hw_resource {
	struct sde_rot_hw_resource hw;
	struct sde_mdp_ctl *ctl;
	struct sde_mdp_mixer *mixer;
	struct sde_mdp_pipe *pipe;
	struct sde_mdp_writeback *wb;
};

struct sde_rotator_r1_data {
	struct sde_rot_mgr *mgr;
	int wb_id;
	int ctl_id;
	int irq_num;
	struct sde_mdp_hw_resource *mdp_hw;
};

static u32 sde_hw_rotator_input_pixfmts[] = {
	SDE_PIX_FMT_XRGB_8888,
	SDE_PIX_FMT_ARGB_8888,
	SDE_PIX_FMT_ABGR_8888,
	SDE_PIX_FMT_RGBA_8888,
	SDE_PIX_FMT_BGRA_8888,
	SDE_PIX_FMT_RGBX_8888,
	SDE_PIX_FMT_BGRX_8888,
	SDE_PIX_FMT_XBGR_8888,
	SDE_PIX_FMT_RGBA_5551,
	SDE_PIX_FMT_ARGB_1555,
	SDE_PIX_FMT_ABGR_1555,
	SDE_PIX_FMT_BGRA_5551,
	SDE_PIX_FMT_BGRX_5551,
	SDE_PIX_FMT_RGBX_5551,
	SDE_PIX_FMT_XBGR_1555,
	SDE_PIX_FMT_XRGB_1555,
	SDE_PIX_FMT_ARGB_4444,
	SDE_PIX_FMT_RGBA_4444,
	SDE_PIX_FMT_BGRA_4444,
	SDE_PIX_FMT_ABGR_4444,
	SDE_PIX_FMT_RGBX_4444,
	SDE_PIX_FMT_XRGB_4444,
	SDE_PIX_FMT_BGRX_4444,
	SDE_PIX_FMT_XBGR_4444,
	SDE_PIX_FMT_RGB_888,
	SDE_PIX_FMT_BGR_888,
	SDE_PIX_FMT_RGB_565,
	SDE_PIX_FMT_BGR_565,
	SDE_PIX_FMT_Y_CB_CR_H2V2,
	SDE_PIX_FMT_Y_CR_CB_H2V2,
	SDE_PIX_FMT_Y_CR_CB_GH2V2,
	SDE_PIX_FMT_Y_CBCR_H2V2,
	SDE_PIX_FMT_Y_CRCB_H2V2,
	SDE_PIX_FMT_Y_CBCR_H1V2,
	SDE_PIX_FMT_Y_CRCB_H1V2,
	SDE_PIX_FMT_Y_CBCR_H2V1,
	SDE_PIX_FMT_Y_CRCB_H2V1,
	SDE_PIX_FMT_YCBYCR_H2V1,
	SDE_PIX_FMT_Y_CBCR_H2V2_VENUS,
	SDE_PIX_FMT_Y_CRCB_H2V2_VENUS,
	SDE_PIX_FMT_RGBA_8888_UBWC,
	SDE_PIX_FMT_RGBX_8888_UBWC,
	SDE_PIX_FMT_RGB_565_UBWC,
	SDE_PIX_FMT_Y_CBCR_H2V2_UBWC,
};

static u32 sde_hw_rotator_output_pixfmts[] = {
	SDE_PIX_FMT_XRGB_8888,
	SDE_PIX_FMT_ARGB_8888,
	SDE_PIX_FMT_ABGR_8888,
	SDE_PIX_FMT_RGBA_8888,
	SDE_PIX_FMT_BGRA_8888,
	SDE_PIX_FMT_RGBX_8888,
	SDE_PIX_FMT_BGRX_8888,
	SDE_PIX_FMT_XBGR_8888,
	SDE_PIX_FMT_RGBA_5551,
	SDE_PIX_FMT_ARGB_1555,
	SDE_PIX_FMT_ABGR_1555,
	SDE_PIX_FMT_BGRA_5551,
	SDE_PIX_FMT_BGRX_5551,
	SDE_PIX_FMT_RGBX_5551,
	SDE_PIX_FMT_XBGR_1555,
	SDE_PIX_FMT_XRGB_1555,
	SDE_PIX_FMT_ARGB_4444,
	SDE_PIX_FMT_RGBA_4444,
	SDE_PIX_FMT_BGRA_4444,
	SDE_PIX_FMT_ABGR_4444,
	SDE_PIX_FMT_RGBX_4444,
	SDE_PIX_FMT_XRGB_4444,
	SDE_PIX_FMT_BGRX_4444,
	SDE_PIX_FMT_XBGR_4444,
	SDE_PIX_FMT_RGB_888,
	SDE_PIX_FMT_BGR_888,
	SDE_PIX_FMT_RGB_565,
	SDE_PIX_FMT_BGR_565,
	SDE_PIX_FMT_Y_CB_CR_H2V2,
	SDE_PIX_FMT_Y_CR_CB_H2V2,
	SDE_PIX_FMT_Y_CR_CB_GH2V2,
	SDE_PIX_FMT_Y_CBCR_H2V2,
	SDE_PIX_FMT_Y_CRCB_H2V2,
	SDE_PIX_FMT_Y_CBCR_H1V2,
	SDE_PIX_FMT_Y_CRCB_H1V2,
	SDE_PIX_FMT_Y_CBCR_H2V1,
	SDE_PIX_FMT_Y_CRCB_H2V1,
	SDE_PIX_FMT_YCBYCR_H2V1,
	SDE_PIX_FMT_Y_CBCR_H2V2_VENUS,
	SDE_PIX_FMT_Y_CRCB_H2V2_VENUS,
	SDE_PIX_FMT_RGBA_8888_UBWC,
	SDE_PIX_FMT_RGBX_8888_UBWC,
	SDE_PIX_FMT_RGB_565_UBWC,
	SDE_PIX_FMT_Y_CBCR_H2V2_UBWC,
};

static struct sde_mdp_hw_resource *sde_rotator_hw_alloc(
	struct sde_rot_mgr *mgr, u32 ctl_id, u32 wb_id, int irq_num)
{
	struct sde_mdp_hw_resource *mdp_hw;
	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
	int pipe_ndx, offset = ctl_id;
	int ret = 0;

	mdp_hw = devm_kzalloc(&mgr->pdev->dev,
			sizeof(struct sde_mdp_hw_resource), GFP_KERNEL);
	if (!mdp_hw)
		return ERR_PTR(-ENOMEM);

	mdp_hw->ctl = sde_mdp_ctl_alloc(mdata, offset);
	if (IS_ERR_OR_NULL(mdp_hw->ctl)) {
		SDEROT_ERR("unable to allocate ctl\n");
		ret = -ENODEV;
		goto error;
	}
	mdp_hw->ctl->irq_num = irq_num;

	mdp_hw->wb = sde_mdp_wb_assign(wb_id, mdp_hw->ctl->num);
	if (IS_ERR_OR_NULL(mdp_hw->wb)) {
		SDEROT_ERR("unable to allocate wb\n");
		ret = -ENODEV;
		goto error;
	}

	mdp_hw->ctl->wb = mdp_hw->wb;
	mdp_hw->mixer = sde_mdp_mixer_assign(mdp_hw->wb->num, true);
	if (IS_ERR_OR_NULL(mdp_hw->mixer)) {
		SDEROT_ERR("unable to allocate wb mixer\n");
		ret = -ENODEV;
		goto error;
	}

	mdp_hw->ctl->mixer_left = mdp_hw->mixer;
	mdp_hw->mixer->ctl = mdp_hw->ctl;

	mdp_hw->mixer->rotator_mode = true;

	switch (mdp_hw->mixer->num) {
	case SDE_MDP_WB_LAYERMIXER0:
		mdp_hw->ctl->opmode = SDE_MDP_CTL_OP_ROT0_MODE;
		break;
	case SDE_MDP_WB_LAYERMIXER1:
		mdp_hw->ctl->opmode =  SDE_MDP_CTL_OP_ROT1_MODE;
		break;
	default:
		SDEROT_ERR("invalid layer mixer=%d\n", mdp_hw->mixer->num);
		ret = -EINVAL;
		goto error;
	}

	mdp_hw->ctl->ops.start_fnc = sde_mdp_writeback_start;
	mdp_hw->ctl->wb_type = SDE_MDP_WB_CTL_TYPE_BLOCK;

	if (mdp_hw->ctl->ops.start_fnc)
		ret = mdp_hw->ctl->ops.start_fnc(mdp_hw->ctl);

	if (ret)
		goto error;

	/* override from dt */
	pipe_ndx = wb_id;
	mdp_hw->pipe = sde_mdp_pipe_assign(mdata, mdp_hw->mixer, pipe_ndx);
	if (IS_ERR_OR_NULL(mdp_hw->pipe)) {
		SDEROT_ERR("dma pipe allocation failed\n");
		ret = -ENODEV;
		goto error;
	}

	mdp_hw->pipe->mixer_left = mdp_hw->mixer;
	mdp_hw->hw.wb_id = mdp_hw->wb->num;
	mdp_hw->hw.pending_count = 0;
	atomic_set(&mdp_hw->hw.num_active, 0);
	mdp_hw->hw.max_active = 1;
	init_waitqueue_head(&mdp_hw->hw.wait_queue);

	return mdp_hw;
error:
	if (!IS_ERR_OR_NULL(mdp_hw->pipe))
		sde_mdp_pipe_destroy(mdp_hw->pipe);
	if (!IS_ERR_OR_NULL(mdp_hw->ctl)) {
		if (mdp_hw->ctl->ops.stop_fnc)
			mdp_hw->ctl->ops.stop_fnc(mdp_hw->ctl, 0);
		sde_mdp_ctl_free(mdp_hw->ctl);
	}
	devm_kfree(&mgr->pdev->dev, mdp_hw);

	return ERR_PTR(ret);
}

static void sde_rotator_hw_free(struct sde_rot_mgr *mgr,
	struct sde_mdp_hw_resource *mdp_hw)
{
	struct sde_mdp_mixer *mixer;
	struct sde_mdp_ctl *ctl;

	if (!mgr || !mdp_hw)
		return;

	mixer = mdp_hw->pipe->mixer_left;

	sde_mdp_pipe_destroy(mdp_hw->pipe);

	ctl = sde_mdp_ctl_mixer_switch(mixer->ctl,
		SDE_MDP_WB_CTL_TYPE_BLOCK);
	if (ctl) {
		if (ctl->ops.stop_fnc)
			ctl->ops.stop_fnc(ctl, 0);
		sde_mdp_ctl_free(ctl);
	}

	devm_kfree(&mgr->pdev->dev, mdp_hw);
}

static struct sde_rot_hw_resource *sde_rotator_hw_alloc_ext(
	struct sde_rot_mgr *mgr, u32 pipe_id, u32 wb_id)
{
	struct sde_mdp_hw_resource *mdp_hw;
	struct sde_rotator_r1_data *hw_data;

	if (!mgr || !mgr->hw_data)
		return NULL;

	hw_data = mgr->hw_data;
	mdp_hw = hw_data->mdp_hw;

	return &mdp_hw->hw;
}

static void sde_rotator_hw_free_ext(struct sde_rot_mgr *mgr,
	struct sde_rot_hw_resource *hw)
{
	/* currently nothing specific for this device */
}

static void sde_rotator_translate_rect(struct sde_rect *dst,
	struct sde_rect *src)
{
	dst->x = src->x;
	dst->y = src->y;
	dst->w = src->w;
	dst->h = src->h;
}

static u32 sde_rotator_translate_flags(u32 input)
{
	u32 output = 0;

	if (input & SDE_ROTATION_NOP)
		output |= SDE_ROT_NOP;
	if (input & SDE_ROTATION_FLIP_LR)
		output |= SDE_FLIP_LR;
	if (input & SDE_ROTATION_FLIP_UD)
		output |= SDE_FLIP_UD;
	if (input & SDE_ROTATION_90)
		output |= SDE_ROT_90;
	if (input & SDE_ROTATION_DEINTERLACE)
		output |= SDE_DEINTERLACE;
	if (input & SDE_ROTATION_SECURE)
		output |= SDE_SECURE_OVERLAY_SESSION;
	return output;
}

static int sde_rotator_config_hw(struct sde_rot_hw_resource *hw,
	struct sde_rot_entry *entry)
{
	struct sde_mdp_hw_resource *mdp_hw;
	struct sde_mdp_pipe *pipe;
	struct sde_rotation_item *item;
	int ret;

	if (!hw || !entry) {
		SDEROT_ERR("null hw resource/entry");
		return -EINVAL;
	}

	mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw);

	pipe = mdp_hw->pipe;
	item = &entry->item;

	pipe->flags = sde_rotator_translate_flags(item->flags);
	pipe->src_fmt = sde_get_format_params(item->input.format);
	pipe->img_width = item->input.width;
	pipe->img_height = item->input.height;
	sde_rotator_translate_rect(&pipe->src, &item->src_rect);
	sde_rotator_translate_rect(&pipe->dst, &item->src_rect);

	pipe->params_changed++;

	ret = sde_mdp_pipe_queue_data(pipe, &entry->src_buf);
	SDEROT_DBG("Config pipe. src{%u,%u,%u,%u}f=%u\n"
		"dst{%u,%u,%u,%u}f=%u session_id=%u\n",
		item->src_rect.x, item->src_rect.y,
		item->src_rect.w, item->src_rect.h, item->input.format,
		item->dst_rect.x, item->dst_rect.y,
		item->dst_rect.w, item->dst_rect.h, item->output.format,
		item->session_id);

	return ret;
}

static int sde_rotator_cancel_hw(struct sde_rot_hw_resource *hw,
	struct sde_rot_entry *entry)
{
	return 0;
}

static int sde_rotator_abort_hw(struct sde_rot_hw_resource *hw,
	struct sde_rot_entry *entry)
{
	return 0;
}

static int sde_rotator_kickoff_entry(struct sde_rot_hw_resource *hw,
	struct sde_rot_entry *entry)
{
	struct sde_mdp_hw_resource *mdp_hw;
	int ret;
	struct sde_mdp_writeback_arg wb_args;

	if (!hw || !entry) {
		SDEROT_ERR("null hw resource/entry");
		return -EINVAL;
	}

	wb_args.data = &entry->dst_buf;
	wb_args.priv_data = entry;

	mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw);

	ret = sde_mdp_writeback_display_commit(mdp_hw->ctl, &wb_args);
	return ret;
}

static int sde_rotator_wait_for_entry(struct sde_rot_hw_resource *hw,
	struct sde_rot_entry *entry)
{
	struct sde_mdp_hw_resource *mdp_hw;
	int ret;
	struct sde_mdp_ctl *ctl;

	if (!hw || !entry) {
		SDEROT_ERR("null hw resource/entry");
		return -EINVAL;
	}

	mdp_hw = container_of(hw, struct sde_mdp_hw_resource, hw);

	ctl = mdp_hw->ctl;

	ret = sde_mdp_display_wait4comp(ctl);

	return ret;
}

static int sde_rotator_hw_validate_entry(struct sde_rot_mgr *mgr,
	struct sde_rot_entry *entry)
{
	int ret = 0;
	u16 src_w, src_h, dst_w, dst_h, bit;
	struct sde_rotation_item *item = &entry->item;
	struct sde_mdp_format_params *fmt;

	src_w = item->src_rect.w;
	src_h = item->src_rect.h;

	if (item->flags & SDE_ROTATION_90) {
		dst_w = item->dst_rect.h;
		dst_h = item->dst_rect.w;
	} else {
		dst_w = item->dst_rect.w;
		dst_h = item->dst_rect.h;
	}

	entry->dnsc_factor_w = 0;
	entry->dnsc_factor_h = 0;

	if ((src_w != dst_w) || (src_h != dst_h)) {
		if ((src_w % dst_w) || (src_h % dst_h)) {
			SDEROT_DBG("non integral scale not support\n");
			ret = -EINVAL;
			goto dnsc_err;
		}
		entry->dnsc_factor_w = src_w / dst_w;
		bit = fls(entry->dnsc_factor_w);
		if ((entry->dnsc_factor_w & ~BIT(bit - 1)) || (bit > 5)) {
			SDEROT_DBG("non power-of-2 scale not support\n");
			ret = -EINVAL;
			goto dnsc_err;
		}
		entry->dnsc_factor_h = src_h / dst_h;
		bit = fls(entry->dnsc_factor_h);
		if ((entry->dnsc_factor_h & ~BIT(bit - 1)) || (bit > 5)) {
			SDEROT_DBG("non power-of-2 dscale not support\n");
			ret = -EINVAL;
			goto dnsc_err;
		}
	}

	fmt =  sde_get_format_params(item->output.format);
	if (sde_mdp_is_ubwc_format(fmt) &&
		(entry->dnsc_factor_h || entry->dnsc_factor_w)) {
		SDEROT_DBG("downscale with ubwc not support\n");
		ret = -EINVAL;
	}

dnsc_err:

	/* Downscaler does not support asymmetrical dnsc */
	if (entry->dnsc_factor_w != entry->dnsc_factor_h) {
		SDEROT_DBG("asymmetric downscale not support\n");
		ret = -EINVAL;
	}

	if (ret) {
		entry->dnsc_factor_w = 0;
		entry->dnsc_factor_h = 0;
	}
	return ret;
}

static ssize_t sde_rotator_hw_show_caps(struct sde_rot_mgr *mgr,
		struct device_attribute *attr, char *buf, ssize_t len)
{
	struct sde_rotator_r1_data *hw_data;
	int cnt = 0;

	if (!mgr || !buf)
		return 0;

	hw_data = mgr->hw_data;

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

	SPRINT("wb_id=%d\n", hw_data->wb_id);
	SPRINT("ctl_id=%d\n", hw_data->ctl_id);
	return cnt;
}

static ssize_t sde_rotator_hw_show_state(struct sde_rot_mgr *mgr,
		struct device_attribute *attr, char *buf, ssize_t len)
{
	struct sde_rotator_r1_data *hw_data;
	int cnt = 0;

	if (!mgr || !buf)
		return 0;

	hw_data = mgr->hw_data;

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

	if (hw_data && hw_data->mdp_hw) {
		struct sde_rot_hw_resource *hw = &hw_data->mdp_hw->hw;

		SPRINT("irq_num=%d\n", hw_data->irq_num);
		SPRINT("max_active=%d\n", hw->max_active);
		SPRINT("num_active=%d\n", atomic_read(&hw->num_active));
		SPRINT("pending_cnt=%u\n", hw->pending_count);
	}

	return cnt;
}

/*
 * sde_hw_rotator_get_pixfmt - get the indexed pixel format
 * @mgr: Pointer to rotator manager
 * @index: index of pixel format
 * @input: true for input port; false for output port
 * @mode: operating mode
 */
static u32 sde_hw_rotator_get_pixfmt(struct sde_rot_mgr *mgr,
		int index, bool input, u32 mode)
{
	if (input) {
		if (index < ARRAY_SIZE(sde_hw_rotator_input_pixfmts))
			return sde_hw_rotator_input_pixfmts[index];
		else
			return 0;
	} else {
		if (index < ARRAY_SIZE(sde_hw_rotator_output_pixfmts))
			return sde_hw_rotator_output_pixfmts[index];
		else
			return 0;
	}
}

/*
 * sde_hw_rotator_is_valid_pixfmt - verify if the given pixel format is valid
 * @mgr: Pointer to rotator manager
 * @pixfmt: pixel format to be verified
 * @input: true for input port; false for output port
 * @mode: operating mode
 */
static int sde_hw_rotator_is_valid_pixfmt(struct sde_rot_mgr *mgr, u32 pixfmt,
		bool input, u32 mode)
{
	int i;

	if (input) {
		for (i = 0; i < ARRAY_SIZE(sde_hw_rotator_input_pixfmts); i++)
			if (sde_hw_rotator_input_pixfmts[i] == pixfmt)
				return true;
	} else {
		for (i = 0; i < ARRAY_SIZE(sde_hw_rotator_output_pixfmts); i++)
			if (sde_hw_rotator_output_pixfmts[i] == pixfmt)
				return true;
	}

	return false;
}

static int sde_rotator_hw_parse_dt(struct sde_rotator_r1_data *hw_data,
		struct platform_device *dev)
{
	int ret = 0;
	u32 data;

	if (!hw_data || !dev)
		return -EINVAL;

	ret = of_property_read_u32(dev->dev.of_node,
			"qcom,mdss-wb-id", &data);
	if (ret)
		hw_data->wb_id = -1;
	else
		hw_data->wb_id = (int) data;
	ret = of_property_read_u32(dev->dev.of_node,
			"qcom,mdss-ctl-id", &data);
	if (ret)
		hw_data->ctl_id = -1;
	else
		hw_data->ctl_id = (int) data;

	return ret;
}

static int sde_rotator_hw_rev_init(struct sde_rot_data_type *mdata)
{
	if (!mdata) {
		SDEROT_ERR("null rotator data\n");
		return -EINVAL;
	}

	clear_bit(SDE_QOS_PER_PIPE_IB, mdata->sde_qos_map);
	set_bit(SDE_QOS_OVERHEAD_FACTOR, mdata->sde_qos_map);
	clear_bit(SDE_QOS_CDP, mdata->sde_qos_map);
	set_bit(SDE_QOS_OTLIM, mdata->sde_qos_map);
	set_bit(SDE_QOS_PER_PIPE_LUT, mdata->sde_qos_map);
	clear_bit(SDE_QOS_SIMPLIFIED_PREFILL, mdata->sde_qos_map);
	set_bit(SDE_CAPS_R1_WB, mdata->sde_caps_map);

	return 0;
}

enum {
	SDE_ROTATOR_INTR_WB_0,
	SDE_ROTATOR_INTR_WB_1,
	SDE_ROTATOR_INTR_MAX,
};

struct intr_callback {
	void (*func)(void *data);
	void *arg;
};

struct intr_callback sde_intr_cb[SDE_ROTATOR_INTR_MAX];

int sde_mdp_set_intr_callback(u32 intr_type, u32 intf_num,
			       void (*fnc_ptr)(void *), void *arg)
{
	if (intf_num >= SDE_ROTATOR_INTR_MAX) {
		SDEROT_WARN("invalid intr type=%u intf_num=%u\n",
				intr_type, intf_num);
		return -EINVAL;
	}

	sde_intr_cb[intf_num].func = fnc_ptr;
	sde_intr_cb[intf_num].arg = arg;

	return 0;
}

static irqreturn_t sde_irq_handler(int irq, void *ptr)
{
	struct sde_rot_data_type *mdata = ptr;
	irqreturn_t ret = IRQ_NONE;
	u32 isr;

	isr = readl_relaxed(mdata->mdp_base + SDE_MDP_REG_INTR_STATUS);

	SDEROT_DBG("intr_status = %8.8x\n", isr);

	if (isr & SDE_MDP_INTR_WB_0_DONE) {
		struct intr_callback *cb = &sde_intr_cb[SDE_ROTATOR_INTR_WB_0];

		if (cb->func) {
			writel_relaxed(SDE_MDP_INTR_WB_0_DONE,
				mdata->mdp_base + SDE_MDP_REG_INTR_CLEAR);
			cb->func(cb->arg);
			ret = IRQ_HANDLED;
		}
	}

	if (isr & SDE_MDP_INTR_WB_1_DONE) {
		struct intr_callback *cb = &sde_intr_cb[SDE_ROTATOR_INTR_WB_1];

		if (cb->func) {
			writel_relaxed(SDE_MDP_INTR_WB_1_DONE,
				mdata->mdp_base + SDE_MDP_REG_INTR_CLEAR);
			cb->func(cb->arg);
			ret = IRQ_HANDLED;
		}
	}

	return ret;
}

static void sde_rotator_hw_destroy(struct sde_rot_mgr *mgr)
{
	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
	struct sde_rotator_r1_data *hw_data;

	if (!mgr || !mgr->pdev || !mgr->hw_data)
		return;

	hw_data = mgr->hw_data;
	if (hw_data->irq_num >= 0)
		devm_free_irq(&mgr->pdev->dev, hw_data->irq_num, mdata);
	sde_rotator_hw_free(mgr, hw_data->mdp_hw);
	devm_kfree(&mgr->pdev->dev, mgr->hw_data);
	mgr->hw_data = NULL;
}

int sde_rotator_r1_init(struct sde_rot_mgr *mgr)
{
	struct sde_rot_data_type *mdata = sde_rot_get_mdata();
	struct sde_rotator_r1_data *hw_data;
	int ret;

	if (!mgr || !mgr->pdev) {
		SDEROT_ERR("null rotator manager/platform device");
		return -EINVAL;
	}

	hw_data = devm_kzalloc(&mgr->pdev->dev,
			sizeof(struct sde_rotator_r1_data), GFP_KERNEL);
	if (hw_data == NULL)
		return -ENOMEM;

	mgr->hw_data = hw_data;
	mgr->ops_config_hw = sde_rotator_config_hw;
	mgr->ops_cancel_hw = sde_rotator_cancel_hw;
	mgr->ops_abort_hw = sde_rotator_abort_hw;
	mgr->ops_kickoff_entry = sde_rotator_kickoff_entry;
	mgr->ops_wait_for_entry = sde_rotator_wait_for_entry;
	mgr->ops_hw_alloc = sde_rotator_hw_alloc_ext;
	mgr->ops_hw_free = sde_rotator_hw_free_ext;
	mgr->ops_hw_destroy = sde_rotator_hw_destroy;
	mgr->ops_hw_validate_entry = sde_rotator_hw_validate_entry;
	mgr->ops_hw_show_caps = sde_rotator_hw_show_caps;
	mgr->ops_hw_show_state = sde_rotator_hw_show_state;
	mgr->ops_hw_create_debugfs = sde_rotator_r1_create_debugfs;
	mgr->ops_hw_get_pixfmt = sde_hw_rotator_get_pixfmt;
	mgr->ops_hw_is_valid_pixfmt = sde_hw_rotator_is_valid_pixfmt;

	ret = sde_rotator_hw_parse_dt(mgr->hw_data, mgr->pdev);
	if (ret)
		goto error_parse_dt;

	hw_data->irq_num = platform_get_irq(mgr->pdev, 0);
	if (hw_data->irq_num < 0) {
		SDEROT_ERR("fail to get rotator irq\n");
	} else {
		ret = devm_request_threaded_irq(&mgr->pdev->dev,
				hw_data->irq_num,
				sde_irq_handler, NULL,
				0, "sde_rotator_r1", mdata);
		if (ret) {
			SDEROT_ERR("fail to request irq r:%d\n", ret);
			hw_data->irq_num = -1;
		} else {
			disable_irq(hw_data->irq_num);
		}
	}

	hw_data->mdp_hw = sde_rotator_hw_alloc(mgr, hw_data->ctl_id,
			hw_data->wb_id, hw_data->irq_num);
	if (IS_ERR_OR_NULL(hw_data->mdp_hw))
		goto error_hw_alloc;

	ret = sde_rotator_hw_rev_init(sde_rot_get_mdata());
	if (ret)
		goto error_hw_rev_init;

	hw_data->mgr = mgr;

	return 0;
error_hw_rev_init:
	if (hw_data->irq_num >= 0)
		devm_free_irq(&mgr->pdev->dev, hw_data->irq_num, mdata);
	sde_rotator_hw_free(mgr, hw_data->mdp_hw);
error_hw_alloc:
	devm_kfree(&mgr->pdev->dev, mgr->hw_data);
error_parse_dt:
	return ret;
}
