Orange Pi5 kernel

Deprecated Linux kernel 5.10.110 for OrangePi 5/5B/5+ boards

3 Commits   0 Branches   0 Tags
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2021 Fuzhou Rockchip Electronics Co., Ltd
 *
 * author:
 *	Ding Wei, leo.ding@rock-chips.com
 *
 */

#define pr_fmt(fmt) "mpp_av1dec: " fmt

#include <asm/cacheflush.h>
#include <linux/clk.h>
#include <linux/clk/clk-conf.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/of_platform.h>
#include <linux/clk/clk-conf.h>
#include <linux/pm_runtime.h>
#include <linux/pm_domain.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/regmap.h>
#include <linux/proc_fs.h>
#include <soc/rockchip/pm_domains.h>

#include "mpp_debug.h"
#include "mpp_common.h"
#include "mpp_iommu.h"

#define AV1DEC_DRIVER_NAME		"mpp_av1dec"

#define	AV1DEC_SESSION_MAX_BUFFERS		40

/* REG_DEC_INT, bits for interrupt */
#define	AV1DEC_INT_PIC_INF		BIT(24)
#define	AV1DEC_INT_TIMEOUT		BIT(18)
#define	AV1DEC_INT_SLICE		BIT(17)
#define	AV1DEC_INT_STRM_ERROR		BIT(16)
#define	AV1DEC_INT_ASO_ERROR		BIT(15)
#define	AV1DEC_INT_BUF_EMPTY		BIT(14)
#define	AV1DEC_INT_BUS_ERROR		BIT(13)
#define	AV1DEC_DEC_INT			BIT(12)
#define	AV1DEC_DEC_INT_RAW		BIT(8)
#define	AV1DEC_DEC_IRQ_DIS		BIT(4)
#define	AV1DEC_DEC_START		BIT(0)

#define MPP_ALIGN(x, a)         (((x)+(a)-1)&~((a)-1))
/* REG_DEC_EN, bit for gate */
#define	AV1DEC_CLOCK_GATE_EN		BIT(10)

#define to_av1dec_info(info)		\
		container_of(info, struct av1dec_hw_info, hw)
#define to_av1dec_task(ctx)		\
		container_of(ctx, struct av1dec_task, mpp_task)
#define to_av1dec_dev(dev)		\
		container_of(dev, struct av1dec_dev, mpp)

/* define functions */
#define MPP_GET_BITS(v, p, b)	(((v) >> (p)) & ((1 << (b)) - 1))
#define MPP_BASE_TO_IDX(a)	((a) / sizeof(u32))

enum AV1DEC_CLASS_TYPE {
	AV1DEC_CLASS_VCD	= 0,
	AV1DEC_CLASS_CACHE	= 1,
	AV1DEC_CLASS_AFBC	= 2,
	AV1DEC_CLASS_BUTT,
};

enum av1dec_trans_type {
	AV1DEC_TRANS_BASE	= 0x0000,

	AV1DEC_TRANS_VCD	= AV1DEC_TRANS_BASE + 0,
	AV1DEC_TRANS_CACHE	= AV1DEC_TRANS_BASE + 1,
	AV1DEC_TRANS_AFBC	= AV1DEC_TRANS_BASE + 2,
	AV1DEC_TRANS_BUTT,
};

struct av1dec_hw_info {
	struct mpp_hw_info hw;
	/* register range by class */
	u32 reg_class_num;
	struct {
		u32 base_s;
		u32 base_e;
	} reg_class[AV1DEC_CLASS_BUTT];
	/* fd translate for class */
	u32 trans_class_num;
	struct {
		u32 class;
		u32 trans_fmt;
	} trans_class[AV1DEC_TRANS_BUTT];

	/* interrupt config register */
	int int_base;
	/* enable hardware register */
	int en_base;
	/* status register */
	int sta_base;
	/* clear irq register */
	int clr_base;
	/* stream register */
	int strm_base;

	u32 err_mask;
};

struct av1dec_task {
	struct mpp_task mpp_task;

	struct av1dec_hw_info *hw_info;
	/* for malloc register data buffer */
	u32 *reg_data;
	/* class register */
	struct {
		u32 valid;
		u32 base;
		u32 *data;
		/* offset base reg_data */
		u32 off;
		/* length for class */
		u32 len;
	} reg_class[AV1DEC_CLASS_BUTT];
	/* register offset info */
	struct reg_offset_info off_inf;

	enum MPP_CLOCK_MODE clk_mode;
	u32 irq_status;
	/* req for current task */
	u32 w_req_cnt;
	struct mpp_request w_reqs[MPP_MAX_MSG_NUM];
	u32 r_req_cnt;
	struct mpp_request r_reqs[MPP_MAX_MSG_NUM];
};

struct av1dec_dev {
	struct mpp_dev mpp;
	struct av1dec_hw_info *hw_info;

	struct mpp_clk_info aclk_info;
	struct mpp_clk_info hclk_info;
	u32 default_max_load;
#ifdef CONFIG_PROC_FS
	struct proc_dir_entry *procfs;
#endif
	struct reset_control *rst_a;
	struct reset_control *rst_h;

	void __iomem *reg_base[AV1DEC_CLASS_BUTT];
	int irq[AV1DEC_CLASS_BUTT];
};

static struct av1dec_hw_info av1dec_hw_info = {
	.hw = {
		.reg_num = 512,
		.reg_id = 0,
		.reg_en = 1,
		.reg_start = 1,
		.reg_end = 319,
	},
	.reg_class_num = 3,
	.reg_class[AV1DEC_CLASS_VCD] = {
		.base_s = 0x0000,
		.base_e = 0x07fc,
	},
	.reg_class[AV1DEC_CLASS_CACHE] = {
		.base_s = 0x10000,
		.base_e = 0x10294,
	},
	.reg_class[AV1DEC_CLASS_AFBC] = {
		.base_s = 0x20000,
		.base_e = 0x2034c,
	},
	.trans_class_num = AV1DEC_TRANS_BUTT,
	.trans_class[AV1DEC_CLASS_VCD] = {
		.class = AV1DEC_CLASS_VCD,
		.trans_fmt = AV1DEC_TRANS_VCD,
	},
	.trans_class[AV1DEC_CLASS_CACHE] = {
		.class = AV1DEC_CLASS_CACHE,
		.trans_fmt = AV1DEC_TRANS_CACHE,
	},
	.trans_class[AV1DEC_CLASS_AFBC] = {
		.class = AV1DEC_CLASS_AFBC,
		.trans_fmt = AV1DEC_TRANS_AFBC,
	},
	.int_base = 0x0004,
	.en_base = 0x0004,
	.sta_base = 0x0004,
	.clr_base = 0x0004,
	.strm_base = 0x02a4,
	.err_mask = 0x7e000,
};

/*
 * file handle translate information for v2
 */
static const u16 trans_tbl_av1_vcd[] = {
	65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91,
	93, 95, 97, 99, 101, 103, 105, 107, 109, 111, 113, 115,
	117, 133, 135, 137, 139, 141, 143, 145, 147,
	167, 169, 171, 173, 175, 177, 179, 183, 190, 192, 194,
	196, 198, 200, 202, 204, 224, 226, 228, 230, 232, 234,
	236, 238, 326, 328, 339, 341, 348, 350, 505, 507
};

static const u16 trans_tbl_av1_cache[] = {
	13, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73, 78, 83, 88,
	134, 135, 138, 139, 142, 143, 146, 147,
};

static const u16 trans_tbl_av1_afbc[] = {
	32, 33, 34, 35, 48, 49, 50, 51, 96, 97, 98, 99
};

static struct mpp_trans_info trans_av1dec[] = {
	[AV1DEC_TRANS_VCD] = {
		.count = ARRAY_SIZE(trans_tbl_av1_vcd),
		.table = trans_tbl_av1_vcd,
	},
	[AV1DEC_TRANS_CACHE] = {
		.count = ARRAY_SIZE(trans_tbl_av1_cache),
		.table = trans_tbl_av1_cache,
	},
	[AV1DEC_TRANS_AFBC] = {
		.count = ARRAY_SIZE(trans_tbl_av1_afbc),
		.table = trans_tbl_av1_afbc,
	},
};

static bool req_over_class(struct mpp_request *req,
			   struct av1dec_task *task, int class)
{
	bool ret;
	u32 base_s, base_e, req_e;
	struct av1dec_hw_info *hw = task->hw_info;

	if (class > hw->reg_class_num)
		return false;

	base_s = hw->reg_class[class].base_s;
	base_e = hw->reg_class[class].base_e;
	req_e = req->offset + req->size - sizeof(u32);

	ret = (req->offset <= base_e && req_e >= base_s) ? true : false;

	return ret;
}

static int av1dec_alloc_reg_class(struct av1dec_task *task)
{
	int i;
	u32 data_size;
	struct av1dec_hw_info *hw = task->hw_info;

	data_size = 0;
	for (i = 0; i < hw->reg_class_num; i++) {
		u32 base_s = hw->reg_class[i].base_s;
		u32 base_e = hw->reg_class[i].base_e;

		task->reg_class[i].base = base_s;
		task->reg_class[i].off = data_size;
		task->reg_class[i].len = base_e - base_s + sizeof(u32);
		data_size += task->reg_class[i].len;
	}

	task->reg_data = kzalloc(data_size, GFP_KERNEL);
	if (!task->reg_data)
		return -ENOMEM;

	for (i = 0; i < hw->reg_class_num; i++)
		task->reg_class[i].data = task->reg_data + (task->reg_class[i].off / sizeof(u32));

	return 0;
}

static int av1dec_update_req(struct av1dec_task *task, int class,
			     struct mpp_request *req_in,
			     struct mpp_request *req_out)
{
	u32 base_s, base_e, req_e, s, e;
	struct av1dec_hw_info *hw = task->hw_info;

	if (class > hw->reg_class_num)
		return -EINVAL;

	base_s = hw->reg_class[class].base_s;
	base_e = hw->reg_class[class].base_e;
	req_e = req_in->offset + req_in->size - sizeof(u32);
	s = max(req_in->offset, base_s);
	e = min(req_e, base_e);

	req_out->offset = s;
	req_out->size = e - s + sizeof(u32);
	req_out->data = (u8 *)req_in->data + (s - req_in->offset);
	mpp_debug(DEBUG_TASK_INFO, "req_out->offset=%08x, req_out->size=%d\n",
		  req_out->offset, req_out->size);

	return 0;
}

static int av1dec_extract_task_msg(struct av1dec_task *task,
				   struct mpp_task_msgs *msgs)
{
	int ret;
	u32 i;
	struct mpp_request *req;
	struct av1dec_hw_info *hw = task->hw_info;

	mpp_debug_enter();

	mpp_debug(DEBUG_TASK_INFO, "req_cnt=%d, set_cnt=%d, poll_cnt=%d, reg_class=%d\n",
		msgs->req_cnt, msgs->set_cnt, msgs->poll_cnt, hw->reg_class_num);

	for (i = 0; i < msgs->req_cnt; i++) {
		req = &msgs->reqs[i];
		mpp_debug(DEBUG_TASK_INFO, "msg: cmd %08x, offset %08x, size %d\n",
			req->cmd, req->offset, req->size);
		if (!req->size)
			continue;

		switch (req->cmd) {
		case MPP_CMD_SET_REG_WRITE: {
			u32 class;
			u32 base, *regs;
			struct mpp_request *wreq;

			for (class = 0; class < hw->reg_class_num; class++) {
				if (!req_over_class(req, task, class))
					continue;
				mpp_debug(DEBUG_TASK_INFO, "found write_calss %d\n", class);
				wreq = &task->w_reqs[task->w_req_cnt];
				av1dec_update_req(task, class, req, wreq);

				base = task->reg_class[class].base;
				regs = (u32 *)task->reg_class[class].data;
				regs += MPP_BASE_TO_IDX(req->offset - base);
				if (copy_from_user(regs, wreq->data, wreq->size)) {
					mpp_err("copy_from_user fail, offset %08x\n", wreq->offset);
					ret = -EIO;
					goto fail;
				}
				task->w_req_cnt++;
			}
		} break;
		case MPP_CMD_SET_REG_READ: {
			u32 class;
			struct mpp_request *rreq;

			for (class = 0; class < hw->reg_class_num; class++) {
				if (!req_over_class(req, task, class))
					continue;
				mpp_debug(DEBUG_TASK_INFO, "found read_calss %d\n", class);
				rreq = &task->r_reqs[task->r_req_cnt];
				av1dec_update_req(task, class, req, rreq);
				task->r_req_cnt++;
			}
		} break;
		case MPP_CMD_SET_REG_ADDR_OFFSET: {
			mpp_extract_reg_offset_info(&task->off_inf, req);
		} break;
		default:
			break;
		}
	}
	mpp_debug(DEBUG_TASK_INFO, "w_req_cnt=%d, r_req_cnt=%d\n",
		  task->w_req_cnt, task->r_req_cnt);

	mpp_debug_leave();
	return 0;

fail:
	mpp_debug_leave();
	return ret;
}

static void *av1dec_alloc_task(struct mpp_session *session,
			       struct mpp_task_msgs *msgs)
{
	int ret;
	u32 i, j;
	struct mpp_task *mpp_task = NULL;
	struct av1dec_task *task = NULL;
	struct mpp_dev *mpp = session->mpp;

	mpp_debug_enter();

	task = kzalloc(sizeof(*task), GFP_KERNEL);
	if (!task)
		return NULL;

	mpp_task = &task->mpp_task;
	mpp_task_init(session, mpp_task);
	mpp_task->hw_info = mpp->var->hw_info;
	task->hw_info = to_av1dec_info(mpp_task->hw_info);

	/* alloc reg data for task */
	ret = av1dec_alloc_reg_class(task);
	if (ret)
		goto free_task;
	mpp_task->reg = task->reg_class[0].data;
	/* extract reqs for current task */
	ret = av1dec_extract_task_msg(task, msgs);
	if (ret)
		goto free_reg_class;

	/* process fd in register */
	if (!(msgs->flags & MPP_FLAGS_REG_FD_NO_TRANS)) {
		int cnt;
		const u16 *tbl;
		u32 offset;
		struct av1dec_hw_info *hw = task->hw_info;

		for (i = 0; i < task->w_req_cnt; i++) {
			struct mpp_request *req = &task->w_reqs[i];

			for (i = 0; i < hw->trans_class_num; i++) {
				u32 class = hw->trans_class[i].class;
				u32 fmt = hw->trans_class[i].trans_fmt;
				u32 *reg = task->reg_class[class].data;
				u32 base_idx = MPP_BASE_TO_IDX(task->reg_class[class].base);

				if (!req_over_class(req, task, i))
					continue;
				mpp_debug(DEBUG_TASK_INFO, "class=%d, base_idx=%d\n",
					  class, base_idx);
				if (!reg)
					continue;

				ret = mpp_translate_reg_address(session, mpp_task, fmt, reg, NULL);
				if (ret)
					goto fail;

				cnt = mpp->var->trans_info[fmt].count;
				tbl = mpp->var->trans_info[fmt].table;
				for (j = 0; j < cnt; j++) {
					offset = mpp_query_reg_offset_info(&task->off_inf,
									tbl[j] + base_idx);
					mpp_debug(DEBUG_IOMMU,
						"reg[%d] + offset %d\n", tbl[j] + base_idx, offset);
					reg[tbl[j]] += offset;
				}
			}
		}
	}
	task->clk_mode = CLK_MODE_NORMAL;

	mpp_debug_leave();

	return mpp_task;

fail:
	mpp_task_dump_mem_region(mpp, mpp_task);
	mpp_task_dump_reg(mpp, mpp_task);
	mpp_task_finalize(session, mpp_task);
free_reg_class:
	kfree(task->reg_data);
free_task:
	kfree(task);

	return NULL;
}
#define AV1_PP_CONFIG_INDEX	321
#define AV1_PP_TILE_SIZE	GENMASK_ULL(10, 9)
#define AV1_PP_TILE_16X16	BIT(10)

#define AV1_PP_OUT_LUMA_ADR_INDEX	326
#define AV1_PP_OUT_CHROMA_ADR_INDEX	328

#define AV1_L2_CACHE_SHAPER_CTRL	0x20
#define AV1_L2_CACHE_SHAPER_EN		BIT(0)
#define AV1_L2_CACHE_INT_MASK		0x30
#define AV1_L2_CACHE_PP0_Y_CONFIG0	0x84
#define AV1_L2_CACHE_PP0_Y_CONFIG2	0x8c
#define AV1_L2_CACHE_PP0_Y_CONFIG3	0x90
#define AV1_L2_CACHE_PP0_U_CONFIG0	0x98
#define AV1_L2_CACHE_PP0_U_CONFIG2	0xa0
#define AV1_L2_CACHE_PP0_U_CONFIG3	0xa4

#define AV1_L2_CACHE_RD_ONLY_CTRL	0x204
#define AV1_L2_CACHE_RD_ONLY_CONFIG	0x208

static int av1dec_set_l2_cache(struct av1dec_dev *dec, struct av1dec_task *task)
{
	int val;
	u32 *regs = (u32 *)task->reg_class[0].data;
	u32 width = (regs[4] >> 19) * 8;
	u32 height = ((regs[4] >> 6) & 0x1fff) * 8;
	u32 pixel_width = (((regs[322]) >> 27) & 0x1F) == 1 ? 8 : 16;
	u32 pre_fetch_height = 136;
	u32 max_h;
	u32 line_cnt;
	u32 line_size;
	u32 line_stride;

	/* channel 4, PPU0_Y Configuration */
	/* afbc sharper can't use open cache.
	 * afbc out must be tile 16x16.
	 */
	if ((regs[AV1_PP_CONFIG_INDEX] & AV1_PP_TILE_SIZE) != AV1_PP_TILE_16X16) {
		line_size = MPP_ALIGN(MPP_ALIGN(width * pixel_width, 8) / 8, 16);
		line_stride = MPP_ALIGN(MPP_ALIGN(width * pixel_width, 8) / 8, 16) >> 4;
		line_cnt = height;
		max_h = pre_fetch_height;

		writel_relaxed(regs[AV1_PP_OUT_LUMA_ADR_INDEX] + 0x1,
			       dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_Y_CONFIG0);
		val = line_size | (line_stride << 16);
		writel_relaxed(val, dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_Y_CONFIG2);

		val = line_cnt | (max_h << 16);
		writel_relaxed(val, dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_Y_CONFIG3);

		/* channel 5, PPU0_U Configuration */
		line_size = MPP_ALIGN(MPP_ALIGN(width * pixel_width, 8) / 8, 16);
		line_stride = MPP_ALIGN(MPP_ALIGN(width * pixel_width, 8) / 8, 16) >> 4;
		line_cnt = height >> 1;
		max_h = pre_fetch_height >> 1;

		writel_relaxed(regs[AV1_PP_OUT_CHROMA_ADR_INDEX] + 0x1,
			       dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_U_CONFIG0);
		val = line_size | (line_stride << 16);
		writel_relaxed(val, dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_U_CONFIG2);

		val = line_cnt | (max_h << 16);
		writel_relaxed(val, dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_PP0_U_CONFIG3);
		/* mask cache irq */
		writel_relaxed(0xf, dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_INT_MASK);

		/* shaper enable */
		writel_relaxed(AV1_L2_CACHE_SHAPER_EN,
			       dec->reg_base[AV1DEC_CLASS_CACHE] + AV1_L2_CACHE_SHAPER_CTRL);

		/* not enable cache en when multi tiles */
		if (!(regs[10] & BIT(1)))
			/* cache all en */
			writel_relaxed(0x00000001, dec->reg_base[AV1DEC_CLASS_CACHE] +
				AV1_L2_CACHE_RD_ONLY_CONFIG);
		/* reorder_e and cache_e */
		writel_relaxed(0x00000081, dec->reg_base[AV1DEC_CLASS_CACHE] +
			       AV1_L2_CACHE_RD_ONLY_CTRL);
		/* wmb */
		wmb();
	}

	return 0;
}
#define REG_CONTROL		0x20
#define REG_INTRENBL		0x34
#define REG_ACKNOWLEDGE		0x38
#define REG_FORMAT		0x100
#define REG_COMPRESSENABLE	0x340
#define REG_HEADERBASE		0x80
#define REG_PAYLOADBASE		0xC0
#define REG_INPUTBUFBASE	0x180
#define REG_INPUTBUFSTRIDE	0x200
#define REG_INPUTBUFSIZE	0x140

static int av1dec_set_afbc(struct av1dec_dev *dec, struct av1dec_task *task)
{
	u32 *regs = (u32 *)task->reg_class[0].data;
	u32 width = (regs[4] >> 19) * 8;
	u32 height = ((regs[4] >> 6) & 0x1fff) * 8;
	u32 pixel_width_y, pixel_width_c, pixel_width = 8;
	u32 vir_top  =  (((regs[503]) >> 16) & 0xf);
	u32 vir_left  =  (((regs[503]) >> 20) & 0xf);
	u32 vir_bottom = (((regs[503]) >> 24) & 0xf);
	u32 vir_right  =  (((regs[503]) >> 28) & 0xf);
	u32 fbc_format = 0;
	u32 fbc_stream_number = 0;
	u32 fbc_comp_en[2] = {0, 0};
	u32 pp_width_final[2] = {0, 0};
	u32 pp_height_final[2] = {0, 0};
	u32 pp_hdr_base[2] = {0, 0};
	u32 pp_payload_base[2] = {0, 0};
	u32 pp_input_base[2] = {0, 0};
	u32 pp_input_stride[2] = {0, 0};
	u32 bus_address;
	u32 i = 0;

	pixel_width_y = ((regs[8] >> 6) & 0x3) + 8;
	pixel_width_c = ((regs[8] >> 4) & 0x3) + 8;
	pixel_width = (pixel_width_y == 8 && pixel_width_c == 8) ? 8 : 10;

	if ((regs[AV1_PP_CONFIG_INDEX] & AV1_PP_TILE_SIZE) == AV1_PP_TILE_16X16) {
		u32 offset = MPP_ALIGN((vir_left + width + vir_right) *
			     (height + 28) / 16, 64);

		bus_address = regs[505];
		fbc_stream_number++;
		if (pixel_width == 10)
			fbc_format = 3;
		else
			fbc_format = 9;
		fbc_comp_en[0] = 1;
		fbc_comp_en[1] = 1;

		pp_width_final[0] = pp_width_final[1] = vir_left + width + vir_right;
		pp_height_final[0] = pp_height_final[1] = vir_top + height + vir_bottom;

		if (pixel_width == 10)
			pp_input_stride[0] = pp_input_stride[1] = 2 * pp_width_final[0];
		else
			pp_input_stride[0] = pp_input_stride[1] = pp_width_final[0];

		pp_hdr_base[0] = pp_hdr_base[1] = bus_address;
		pp_payload_base[0] = pp_payload_base[1] = bus_address + offset;
		pp_input_base[0] = pp_input_base[1] = bus_address;

		writel_relaxed((fbc_stream_number << 9),
			       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
		writel_relaxed(0x1, dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INTRENBL);

		for (i = 0; i < 2; i++) {
			writel_relaxed(fbc_format,
				       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_FORMAT + i * 4);
			writel_relaxed(fbc_comp_en[i], dec->reg_base[AV1DEC_CLASS_AFBC] +
				       REG_COMPRESSENABLE + i * 4);
			/* hdr base */
			writel_relaxed(pp_hdr_base[i],
				       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_HEADERBASE + i * 4);
			/* payload */
			writel_relaxed(pp_payload_base[i],
				       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_PAYLOADBASE + i * 4);
			/* bufsize */
			writel_relaxed(((pp_height_final[i] << 15) | pp_width_final[i]),
				       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INPUTBUFSIZE + i * 4);
			/* buf */
			writel_relaxed(pp_input_base[i],
				       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_INPUTBUFBASE + i * 4);
			/* stride */
			writel_relaxed(pp_input_stride[i], dec->reg_base[AV1DEC_CLASS_AFBC] +
				       REG_INPUTBUFSTRIDE + i * 4);
		}
		/* wmb */
		wmb();
		writel(((fbc_stream_number << 9) | (1 << 7)),
		       dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL); /* update */
		writel((fbc_stream_number << 9), dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);

	}
	return 0;
}

static int av1dec_run(struct mpp_dev *mpp, struct mpp_task *mpp_task)
{
	int i;
	u32 en_val = 0;
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_hw_info *hw = dec->hw_info;
	struct av1dec_task *task = to_av1dec_task(mpp_task);
	u32 timing_en = mpp->srv->timing_en;

	mpp_debug_enter();
	mpp_iommu_flush_tlb(mpp->iommu_info);
	av1dec_set_l2_cache(dec, task);
	av1dec_set_afbc(dec, task);

	for (i = 0; i < task->w_req_cnt; i++) {
		int class;
		struct mpp_request *req = &task->w_reqs[i];

		for (class = 0; class < hw->reg_class_num; class++) {
			int j, s, e;
			u32 base, *regs;

			if (!req_over_class(req, task, class))
				continue;
			base = task->reg_class[class].base;
			s = MPP_BASE_TO_IDX(req->offset - base);
			e = s + req->size / sizeof(u32);
			regs = (u32 *)task->reg_class[class].data;

			mpp_debug(DEBUG_TASK_INFO, "found rd_class %d, base=%08x, s=%d, e=%d\n",
				  class, base, s, e);
			for (j = s; j < e; j++) {
				if (class == 0 && j == hw->hw.reg_en) {
					en_val = regs[j];
					continue;
				}
				writel_relaxed(regs[j], dec->reg_base[class] + j * sizeof(u32));
			}
		}
	}

	/* init current task */
	mpp->cur_task = mpp_task;

	mpp_task_run_begin(mpp_task, timing_en, MPP_WORK_TIMEOUT_DELAY);

	/* Flush the register before the start the device */
	wmb();
	mpp_write(mpp, hw->en_base, en_val);

	mpp_task_run_end(mpp_task, timing_en);

	mpp_debug_leave();

	return 0;
}

static int av1dec_vcd_irq(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_hw_info *hw = dec->hw_info;

	mpp_debug_enter();

	mpp->irq_status = mpp_read(mpp, hw->sta_base);
	if (!mpp->irq_status)
		return IRQ_NONE;

	mpp_write(mpp, hw->clr_base, 0);

	mpp_debug_leave();

	return IRQ_WAKE_THREAD;
}

static int av1dec_isr(struct mpp_dev *mpp)
{
	struct mpp_task *mpp_task = mpp->cur_task;
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_task *task = to_av1dec_task(mpp_task);
	u32 *regs = (u32 *)task->reg_class[0].data;

	mpp_debug_enter();

	/* FIXME use a spin lock here */
	if (!mpp_task) {
		dev_err(mpp->dev, "no current task\n");
		return IRQ_HANDLED;
	}

	mpp_time_diff(mpp_task, 0);
	mpp->cur_task = NULL;

	/* clear l2 cache status */
	writel_relaxed(0x0, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x020);
	writel_relaxed(0x0, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x204);
	/* multi id enable bit */
	writel_relaxed(0x00000000, dec->reg_base[AV1DEC_CLASS_CACHE] + 0x208);

	if (((regs[321] >> 9) & 0x3) == 0x2) {
		u32 ack_status = readl(dec->reg_base[AV1DEC_CLASS_AFBC] + REG_ACKNOWLEDGE);

		if ((ack_status & 0x1) == 0x1) {
			u32 ctl_val = readl(dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);

			ctl_val |= 1;
			writel_relaxed(ctl_val, dec->reg_base[AV1DEC_CLASS_AFBC] + REG_CONTROL);
		}
	}
	task->irq_status = mpp->irq_status;
	mpp_debug(DEBUG_IRQ_STATUS, "irq_status: %08x\n", task->irq_status);
	if (task->irq_status & dec->hw_info->err_mask) {
		atomic_inc(&mpp->reset_request);
		/* dump register */
		if (mpp_debug_unlikely(DEBUG_DUMP_ERR_REG)) {
			mpp_debug(DEBUG_DUMP_ERR_REG, "irq_status: %08x\n",
				  task->irq_status);
			mpp_task_dump_hw_reg(mpp);
		}
	}
	mpp_task_finish(mpp_task->session, mpp_task);

	mpp_debug_leave();

	return IRQ_HANDLED;
}

static int av1dec_finish(struct mpp_dev *mpp,
			 struct mpp_task *mpp_task)
{
	u32 i;
	struct av1dec_task *task = to_av1dec_task(mpp_task);
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_hw_info *hw = dec->hw_info;

	mpp_debug_enter();

	for (i = 0; i < task->r_req_cnt; i++) {
		int class;
		struct mpp_request *req = &task->r_reqs[i];

		for (class = 0; class < hw->reg_class_num; class++) {
			int j, s, e;
			u32 base, *regs;

			if (!req_over_class(req, task, class))
				continue;
			base = task->reg_class[class].base;
			s = MPP_BASE_TO_IDX(req->offset - base);
			e = s + req->size / sizeof(u32);
			regs = (u32 *)task->reg_class[class].data;

			mpp_debug(DEBUG_TASK_INFO, "found rd_class %d, base=%08x, s=%d, e=%d\n",
				  class, base, s, e);
			for (j = s; j < e; j++) {
				/* revert hack for irq status */
				if (class == 0 && j == MPP_BASE_TO_IDX(hw->sta_base)) {
					regs[j] = task->irq_status;
					continue;
				}
				regs[j] = readl_relaxed(dec->reg_base[class] + j * sizeof(u32));
			}
		}
	}

	mpp_debug_leave();

	return 0;
}

static int av1dec_result(struct mpp_dev *mpp,
			 struct mpp_task *mpp_task,
			 struct mpp_task_msgs *msgs)
{
	u32 i;
	struct av1dec_task *task = to_av1dec_task(mpp_task);
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_hw_info *hw = dec->hw_info;

	mpp_debug_enter();

	for (i = 0; i < task->r_req_cnt; i++) {
		int class;
		struct mpp_request *req = &task->r_reqs[i];

		for (class = 0; class < hw->reg_class_num; class++) {
			u32 base, *regs;

			if (!req_over_class(req, task, class))
				continue;
			base = task->reg_class[class].base;
			regs = (u32 *)task->reg_class[class].data;
			regs += MPP_BASE_TO_IDX(req->offset - base);

			if (copy_to_user(req->data, regs, req->size)) {
				mpp_err("copy_to_user reg fail\n");
				return -EIO;
			}
		}
	}
	mpp_debug_leave();

	return 0;
}

static int av1dec_free_task(struct mpp_session *session,
			    struct mpp_task *mpp_task)
{
	struct av1dec_task *task = to_av1dec_task(mpp_task);

	mpp_task_finalize(session, mpp_task);
	kfree(task->reg_data);
	kfree(task);

	return 0;
}

#ifdef CONFIG_PROC_FS
static int av1dec_procfs_remove(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);

	if (dec->procfs) {
		proc_remove(dec->procfs);
		dec->procfs = NULL;
	}

	return 0;
}

static int av1dec_procfs_init(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);

	dec->procfs = proc_mkdir(mpp->dev->of_node->name, mpp->srv->procfs);
	if (IS_ERR_OR_NULL(dec->procfs)) {
		mpp_err("failed on open procfs\n");
		dec->procfs = NULL;
		return -EIO;
	}

	/* for common mpp_dev options */
	mpp_procfs_create_common(dec->procfs, mpp);

	/* for debug */
	mpp_procfs_create_u32("aclk", 0644,
			      dec->procfs, &dec->aclk_info.debug_rate_hz);
	mpp_procfs_create_u32("session_buffers", 0644,
			      dec->procfs, &mpp->session_max_buffers);

	return 0;
}
#else
static inline int av1dec_procfs_remove(struct mpp_dev *mpp)
{
	return 0;
}

static inline int av1dec_procfs_init(struct mpp_dev *mpp)
{
	return 0;
}
#endif

static int av1dec_init(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	int ret = 0;

	/* Get clock info from dtsi */
	ret = mpp_get_clk_info(mpp, &dec->aclk_info, "aclk_vcodec");
	if (ret)
		mpp_err("failed on clk_get aclk_vcodec\n");
	ret = mpp_get_clk_info(mpp, &dec->hclk_info, "hclk_vcodec");
	if (ret)
		mpp_err("failed on clk_get hclk_vcodec\n");

	/* Get normal max workload from dtsi */
	of_property_read_u32(mpp->dev->of_node,
			     "rockchip,default-max-load",
			     &dec->default_max_load);
	/* Set default rates */
	mpp_set_clk_info_rate_hz(&dec->aclk_info, CLK_MODE_DEFAULT, 300 * MHZ);

	/* Get reset control from dtsi */
	dec->rst_a = mpp_reset_control_get(mpp, RST_TYPE_A, "video_a");
	if (!dec->rst_a)
		mpp_err("No aclk reset resource define\n");
	dec->rst_h = mpp_reset_control_get(mpp, RST_TYPE_H, "video_h");
	if (!dec->rst_h)
		mpp_err("No hclk reset resource define\n");

	return 0;
}

static int av1dec_reset(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);

	mpp_debug_enter();

	if (dec->rst_a && dec->rst_h) {
		rockchip_pmu_idle_request(mpp->dev, true);
		mpp_safe_reset(dec->rst_a);
		mpp_safe_reset(dec->rst_h);
		udelay(5);
		mpp_safe_unreset(dec->rst_a);
		mpp_safe_unreset(dec->rst_h);
		rockchip_pmu_idle_request(mpp->dev, false);
	}

	mpp_debug_leave();

	return 0;
}

static int av1dec_clk_on(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);

	mpp_clk_safe_enable(dec->aclk_info.clk);
	mpp_clk_safe_enable(dec->hclk_info.clk);

	return 0;
}

static int av1dec_clk_off(struct mpp_dev *mpp)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);

	clk_disable_unprepare(dec->aclk_info.clk);
	clk_disable_unprepare(dec->hclk_info.clk);

	return 0;
}

static int av1dec_set_freq(struct mpp_dev *mpp,
			   struct mpp_task *mpp_task)
{
	struct av1dec_dev *dec = to_av1dec_dev(mpp);
	struct av1dec_task *task = to_av1dec_task(mpp_task);

	mpp_clk_set_rate(&dec->aclk_info, task->clk_mode);

	return 0;
}

static struct mpp_hw_ops av1dec_hw_ops = {
	.init = av1dec_init,
	.clk_on = av1dec_clk_on,
	.clk_off = av1dec_clk_off,
	.set_freq = av1dec_set_freq,
	.reset = av1dec_reset,
};

static struct mpp_dev_ops av1dec_dev_ops = {
	.alloc_task = av1dec_alloc_task,
	.run = av1dec_run,
	.irq = av1dec_vcd_irq,
	.isr = av1dec_isr,
	.finish = av1dec_finish,
	.result = av1dec_result,
	.free_task = av1dec_free_task,
};
static const struct mpp_dev_var av1dec_data = {
	.device_type = MPP_DEVICE_AV1DEC,
	.hw_info = &av1dec_hw_info.hw,
	.trans_info = trans_av1dec,
	.hw_ops = &av1dec_hw_ops,
	.dev_ops = &av1dec_dev_ops,
};

static const struct of_device_id mpp_av1dec_dt_match[] = {
	{
		.compatible = "rockchip,av1-decoder",
		.data = &av1dec_data,
	},
	{},
};

static int av1dec_device_match(struct device *dev, struct device_driver *drv)
{
	return 1;
}

static int av1dec_device_probe(struct device *dev)
{
	int ret;
	const struct platform_driver *drv;
	struct platform_device *pdev = to_platform_device(dev);

	ret = of_clk_set_defaults(dev->of_node, false);
	if (ret < 0)
		return ret;

	ret = dev_pm_domain_attach(dev, true);
	if (ret)
		return ret;

	drv = to_platform_driver(dev->driver);
	if (drv->probe) {
		ret = drv->probe(pdev);
		if (ret)
			dev_pm_domain_detach(dev, true);
	}

	return ret;
}

static int av1dec_device_remove(struct device *dev)
{

	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *drv = to_platform_driver(dev->driver);

	if (dev->driver && drv->remove)
		drv->remove(pdev);

	dev_pm_domain_detach(dev, true);

	return 0;
}

static void av1dec_device_shutdown(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct platform_driver *drv = to_platform_driver(dev->driver);

	if (dev->driver && drv->shutdown)
		drv->shutdown(pdev);
}

static int av1dec_dma_configure(struct device *dev)
{
	return of_dma_configure(dev, dev->of_node, true);
}

static const struct dev_pm_ops platform_dev_pm_ops = {
	.runtime_suspend = pm_generic_runtime_suspend,
	.runtime_resume = pm_generic_runtime_resume,
};

struct bus_type av1dec_bus = {
	.name		= "av1dec_bus",
	.match		= av1dec_device_match,
	.probe		= av1dec_device_probe,
	.remove		= av1dec_device_remove,
	.shutdown	= av1dec_device_shutdown,
	.dma_configure  = av1dec_dma_configure,
	.pm		= &platform_dev_pm_ops,
};

static int av1_of_device_add(struct platform_device *ofdev)
{
	WARN_ON(ofdev->dev.of_node == NULL);

	/* name and id have to be set so that the platform bus doesn't get
	 * confused on matching
	 */
	ofdev->name = dev_name(&ofdev->dev);
	ofdev->id = PLATFORM_DEVID_NONE;

	/*
	 * If this device has not binding numa node in devicetree, that is
	 * of_node_to_nid returns NUMA_NO_NODE. device_add will assume that this
	 * device is on the same node as the parent.
	 */
	set_dev_node(&ofdev->dev, of_node_to_nid(ofdev->dev.of_node));

	return device_add(&ofdev->dev);
}

static struct platform_device *av1dec_device_create(void)
{
	int ret = -ENODEV;
	struct device_node *root, *child;
	struct platform_device *pdev;

	root = of_find_node_by_path("/");

	for_each_child_of_node(root, child) {
		if (!of_match_node(mpp_av1dec_dt_match, child))
			continue;

		pr_info("Adding child %pOF\n", child);

		pdev = of_device_alloc(child, "av1d-master", NULL);
		if (!pdev)
			return ERR_PTR(-ENOMEM);

		pdev->dev.bus = &av1dec_bus;

		dma_coerce_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));

		ret = av1_of_device_add(pdev);
		if (ret) {
			platform_device_put(pdev);
			return ERR_PTR(-EINVAL);
		}

		pr_info("register device %s\n", dev_name(&pdev->dev));

		return  pdev;
	}

	return ERR_PTR(ret);
}

static void av1dec_device_destory(void)
{
	struct platform_device *pdev;
	struct device *dev;

	dev = bus_find_device_by_name(&av1dec_bus, NULL, "av1d-master");
	pdev = dev ? to_platform_device(dev) : NULL;
	if (!pdev) {
		pr_err("cannot find platform device\n");
		return;
	}

	pr_info("destroy device %s\n", dev_name(&pdev->dev));
	platform_device_del(pdev);
	platform_device_put(pdev);
}

void av1dec_driver_unregister(struct platform_driver *drv)
{
	/* 1. unregister av1 driver */
	driver_unregister(&drv->driver);
	/* 2. release device */
	av1dec_device_destory();
	/* 3. unregister iommu driver */
	platform_driver_unregister(&rockchip_av1_iommu_driver);
	/* 4. unregister bus */
	bus_unregister(&av1dec_bus);
}

int av1dec_driver_register(struct platform_driver *drv)
{
	int ret;
	/* 1. register bus */
	ret = bus_register(&av1dec_bus);
	if (ret) {
		pr_err("failed to register av1 bus: %d\n", ret);
		return ret;
	}
	/* 2. register iommu driver */
	platform_driver_register(&rockchip_av1_iommu_driver);
	/* 3. create device */
	av1dec_device_create();
	/* 4. register av1 driver */
	return driver_register(&drv->driver);
}

static int av1dec_cache_init(struct platform_device *pdev, struct av1dec_dev *dec)
{
	struct resource *res;
	struct device *dev = &pdev->dev;

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cache");
	if (!res)
		return -ENOMEM;

	dec->reg_base[AV1DEC_CLASS_CACHE] = devm_ioremap(dev, res->start, resource_size(res));
	if (!dec->reg_base[AV1DEC_CLASS_CACHE]) {
		dev_err(dev, "ioremap failed for resource %pR\n", res);
		return -EINVAL;
	}
	return 0;
}

static int av1dec_afbc_init(struct platform_device *pdev, struct av1dec_dev *dec)
{
	struct resource *res;
	struct device *dev = &pdev->dev;

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "afbc");
	if (!res)
		return -ENOMEM;

	dec->reg_base[AV1DEC_CLASS_AFBC] = devm_ioremap(dev, res->start, resource_size(res));
	if (!dec->reg_base[AV1DEC_CLASS_AFBC]) {
		dev_err(dev, "ioremap failed for resource %pR\n", res);
		return -EINVAL;
	}
	dec->irq[AV1DEC_CLASS_AFBC] = platform_get_irq(pdev, 2);

	return 0;
}

static int av1dec_probe(struct platform_device *pdev)
{
	int ret = 0;
	struct device *dev = &pdev->dev;
	struct av1dec_dev *dec = NULL;
	struct mpp_dev *mpp = NULL;
	const struct of_device_id *match = NULL;

	dev_info(dev, "probing start\n");

	dec = devm_kzalloc(dev, sizeof(*dec), GFP_KERNEL);
	if (!dec)
		return -ENOMEM;

	mpp = &dec->mpp;
	platform_set_drvdata(pdev, dec);

	if (pdev->dev.of_node) {
		match = of_match_node(mpp_av1dec_dt_match, pdev->dev.of_node);
		if (match)
			mpp->var = (struct mpp_dev_var *)match->data;
	}
	/* get vcd resource */
	ret = mpp_dev_probe(mpp, pdev);
	if (ret)
		return ret;

	/* iommu may disabled */
	if (mpp->iommu_info)
		mpp->iommu_info->av1d_iommu = 1;

	dec->reg_base[AV1DEC_CLASS_VCD] = mpp->reg_base;
	ret = devm_request_threaded_irq(dev, mpp->irq,
					mpp_dev_irq,
					mpp_dev_isr_sched,
					IRQF_SHARED,
					dev_name(dev), mpp);
	if (ret) {
		dev_err(dev, "register interrupter runtime failed\n");
		goto failed_get_irq;
	}
	dec->irq[AV1DEC_CLASS_VCD] = mpp->irq;
	/* get cache resource */
	ret = av1dec_cache_init(pdev, dec);
	if (ret)
		goto failed_get_irq;
	/* get afbc resource */
	ret = av1dec_afbc_init(pdev, dec);
	if (ret)
		goto failed_get_irq;
	mpp->session_max_buffers = AV1DEC_SESSION_MAX_BUFFERS;
	dec->hw_info = to_av1dec_info(mpp->var->hw_info);
	av1dec_procfs_init(mpp);
	mpp_dev_register_srv(mpp, mpp->srv);
	dev_info(dev, "probing finish\n");

	return 0;

failed_get_irq:
	mpp_dev_remove(mpp);

	return ret;
}

static int av1dec_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct av1dec_dev *dec = platform_get_drvdata(pdev);

	dev_info(dev, "remove device\n");
	mpp_dev_remove(&dec->mpp);
	av1dec_procfs_remove(&dec->mpp);

	return 0;
}

static void av1dec_shutdown(struct platform_device *pdev)
{
	int ret;
	int val;
	struct device *dev = &pdev->dev;
	struct av1dec_dev *dec = platform_get_drvdata(pdev);
	struct mpp_dev *mpp = &dec->mpp;

	dev_info(dev, "shutdown device\n");

	atomic_inc(&mpp->srv->shutdown_request);
	ret = readx_poll_timeout(atomic_read,
				 &mpp->task_count,
				 val, val == 0, 1000, 200000);
	if (ret == -ETIMEDOUT)
		dev_err(dev, "wait total running time out\n");

	dev_info(dev, "shutdown success\n");
}

struct platform_driver rockchip_av1dec_driver = {
	.probe = av1dec_probe,
	.remove = av1dec_remove,
	.shutdown = av1dec_shutdown,
	.driver = {
		.name = AV1DEC_DRIVER_NAME,
		.of_match_table = of_match_ptr(mpp_av1dec_dt_match),
		.bus = &av1dec_bus,
	},
};