// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2021 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <uapi/linux/sched/types.h>

#include <linux/dvb/dmx2_mtk.h>
#include "mtk_dmx_tee_if.h"
#include "mtk_dmx_dev.h"
#include "mtk_dmx_pvr.h"
#include "mtk_dmx_common.h"
#include "mtk_dmx_log.h"
#include "mtk_dmx_riu.h"
#include "mtk_dmx_rm.h"
#include "mtk_dmx_pm.h"
#include "mtk_dmx_io.h"
#include "mtk_dmx_bufctrl.h"
#include "mtk_dmx_dvrdrv.h"
#include "mtk_dmx_m2ts.h"
#include "mtk_dmx_isdb3.h"
#include "mtk_dmx_atsc3.h"
#include "mtk_dmx_tm.h"
#include "mtk_dmx_mediactrl.h"

#define FLUSH_RETRY_CNT 100
#define PVR_TIMEOUT_NULL 0

static enum dvr2_protocol_format get_dvr_protocol(struct mtk_dvr2 *dvr)
{
	int path_id = dvr->path_id;
	u16 ts_num = mtk_dmx_rm_get_hwcap_by_id(dvr->rm, HWCAPS_TYPE_LIVEIN);
	u16 tlv_num = mtk_dmx_rm_get_hwcap_by_id(dvr->rm, HWCAPS_TYPE_TLV);
	u16 alp_num = mtk_dmx_rm_get_hwcap_by_id(dvr->rm, HWCAPS_TYPE_ALP);

	if (path_id < ts_num)
		return DVR2_FORMAT_M2TS;
	else if (path_id < (ts_num + tlv_num))
		return DVR2_FORMAT_ISDB3;
	else if (path_id < (ts_num + tlv_num + alp_num))
		return DVR2_FORMAT_ATSC3;

	return DVR2_FORMAT_NONE;
}

int mtk_dvr2_encrypt_task(void *data)
{
	int ret;
	struct mtk_dvr2_encrypt *p_dvr_encrypt = (struct mtk_dvr2_encrypt *)data;
	struct mtk_dvr2 *dvr = p_dvr_encrypt->p_dvr;
	u64 prev_offset = p_dvr_encrypt->src_prev_offset;
	ssize_t len = p_dvr_encrypt->len;
	struct sched_param param = { 0 };

	if (dvr == NULL) {
		MTK_DMX_ERR("failed to get dvr\n");
		return -EINVAL;
	}

	param.sched_priority = dvr->record_task_info.priority;
	if (sched_setscheduler(
				current,
				dvr->record_task_info.policy,
				&param) != 0) {
		MTK_DMX_ERR(
			"sched_setscheduler(..., %d, %d) is failed.\n",
			dvr->record_task_info.policy,
			param.sched_priority);

		return -EIO;
	}

	ret = call_op(dvr, crypto, encrypt, dvr, prev_offset, len);

	p_dvr_encrypt->execute_done = 1;

	if (ret < 0)
		return -EFAULT;

	return 0;
}

static int dvr_callback(
	const u8 *buf1, size_t len1,
	const u8 *buf2, size_t len2,
	struct dvb_dvr2 *src,
	u32 *buf_flags)
{
	int ret;
	u64 start, wptr, end;
	u64 offset, dst_offset, src_prev_offset, dst_prev_offset, align16_size;
	ssize_t len;
	struct mtk_dvr2 *dvr = cast_to_mtk_dvr(src);
	struct mem_info mem_info = { 0 };
	struct mtk_bc_ctx *bc_ctx =
		dynamic_cast(struct mtk_bc_ctx *, src->buf_ctx[DVR2_BUF_CTX_TYPE_OUT]);
	u8 bResetRingbuf = 0;
	bool b_align16 = false;

	if (src->protocol == DVR2_FORMAT_ISDB3)
		b_align16 = true;

	ret = mtk_bc_get_mem_info(bc_ctx, MEM_INFO_TYPE_NORMAL, &mem_info);
	if (ret < 0) {
		MTK_DMX_ERR("get meminfo fail\n");
		return -EFAULT;
	}

	if (MTK_DMX_VERIFY(mem_info.size == 0)) {
		MTK_DMX_ERR("mem_info.size = 0\n");
		return -EFAULT;
	}
	start = mem_info.paddr;
	end = mem_info.paddr + mem_info.size;
	wptr = *((u64 *)buf1);

	if (wptr < start || wptr >= end) {
		MTK_DMX_ERR("(wptr < start || wptr >= end)\n");
		return -EINVAL;
	}
	MTK_DMX_DEBUG("(s,w,e) = (%llx,%llx,%llx) diff:%d\n", start, wptr, end, dvr->record_task_info.last_wptr_diff);
	dvr->crypto_cfg.start= start;
	dvr->crypto_cfg.end = end;

	/* offset: pvr & aesdma buffer maybe not identical */
	offset = wptr - start;
	if (dvr->record_task_info.src_prev_offset == -1) {
		dvr->record_task_info.src_prev_offset = offset;
		src_prev_offset = 0;
	}
	else
	{
		src_prev_offset = dvr->record_task_info.src_prev_offset;
	}

	dst_prev_offset = mtk_bc_ringbuf_get_wptr(bc_ctx);
	//recover to real last wptr
	dst_prev_offset += dvr->record_task_info.last_wptr_diff;
	dvr->record_task_info.dst_prev_offset = dst_prev_offset;

	len = offset - src_prev_offset;
	if (len < 0)
		len += mem_info.size;

	//consider async mode, we can't use mtk_bc_ringbuf_get_wptr(bc_ctx) to get new dst write pointer
	dst_offset = dst_prev_offset + len;
	if (dst_offset > mem_info.size)
		dst_offset -= mem_info.size;

	MTK_DMX_DEBUG("(offset,src_prev_offset,dst_prev_offset,len) = (%llx,%llx,%llx,%llx)\n", offset, src_prev_offset, dst_prev_offset, (u64)len);

	if (len) {
		mtk_bc_ringbuf_set_water_level(dvr->rm->pvr_reset_threshold);

		if (mtk_dvr2_is_tee_mode(dvr)) {
#if (INDEPEND_ENCRYPT_THREAD)
			const int enc_wait_time1 = INDEPENDENT_ENCRYPT_THREAD_WAIT_TIME1;
			const int enc_wait_time2 = INDEPENDENT_ENCRYPT_THREAD_WAIT_TIME2;
			struct mtk_dvr2_encrypt *dvr_encrypt = NULL;
			struct task_struct *encrypt_task = NULL;

			dvr_encrypt = kzalloc(sizeof(struct mtk_dvr2_encrypt), GFP_KERNEL);
			if (dvr_encrypt == NULL) {
				MTK_DMX_ERR("memory allocate fail\n");
				return -ENOMEM;
			}

			dvr_encrypt->p_dvr = dvr;
			dvr_encrypt->execute_done = 0;
			dvr_encrypt->src_prev_offset = src_prev_offset;
			dvr_encrypt->len = len;

			encrypt_task = kthread_run(mtk_dvr2_encrypt_task,
											   (void *)(dvr_encrypt),
											   "encrypt_task%d",
											   dvr->path_id);
			if (IS_ERR(encrypt_task)) {
				MTK_DMX_ERR("create encrypt task fail\n");
				encrypt_task = NULL;
				kfree(dvr_encrypt);
				return -ENODEV;
			}

			//MTK_DMX_DEBUG("[T] start encrypt task(%d)\n", dvr->path_id);

			dvr->record_task_info.wait_times[0] = DEFAULT_RECORD_TASK_WAIT_TIME1;
			dvr->record_task_info.wait_times[1] = DEFAULT_RECORD_TASK_WAIT_TIME2;

			while(1) {
				if (dvr_encrypt->execute_done == 1) {
					kfree(dvr_encrypt);
					break;
				}
				ret = mtk_bc_ringbuf_check_water_level(bc_ctx);
				if (ret < 0) {
					//free buffer size < 0.5MB (40ms duration with 8K content)
					//pause record engine and reset r/w pointers
					_pause_hw_param_begin(src->index);
					bResetRingbuf = 1;
					MTK_DMX_ERR("PVR flush and reset ringbuf\n");
				}


				dvr->record_task_info.wait_times[0] =
				(dvr->record_task_info.wait_times[0] > enc_wait_time2) ?
				(dvr->record_task_info.wait_times[0] - enc_wait_time2) : 0;

				dvr->record_task_info.wait_times[1] =
				(dvr->record_task_info.wait_times[1] > enc_wait_time2) ?
				(dvr->record_task_info.wait_times[1] - enc_wait_time2) : 0;

				usleep_range(enc_wait_time1, enc_wait_time2);
			}

			if (bResetRingbuf == 1) {
				mtk_bc_ringbuf_reset(bc_ctx, false);
				dvr->record_task_info.last_wptr_diff = 0;
				dvr->record_task_info.src_prev_offset = -1;
				dvr->record_task_info.dst_prev_offset = -1;
				_pause_hw_param_end(src->index);
			}
#else
			int dvb_ringbuf_overrun = mtk_bc_ringbuf_check_water_level(bc_ctx);

			if (dvb_ringbuf_overrun < 0) {
				MTK_DMX_ERR("dvb buffer overrun. free:%ld,threshold:%u,size:%u\n",
					(long)dvb_ringbuffer_free(&bc_ctx->d_ctx.buf.ring),
					dvr->rm->pvr_reset_threshold,
					mem_info.size);
			}

			// if dvb ring buffer or rec buffer is going to overflow
			if (dvb_ringbuf_overrun < 0	||
				dvr->record_task_info.is_recbuffer_overflow) {
				//free buffer size < 0.5MB (40ms duration with 8K content)
				//pause record engine and reset r/w pointers
				_pause_hw_param_begin(src->index);
				mtk_bc_ringbuf_reset(bc_ctx, false);
				bResetRingbuf = 1;
				_pause_hw_param_end(src->index);
				offset = -1;
				dvr->record_task_info.last_wptr_diff = 0;
				dvr->record_task_info.src_prev_offset = -1;
				dvr->record_task_info.dst_prev_offset = -1;
				dvr->record_task_info.is_recbuffer_overflow = false;
				MTK_DMX_ERR("PVR flush and reset ringbuf\n");
			} else {
				ktime_t start_time, end_time;
				s64 elapsed_time; //us

				start_time = ktime_get();
				dvr->record_task_info.wait_times[0] =
					DEFAULT_RECORD_TASK_WAIT_TIME1;
				dvr->record_task_info.wait_times[1] =
					DEFAULT_RECORD_TASK_WAIT_TIME2;

				// execute encrypt and framing
				ret = call_op(dvr, crypto, encrypt, dvr, src_prev_offset, len);

				end_time = ktime_get();
				elapsed_time = ktime_to_us(ktime_sub(end_time, start_time));
				MTK_DMX_DEBUG("Execution time: %lld us, MBps: %2lld\n"
					, (elapsed_time), len/((elapsed_time)));

				// to make thread's sleepTime+execTime is fixed(e.g.80ms)
				// we have to reduce sleep time base on the real exectime
				dvr->record_task_info.wait_times[0] =
				(dvr->record_task_info.wait_times[0] > ((elapsed_time))) ?
				(dvr->record_task_info.wait_times[0] - (elapsed_time)) : 0;
				dvr->record_task_info.wait_times[1] =
				(dvr->record_task_info.wait_times[1] > (elapsed_time)) ?
				(dvr->record_task_info.wait_times[1] - (elapsed_time)) : 0;

				if (ret < 0)
					return -EFAULT;
			}
#endif
		}
		else
		{
			//non-tee mode case
			if (dvr->rec_cfg.free_run == false) {
				ret = mtk_bc_ringbuf_check_water_level(bc_ctx);
				if (ret < 0) {
					//free buffer size < 0.5MB (40ms duration with 8K content)
					//pause record engine and reset r/w pointers
					_pause_hw_param_begin(src->index);
					mtk_bc_ringbuf_reset(bc_ctx, false);
					bResetRingbuf = 1;
					dvr->record_task_info.last_wptr_diff = 0;
					dvr->record_task_info.src_prev_offset = -1;
					dvr->record_task_info.dst_prev_offset = -1;
					_pause_hw_param_end(src->index);
					MTK_DMX_ERR("PVR flush and reset ringbuf\n");
				}
			}
		}

		if (bResetRingbuf == 0 &&
			dvr->rec_cfg.free_run == false) {
			if (b_align16) {
				align16_size = div_u64(dst_offset, PVR_MIU_ALIGN) * PVR_MIU_ALIGN;
				MTK_DMX_DEBUG("align16_size=%llx\n", align16_size);
			} else {
				align16_size = offset;
			}

			ret = mtk_bc_ringbuf_set_wptr(bc_ctx, align16_size);
			if (ret < 0) {
				//buffer overflow happened
				//reset r/w pointers
				_pause_hw_param_begin(src->index);
				mtk_bc_ringbuf_reset(bc_ctx, false);
				dvr->record_task_info.last_wptr_diff = 0;
				dvr->record_task_info.src_prev_offset = -1;
				dvr->record_task_info.dst_prev_offset = -1;
				_pause_hw_param_end(src->index);
				return -EFAULT;
			}
			if (b_align16) {
				dvr->record_task_info.last_wptr_diff = dst_offset - align16_size;
			} else {
				dvr->record_task_info.last_wptr_diff = offset - align16_size;
			}
			MTK_DMX_DEBUG("diff:%d\n", dvr->record_task_info.last_wptr_diff);
			MTK_DMX_DEBUG("(offset,prev_offset,len) = (%llx,%llx,%llx)\n", offset, src_prev_offset, (u64)len);
		}
	}

	dvr->record_task_info.src_prev_offset = offset;

	return 0;
}

static inline void _init_dvr_crypto(struct mtk_dvr2 *dvr)
{
	struct dvr_crypto_config *cfg = &dvr->crypto_cfg;

	cfg->pid_num = 0;
	INIT_LIST_HEAD(&cfg->list_head);
	spin_lock_init(&cfg->lock);
}

static void _pre_rec_init(struct mtk_dvr2 *dvr)
{
	struct record_task_info default_record_task_info = {
		.policy = DEFAULT_RECORD_TASK_POLICY,
		.priority = DEFAULT_RECORD_TASK_PRIORITY,
		.wait_times = {
			DEFAULT_RECORD_TASK_WAIT_TIME1,
			DEFAULT_RECORD_TASK_WAIT_TIME2,
		},
		.finalize_callback = DEFAULT_RECORD_TASK_FINALIZE_CALLBACK,
		.finalize_timeout = DEFAULT_RECORD_TASK_FINALIZE_TIMEOUT,
		.pause_callback = DEFAULT_RECORD_TASK_FINALIZE_CALLBACK,
		.pause_timeout = DEFAULT_RECORD_TASK_FINALIZE_TIMEOUT,
		.last_wptr_diff = 0,
		.src_prev_offset = -1,
		.dst_prev_offset = -1,
		.pause_state = DVR_PAUSE_STATE_INIT,
	};

	if (MTK_DMX_VERIFY(dvr == NULL))
		return;

	memcpy(&dvr->record_task_info,
		&default_record_task_info,
		sizeof(default_record_task_info));
}

static void _pre_filein_init(struct mtk_dvr2 *dvr)
{
	struct filein_task_info default_filein_task_info = {
		.policy = DEFAULT_FILEIN_TASK_POLICY,
		.priority = DEFAULT_FILEIN_TASK_PRIORITY,
		.wait_times = {
			DEFAULT_FILEIN_TASK_WAIT_TIME1,
			DEFAULT_FILEIN_TASK_WAIT_TIME2,
		},
		.filein_state = DVR_FILEIN_TASK_NORMAL_STATE,
		.write_offset = INVALID_FILEIN_WRITE_OFFSET,
		.last_idle = 0,
	};

	if (MTK_DMX_VERIFY(dvr == NULL))
		return;

	memcpy(&dvr->filein_task_info,
		&default_filein_task_info,
		sizeof(default_filein_task_info));
}

static inline void _init_dvr_filein_cfg(struct mtk_dvr2 *dvr)
{
	struct dvr_filein_config *filein_cfg = &dvr->filein_cfg;
	memset(filein_cfg, 0, sizeof(struct dvr_filein_config));
}

static int _init(struct mtk_dvr2 *dvr)
{
	int ret;
	enum dvr2_protocol_format protocol = get_dvr_protocol(dvr);
	_pre_rec_init(dvr);
	_pre_filein_init(dvr);
	_init_dvr_filein_cfg(dvr);

	switch (protocol) {
	case DVR2_FORMAT_M2TS:
		ret = mtk_dvr_m2ts_init(dvr);
		break;
	case DVR2_FORMAT_ISDB3:
		ret = mtk_dvr_isdb3_init(dvr);
		break;
	case DVR2_FORMAT_ATSC3:
		ret = mtk_dvr_atsc3_init(dvr);
		break;
	default:
		return -ENOTTY;
	}

	if (ret < 0) {
		MTK_DMX_ERR("init dvr fail (protocol = %x)\n", protocol);
		return -EFAULT;
	}

	cast_to_dvr_parent(dvr)->callback = dvr_callback;
	cast_to_dvr_parent(dvr)->index = PVR_INVALID;

	_init_dvr_crypto(dvr);
	return 0;
}

static int _deinit(struct mtk_dvr2 *dvr)
{
	int ret;
	enum dvr2_protocol_format protocol = get_dvr_protocol(dvr);

	switch (protocol) {
	case DVR2_FORMAT_M2TS:
		ret = mtk_dvr_m2ts_exit(dvr);
		break;
	case DVR2_FORMAT_ISDB3:
		ret = mtk_dvr_isdb3_exit(dvr);
		break;
	case DVR2_FORMAT_ATSC3:
		ret = mtk_dvr_atsc3_exit(dvr);
		break;
	default:
		return -ENOTTY;
	}

	if (ret < 0) {
		MTK_DMX_ERR("de-init dvr fail (protocol = %x)\n", protocol);
		return -EFAULT;
	}

	return 0;
}

static int _set_hw_param(s32 eng_id, s32 path_id, struct mem_info *pvr_buffer)
{
	u64 start_addr0, end_addr0;
	u64 start_addr1, end_addr1;
	int ret = MTK_DMX_FAIL;

	start_addr0 = pvr_buffer->paddr;
	end_addr0 = pvr_buffer->paddr + (pvr_buffer->size >> 1);
	start_addr1 = end_addr0;
	end_addr1 = start_addr1 + (pvr_buffer->size >> 1);

	ret = mtk_pvr_set_buffer(eng_id, false, start_addr0, end_addr0);
	if (ret != MTK_DMX_SUCCESS)
		return MTK_DMX_FAIL;

	ret = mtk_pvr_set_buffer(eng_id, true, start_addr1, end_addr1);
	if (ret != MTK_DMX_SUCCESS)
		return MTK_DMX_FAIL;

	mtk_pvr_pingpong_en(eng_id, true);
	mtk_pvr_set_src(eng_id, path_id);
	mtk_pvr_bypass_hdr(eng_id, true);
	mtk_pvr_reset_wptr(eng_id);
	mtk_pvr_enable(eng_id, true);

	return 0;
}

static int _reset_hw_param(s32 eng_id)
{
	mtk_pvr_enable(eng_id, false);
	mtk_pvr_set_src(eng_id, DEMUX_PATH_NULL);
	mtk_pvr_bypass_hdr(eng_id, false);
	mtk_pvr_pingpong_en(eng_id, false);

	return 0;
}

int _pause_hw_param_begin(s32 eng_id)
{
	mtk_pvr_enable(eng_id, false);
	mtk_pvr_flush_data(eng_id);

	return 0;
}

int _pause_hw_param_end(s32 eng_id)
{
	mtk_pvr_reset_wptr(eng_id);
	mtk_pvr_enable(eng_id, true);

	return 0;
}

static int _dvr_in_set_file_in_cfg(struct mtk_dvr2 *dvr, struct dvr2_playback_settings *pCfg)
{
	enum demux_in_format format;
	u16 path_id = dvr->path_id;
	bool timestamp_en = false;
	int eng;

	switch (pCfg->format) {
	case DVR2_OUT_TTS:
		format = DEMUX_IN_FORMAT_M2TS_TIMESTAMP;
		timestamp_en = true;
		break;
	case DVR2_OUT_TS:
		format = DEMUX_IN_FORMAT_M2TS;
		break;
	case DVR2_OUT_TLV:
		format = DEMUX_IN_FORMAT_TLV;
		break;
	case DVR2_OUT_ALP:
		format = DEMUX_IN_FORMAT_ALP;
		break;
	default:
		MTK_DMX_ERR("play type (%d) not support\n", pCfg->format);
		return -EINVAL;
	}

	eng = mtk_dmx_rm_filein_find_engine_by_path(dvr->rm, path_id);
	if (eng < 0)
		return -ENODEV;
	mtk_dmx_io_set_filein_cfg(eng, format, timestamp_en);
	dvr->filein_cfg.timestamp_en = timestamp_en;

	return 0;
}

// Must set record cfg before dvr start
static int _dvr_in_set_record_cfg(struct mtk_dvr2 *dvr, struct dvr2_record_settings *pCfg)
{
	enum dvr2_protocol_format protocol = get_dvr_protocol(dvr);

	dvr->rec_cfg.record_all = pCfg->rec_all_input;

	if ((protocol == DVR2_FORMAT_M2TS) && (pCfg->format == DVR2_OUT_TTS))
		dvr->rec_cfg.pre_hdr_type = DVR_INSERT_TIMESTAMP;
	else
		dvr->rec_cfg.pre_hdr_type = DVR_INSERT_NONE;

	return 0;
}

int mtk_dvr2_set_ext_ctrl(struct mtk_dvr2 *dvr, u32 ctrl_id, void *arg)
{
#define TIMEOUT_CNT	5000
#define PAUSE_STATE_CHECK_INTERVAL_1	100
#define PAUSE_STATE_CHECK_INTERVAL_2	110

	int ret = 0;
	int timeout = 0;

	if (dvr == NULL || arg == NULL) {
		MTK_DMX_ERR("input source param invalid\n");
		return -EINVAL;
	}

	switch (ctrl_id) {
	case DMX2_CID_SET_SOURCE:
		if (!(dvr->capabilities & DMX2_CAP_OUT_MEMORY) &&
			(((struct dmx2_source_settings *)arg)->input_type == DMX2_INPUT_TYPE_DRAM)) {
			MTK_DMX_ERR("path id (%d) not support file-in\n", dvr->path_id);
			return -EINVAL;
		} else
			ret = mtk_mc_dvr2_ext_set_input_source(dvr->rm,
				dvr->path_id, (struct dmx2_source_settings *)arg, NULL);
		break;
	case DMX2_CID_PLAYBACK_SETTING:
		ret = _dvr_in_set_file_in_cfg(dvr, (struct dvr2_playback_settings *)arg);
		break;
	case DMX2_CID_RECORD_SETTINGS:
		ret = _dvr_in_set_record_cfg(dvr, (struct dvr2_record_settings *)arg);
		break;
	case DMX2_CID_RECORD_PAUSE:
		if (*(u32 *)arg) {
			//pvr pause
			MTK_DMX_INFO("PVR record pause\n");
			//change state to pause
			timeout = 0;
			if (dvr->record_task_info.pause_state == DVR_PAUSE_STATE_INIT) {
				dvr->record_task_info.pause_state = DVR_PAUSE_STATE_GO;

				mtk_pvr_enable(cast_to_dvr_parent(dvr)->index, false);

				while (dvr->record_task_info.pause_state == DVR_PAUSE_STATE_GO) {
					//check timeout count
					usleep_range(PAUSE_STATE_CHECK_INTERVAL_1,
								 PAUSE_STATE_CHECK_INTERVAL_2);
					timeout++;
					if (timeout > TIMEOUT_CNT) {
						MTK_DMX_ERR("Wait PVR encryt thread timeout!\n");
						MTK_DMX_ERR("record pause state:%d\n",
							dvr->record_task_info.pause_state);
						break;
					}
				}

				MTK_DMX_INFO("PVR record pause callback done. pause_state:%d\n",
							dvr->record_task_info.pause_state);
			} else {
				MTK_DMX_INFO("Duplicated record pause\n");
				return -EINVAL;
			}
		} else {
			//pvr resume
			struct mtk_bc_ctx *bc_ctx =
				dynamic_cast(
					struct mtk_bc_ctx *,
					cast_to_dvr_parent(dvr)->buf_ctx[DVR2_BUF_CTX_TYPE_OUT]);
			if (bc_ctx == NULL) {
				MTK_DMX_ERR("input param invalid\n");
				return -EINVAL;
			}

			MTK_DMX_INFO("PVR record resume\n");
			if (dvr->record_task_info.pause_state == DVR_PAUSE_STATE_IDLE) {
				MTK_DMX_INFO("reset PVR write ptr\n");
				mtk_pvr_reset_wptr(cast_to_dvr_parent(dvr)->index);
				dvr->record_task_info.last_wptr_diff = 0;
				dvr->record_task_info.src_prev_offset = -1;
				dvr->record_task_info.dst_prev_offset = -1;
				MTK_DMX_INFO("reset meta data\n");
				mtk_bc_metabuf_reset(bc_ctx);
				mtk_bc_ringbuf_reset(bc_ctx, false);
				mtk_pvr_enable(cast_to_dvr_parent(dvr)->index, true);
				dvr->record_task_info.pause_state = DVR_PAUSE_STATE_INIT;
				MTK_DMX_INFO("PVR record resume done. pause_state:%d\n",
							dvr->record_task_info.pause_state);
			} else {
				MTK_DMX_INFO("Duplicated record resume\n");
				return -EINVAL;
			}
		}
		break;
	default:
		MTK_DMX_ERR("ctrl id (%x) not support\n", ctrl_id);
		return -EINVAL;
	}
	return ret;

#undef TIMEOUT_CNT
#undef PAUSE_STATE_CHECK_INTERVAL_1
#undef PAUSE_STATE_CHECK_INTERVAL_2
}

int mtk_dvr2_get_ext_ctrl(struct dvb_fh *fh, u32 ctrl_id, void *arg)
{
	int ret = 0;
	if (fh == NULL || arg == NULL) {
		MTK_DMX_ERR("input source param invalid\n");
		return -EINVAL;
	}

	switch (ctrl_id) {
	case DMX2_CID_GET_METADATA:
		{
			struct mtk_dvr2 *dvr = fh_to_dvr(fh);
			struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);
			struct mtk_bc_ctx *bc_ctx = fh_dmxctx_get_bc_ctx(ctx);

			// @NOTE: release mutex if adopt blocking mode
			if (mtk_bc_metabuf_dequeue(bc_ctx, arg, true) < 0) {
				MTK_DMX_DEBUG("dequeue meta fail\n");
				ret = -EFAULT;
			}
		}
		break;
	case DMX2_CID_GET_REMAINING_SIZE:
		{
			struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
			struct mtk_bc_ctx *buf_ctx = (ctx == NULL) ? NULL : fh_dvrctx_get_bc_ctx(ctx);
			u32 *buf_available_size = static_cast(u32 *, arg);

			if (buf_ctx == NULL || ctx == NULL) {
				MTK_DMX_ERR("get mtk_bc_ctx(%p) or mtk_dvr2_fh_ctx fail(%p)\n", buf_ctx, ctx);
				return -EINVAL;
			}

			*buf_available_size = mtk_bc_ringbuf_available_to_read(buf_ctx) +
						mtk_bc_secure_ringbuf_available_to_read(buf_ctx);
		}
		break;
	default:
		MTK_DMX_ERR("ctrl id (%x) not support\n", ctrl_id);
		return -EINVAL;
	}

	return ret;
}

int mtk_dvr2_open(struct mtk_dvr2 *dvr, struct dvb_fh *fh)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	struct mtk_bc_ctx *buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);

	if (cast_to_dvr_parent(dvr)->fh != NULL)
		MTK_DMX_ERR("Dvr is opened by another direction. Current open dir: %s\n"
			, IS_OUTPUT_DEVICE(dir) ? "output" : "input");

	if (IS_OUTPUT_DEVICE(dir)) {
		if (cast_to_dvr_parent(dvr)->index == PVR_INVALID) {
			cast_to_dvr_parent(dvr)->index =
				mtk_dmx_rm_pvr_alloc_engine(dvr->rm, dvr->path_id);
			if (cast_to_dvr_parent(dvr)->index == -1) {
				MTK_DMX_ERR("allocate pvr engine fail\n");
				cast_to_dvr_parent(dvr)->index = PVR_INVALID;
				return -EFAULT;
			}
		}
		MTK_DMX_INFO("pvr engine %d is allocated to path %d\n",
			cast_to_dvr_parent(dvr)->index,
			dvr->path_id);
	}

	ret = dvb_dvr2_open(cast_to_dvr_parent(dvr), fh);
	if (ret < 0) {
		MTK_DMX_ERR("open dvr fail\n");
		return -EFAULT;
	}

	if (IS_OUTPUT_DEVICE(dir)) {
		ret = dvb_dvr2_set_buffer(
			cast_to_dvr_parent(dvr),
			DVR2_BUF_CTX_TYPE_OUT,
			&buf_ctx->d_ctx.buf);
		if (ret < 0) {
			MTK_DMX_ERR("set dvr output buffer fail\n");
			return -EFAULT;
		}
	} else if (IS_INPUT_DEVICE(dir)) {
		ret = dvb_dvr2_set_buffer(
			cast_to_dvr_parent(dvr),
			DVR2_BUF_CTX_TYPE_IN,
			&buf_ctx->d_ctx.buf);
		if (ret < 0) {
			MTK_DMX_ERR("set dvr input buffer fail\n");
			return -EFAULT;
		}
	}

	return 0;
}

static int set_ringbuf_rptr(struct mtk_bc_ctx *ctx, u64 rptr)
{
	int ret;
	struct mtk_dvr2_fh_ctx *fh_ctx = container_of(ctx, struct mtk_dvr2_fh_ctx, bc_ctx);
	struct mtk_dvr2 *dvr = fh_dvrctx_get_dvr(fh_ctx);

	ret = call_op(dvr, buf, set_rptr, dvr, rptr);

	return ret;
}

int mtk_dvr2_start(struct mtk_dvr2 *dvr)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);
	struct mtk_bc_ctx *buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	struct mem_info mem_info = { 0 };
	struct dvb_dvr2 *dvb_dvr2 = NULL;
	struct mtk_atsc3_dvr_private *mtk_atsc3_dvr_private = NULL;
	struct mtk_demux2 *mtk_demux2 = NULL;

	mtk_demux2 = fh_to_demux(cast_to_dvr_parent(dvr)->fh);
	if (mtk_demux2->base.protocol == DMX2_FORMAT_ATSC3) {
		dvb_dvr2 = cast_to_dvr_parent(dvr);
		mtk_atsc3_dvr_private = (struct mtk_atsc3_dvr_private *)dvb_dvr2->priv;
		if (mtk_atsc3_dvr_private->is_austin) {
		buf_ctx = &(mtk_atsc3_dvr_private->bc_ctx_austin);
		}
	}
	ret = mtk_bc_get_mem_info(buf_ctx, MEM_INFO_TYPE_NORMAL, &mem_info);
	if (ret < 0) {
		MTK_DMX_ERR("get meminfo fail\n");
		return -EFAULT;
	}
	if (MTK_DMX_VERIFY(mem_info.size == 0))
		return -EFAULT;

	if (mtk_dvr2_is_need_ringbuf(cast_to_dvr_parent(dvr)->fh)) {
		ret = mtk_bc_ringbuf_set_rptr_cb(buf_ctx, &set_ringbuf_rptr);
		if (ret < 0) {
			MTK_DMX_ERR("set rptr cb fail\n");
			return -EFAULT;
		}
	} else if (mtk_bc_vb2_is_ringbuf_mode(buf_ctx)) {
		ret = mtk_bc_vb2_set_rptr_cb(buf_ctx, &set_ringbuf_rptr);
		if (ret < 0) {
			MTK_DMX_ERR("set rptr cb fail\n");
			return -EFAULT;
		}
	}

	//Setup record type and mode
	ret = dvb_dvr2_start(cast_to_dvr_parent(dvr));
	if (ret < 0) {
		MTK_DMX_ERR("start dvr fail\n");
		mtk_dmx_rm_pvr_free_engine(dvr->rm, dvr->path_id);
		return -EFAULT;
	}

	//Setup common pvr HW param and buffer
	ret = _set_hw_param(cast_to_dvr_parent(dvr)->index,
		dvr->path_id,
		&mem_info);
	if (ret < 0) {
		MTK_DMX_ERR("_set_hw_param fail\n");
		ret = dvb_dvr2_stop(cast_to_dvr_parent(dvr));
		if (ret < 0)
			MTK_DMX_ERR("stop dvr fail\n");

		mtk_dmx_rm_pvr_free_engine(dvr->rm, dvr->path_id);
		return -EFAULT;
	}

	if (!dvr->record_task) {
		dvr->record_task = kthread_run(mtk_dmx_tm_pvr_rec_task,
			(void *)dvr,
			"dd_rec_task%d",
			dvr->path_id);

		if (IS_ERR(dvr->record_task)) {
			MTK_DMX_ERR("create record task fail\n");
			dvr->record_task = NULL;
			return -ENODEV;
		}
		set_user_nice(dvr->record_task, DEFAULT_RECORD_TASK_NICE);
		dvr->rec_monitor_task = NULL;
		if (mtk_demux2->base.protocol != DMX2_FORMAT_ATSC3) {
			dvr->rec_monitor_task = kthread_run(mtk_dmx_tm_pvr_rec_monitor,
				(void *)dvr,
				"dd_rec_mon%d",
				dvr->path_id);
			if (IS_ERR(dvr->rec_monitor_task)) {
				MTK_DMX_ERR("create record monitor task fail\n");
				dvr->rec_monitor_task = NULL;
				return -ENODEV;
			}
			set_user_nice(dvr->rec_monitor_task, DEFAULT_RECORD_TASK_NICE);

			MTK_DMX_DEBUG("start record task(%d)\n", dvr->path_id);
		}
	}

	return 0;
}

int mtk_dvr2_stop(struct mtk_dvr2 *dvr)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = NULL;
	struct mtk_bc_ctx *buf_ctx = NULL;
	struct dvb_dvr2 *dvb_dvr2 = NULL;
	struct mtk_atsc3_dvr_private *mtk_atsc3_dvr_private = NULL;
	struct mtk_demux2 *mtk_demux2 = NULL;

	if (dvr == NULL) {
		MTK_DMX_ERR("dvr is NULL\n");
		return -EINVAL;
	}
	ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);
	if (ctx == NULL) {
		MTK_DMX_ERR("ctx is NULL\n");
		return -EINVAL;
	}
	buf_ctx = fh_dvrctx_get_bc_ctx(ctx);

	if (buf_ctx == NULL) {
		MTK_DMX_ERR("mtk_demux2 is NULL\n");
		return -EINVAL;
	}
	dvb_dvr2 = cast_to_dvr_parent(dvr);
	if (dvb_dvr2 == NULL) {
		MTK_DMX_ERR("dvb_dvr2 is NULL\n");
		return -EINVAL;
	}

	mtk_demux2 = fh_to_demux(cast_to_dvr_parent(dvr)->fh);
	if (mtk_demux2 == NULL) {
		MTK_DMX_ERR("mtk_demux2 is NULL\n");
		return -EINVAL;
	}
	if (mtk_demux2->base.protocol == DMX2_FORMAT_ATSC3) {
		mtk_atsc3_dvr_private = (struct mtk_atsc3_dvr_private *)dvb_dvr2->priv;
		if (mtk_atsc3_dvr_private == NULL) {
			MTK_DMX_ERR("mtk_atsc3_dvr_private is NULL\n");
			return -EINVAL;
		}
		if (mtk_atsc3_dvr_private->is_austin) {
			buf_ctx = &(mtk_atsc3_dvr_private->bc_ctx_austin);
		}
	}

	mtk_pvr_enable(cast_to_dvr_parent(dvr)->index, false);

	if (dvr->record_task) {
		MTK_DMX_DEBUG("stop record task(%d)\n", dvr->path_id);
		if (dvb_dvr2->buf_ctx[DVR2_BUF_CTX_TYPE_IN] == NULL) {
			dvr->record_task_info.wait_times[0] = PVR_TIMEOUT_NULL;
			dvr->record_task_info.wait_times[1] = PVR_TIMEOUT_NULL;
			dvr->record_task_info.finalize_callback = NULL;
		}
		kthread_stop(dvr->record_task);
		dvr->record_task = NULL;
	}

	if (dvr->rec_monitor_task) {
		MTK_DMX_DEBUG("stop record monitor task(%d)\n", dvr->path_id);
		kthread_stop(dvr->rec_monitor_task);
		dvr->rec_monitor_task = NULL;
	}

	_reset_hw_param(cast_to_dvr_parent(dvr)->index);

	ret = dvb_dvr2_stop(cast_to_dvr_parent(dvr));
	if (ret < 0) {
		MTK_DMX_ERR("stop dvr fail\n");
		return -EFAULT;
	}

	memset(&dvr->rec_cfg, 0, sizeof(struct dvr_rec_config));
	_pre_rec_init(dvr);

	ret = mtk_bc_ringbuf_set_rptr_cb(buf_ctx, NULL);
	if (ret < 0) {
		MTK_DMX_ERR("set rptr cb fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_dvr2_flush(struct mtk_dvr2 *dvr)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = NULL;

	if (dvr == NULL) {
		MTK_DMX_ERR("dvr is NULL\n");
		return -EFAULT;
	}
	ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);
	if (ctx == NULL) {
		MTK_DMX_ERR("mtk_dvr2_fh_ctx is NULL\n");
		return -EFAULT;
	}

	if (IS_INPUT_DEVICE(ctx->dir) && dvr->feed_task != NULL) {
		int retry_cnt = FLUSH_RETRY_CNT;
		// change state, tell mtk_dmx_tm_pvr_feed_task to handle the flush
		MTK_DMX_DEBUG("set flush state\n");
		dvr->filein_task_info.filein_state = DVR_FILEIN_TASK_FLUSH_STATE;
		// wait until flush done (leave flush state)
		while (dvr->filein_task_info.filein_state == DVR_FILEIN_TASK_FLUSH_STATE && retry_cnt > 0) {
			MTK_DMX_DEBUG("wait flush done\n");
			usleep_range(
				dvr->filein_task_info.wait_times[0],
				dvr->filein_task_info.wait_times[1]);
			retry_cnt--;
		}

		if (dvr->filein_task_info.filein_state == DVR_FILEIN_TASK_FLUSH_STATE) {
			MTK_DMX_ERR("filein flush fail\n");
			return -EFAULT;
		}
	}
	else {
		ret = dvb_dvr2_flush(cast_to_dvr_parent(dvr));
		if (ret < 0) {
			MTK_DMX_ERR("close dvr fail\n");
			return -EFAULT;
		}
	}

	return 0;
}

static int mtk_dvr2_clear_ca_pid(struct mtk_dvr2 *dvr)
{
	struct dvr_ca_pid *ca_pid, *q;
	struct dvr_crypto_config *cfg = &dvr->crypto_cfg;

	spin_lock(&cfg->lock);

	if (cfg->pid_num > 0) {
		MTK_DMX_INFO("Clear dvr ca pid, num: %d\n", cfg->pid_num);
		list_for_each_entry_safe(ca_pid, q, &cfg->list_head, list) {
			list_del(&ca_pid->list);
			kfree(ca_pid);
		}
	}
	cfg->pid_num = 0;

	spin_unlock(&cfg->lock);

	return 0;
}

int mtk_dvr2_close(struct mtk_dvr2 *dvr, struct dvb_fh *fh)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);

	if (IS_OUTPUT_DEVICE(dir)) {
		mtk_dvr2_clear_ca_pid(dvr);

		ret = dvb_dvr2_set_buffer(
			cast_to_dvr_parent(dvr),
			DVR2_BUF_CTX_TYPE_OUT,
			NULL);
		if (ret < 0) {
			MTK_DMX_ERR("reset dvr output buffer fail\n");
			return -EFAULT;
		}
	} else if (IS_INPUT_DEVICE(dir)) {
		if (dvr->feed_task) {
			MTK_DMX_DEBUG("stop feed task(%d)\n", dvr->path_id);
			dvr->filein_task_info.filein_state = DVR_FILEIN_TASK_CLOSED_STATE;
			kthread_stop(dvr->feed_task);
			dvr->feed_task = NULL;
		}
		dvb_dvr2_flush(cast_to_dvr_parent(dvr));

		ret = dvb_dvr2_set_buffer(
			cast_to_dvr_parent(dvr),
			DVR2_BUF_CTX_TYPE_IN,
			NULL);
		if (ret < 0) {
			MTK_DMX_ERR("reset dvr input buffer fail\n");
			return -EFAULT;
		}
	}

	ret = dvb_dvr2_close(cast_to_dvr_parent(dvr));
	if (ret < 0) {
		MTK_DMX_ERR("close dvr fail\n");
		return -EFAULT;
	}

	if (IS_OUTPUT_DEVICE(dir)) {
		if (cast_to_dvr_parent(dvr)->index != PVR_INVALID) {
			MTK_DMX_INFO("pvr engine %d is freed from path %d\n",
				cast_to_dvr_parent(dvr)->index,
				dvr->path_id);
			mtk_dmx_rm_pvr_free_engine(dvr->rm, cast_to_dvr_parent(dvr)->index);
			cast_to_dvr_parent(dvr)->index = PVR_INVALID;
		}
	}

	return 0;
}

bool mtk_dvr2_is_running(struct mtk_dvr2 *dvr)
{
	return (cast_to_dvr_parent(dvr)->state == DVR2_STATE_GO);
}

bool mtk_dvr2_is_need_ringbuf(struct dvb_fh *fh)
{
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	struct mtk_bc_ctx *buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	enum mtk_bc_memtype type;

	if (mtk_bc_get_data_buf_type(buf_ctx, &type) < 0) {
		MTK_DMX_ERR("get data buffer type fail\n");
		return false;
	}

	return (type == MTK_BC_MEMTYPE_RINGBUF);
}

bool mtk_dvr2_is_tee_mode(struct mtk_dvr2 *dvr)
{
	return (dvr->rec_cfg.pipeline_id != 0);
}

bool mtk_dvr2_is_filein_tee_mode(struct mtk_dvr2 *dvr)
{
	#if(ENABLE_SECURE_FILEIN_VERITY)
	return true;
	#endif
	return (dvr->filein_cfg.pipeline_id != 0);
}

int mtk_dvr2_query_caps(struct mtk_dvr2 *dvr, struct dmx2_capability *info)
{
	int i;
	int ret = 0;
	u16 num;
	struct dmx2_device_caps *entry = NULL;

	info->capabilities = dvr->capabilities;

	for (i = 0, entry = info->device_info; i < info->count; i++, entry++) {
		num = mtk_dmx_rm_get_hwcap_by_name(dvr->rm, entry->match);

		if (!num)
			entry->supported = false;
		else {
			entry->supported = true;
			ret = snprintf(entry->cap, sizeof(entry->cap), "%u", num);
			if (ret < 0) {
				MTK_DMX_ERR("snprintf err\n");
				return -EFAULT;
			}
		}
	}

	return 0;
}

int mtk_dvr2_prewrite(struct mtk_dvr2 *dvr)
{
	int ret = 0;

	ret = call_op(dvr, playback, prewrite, dvr);
	if (ret == -ENOTTY)
		return 0;

	return ret;
}

static void mtk_dvr_set_status(struct mtk_dvr2 *dvr, bool status)
{
	struct dvb_dvr2 *base = cast_to_dvr_parent(dvr);
	void *priv = base->priv;

	if (!priv)
		return;

	switch (base->protocol) {
	case DVR2_FORMAT_M2TS:
		((struct mtk_m2ts_dvr_private *)priv)->status = status;
		break;

	case DVR2_FORMAT_ISDB3:
		((struct mtk_isdb3_dvr_private *)priv)->status = status;
		break;

	case DVR2_FORMAT_ATSC3:
		((struct mtk_atsc3_dvr_private *)priv)->status = status;
		break;

	default:
		break;
	}
}

int mtk_dvr2_do_write(struct mtk_dvr2 *dvr, bool blocking)
{
#define FILEIN_IDLE_TIMEOUT 20000 // ms
	struct dvb_dvr2 *dvb_dvr;
	bool idle = false;
	int eng_id;
	struct dmx2_buffer_ctx *buf_ctx;
	struct mtk_bc_ctx *bc_ctx;

	dvb_dvr = cast_to_dvr_parent(dvr);
	eng_id = mtk_dmx_rm_filein_find_engine_by_path(dvr->rm, dvr->path_id);

	// get filein status
	idle = mtk_dmx_io_is_filein_done(eng_id);

	if (!idle && !blocking) {
		if (jiffies_to_msecs(jiffies - dvr->filein_task_info.last_idle) > FILEIN_IDLE_TIMEOUT) {
			dvr->filein_task_info.last_idle = jiffies;
			mtk_dmx_io_get_audio_fifo_status();
			mtk_dmx_io_get_video_fifo_status();
		}
		return -EBUSY;
	}
	dvr->filein_task_info.last_idle = jiffies;

	if (dvr->filein_task_info.write_offset != INVALID_FILEIN_WRITE_OFFSET) {
		buf_ctx = dvb_dvr->buf_ctx[DVR2_BUF_CTX_TYPE_IN];
		bc_ctx = dynamic_cast(struct mtk_bc_ctx *, buf_ctx);

		MTK_DMX_DEBUG("update wptr to %x\n", dvr->filein_task_info.write_offset);
		mtk_bc_ringbuf_set_rptr(bc_ctx, dvr->filein_task_info.write_offset);

		if (mtk_bc_ringbuf_available_to_read(bc_ctx) <= 0) {
			MTK_DMX_DEBUG("no data to be feed\n");
			return 0;
		}
	}

	if (!idle && blocking) {
		if (!mtk_dmx_io_wait_filein_status_idle(eng_id))
			return 0;
	}

	return dvb_dvr2_write(dvb_dvr);
}

int mtk_dvr2_write(struct mtk_dvr2 *dvr, int non_blocking)
{
	int ret = 0;
	struct dvb_device *dvbdev;
	struct dvbdev_properties props = { 0 };
	struct dvbdev_property prop = { 0 };
	struct dvb_dvr2 *base = cast_to_dvr_parent(dvr);
	bool streaming_on = false;

	if (!(dvr->capabilities & DMX2_CAP_IN_MEMORY)) {
		MTK_DMX_DEBUG("Not support (path: %d) !!!\n", dvr->path_id);
		return -EOPNOTSUPP;
	}

	switch (base->protocol) {
	case DVR2_FORMAT_M2TS:
		streaming_on = true;
		break;
	case DVR2_FORMAT_ISDB3:
		streaming_on = true;
		break;
	case DVR2_FORMAT_ATSC3:
		streaming_on = true;
		break;
	default:
		break;
	}
	mtk_dvr_set_status(dvr, true);

	if (streaming_on) {
		dvbdev = mdrv_adapter_find_device(DVB_DEVICE_DEMUX2, dvr->path_id);
		if (dvbdev) {
			prop.cmd = ADAP_CMD_FILEIN_STREAMING_ON;
			props.num = 1;
			props.props = &prop;
			mdrv_adapter_query_device_params(dvbdev, &props);
		}
	}

	if (non_blocking) {
		if (!dvr->feed_task) {
			dvr->feed_task = kthread_run(
				mtk_dmx_tm_pvr_feed_task,
				(void *)dvr,
				"feed_task%d",
				dvr->path_id);
			set_user_nice(dvr->feed_task, DEFAULT_FILEIN_TASK_NICE);
			if (IS_ERR(dvr->feed_task)) {
				MTK_DMX_ERR("create feed task fail\n");
				dvr->feed_task = NULL;
				mtk_dvr_set_status(dvr, false);
				return -ENODEV;
			}
		}

		MTK_DMX_DEBUG("start feed task(%d)\n", dvr->path_id);
		ret = 0;
	} else {
		ret = mtk_dvr2_do_write(dvr, true);
	}

	mtk_dvr_set_status(dvr, false);

	return ret;
}

int mtk_dvr2_set_secure_mode(struct mtk_dvr2 *dvr, u64 key_token, u32 pipeline_id)
{
	dvr->rec_cfg.key_token = key_token;
	dvr->rec_cfg.pipeline_id = pipeline_id;
	return 0;
}

int mtk_dvr2_set_filein_secure_mode(struct mtk_dvr2 *dvr, u64 key_token, u32 pipeline_id)
{
	dvr->filein_cfg.key_token = key_token;
	dvr->filein_cfg.pipeline_id = pipeline_id;
	return 0;
}

int mtk_dvr2_set_crypto_type(struct mtk_dvr2 *dvr, enum dvr_encrypt_type type)
{
	dvr->rec_cfg.encrypt_type = type;
	return 0;
}

int mtk_dvr2_set_filein_crypto_type(struct mtk_dvr2 *dvr, enum dvr_encrypt_type type)
{
	dvr->filein_cfg.encrypt_type = type;
	return 0;
}

int mtk_dvr2_attach_ca(struct mtk_dvr2 *dvr, struct dmx_ca_info info)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);

	// Check if pvr is enabled, set pvr id
	if (ctx != NULL && IS_OUTPUT_DEVICE(ctx->dir))
		info.pvr_id = cast_to_dvr_parent(dvr)->index;
	else
		info.pvr_id = PVR_INVALID;

	ret = call_op(dvr, crypto, attach, dvr, info);
	if (ret < 0) {
		MTK_DMX_ERR("attach CA engine(%u) fail\n", info.eng_id);
		return -EFAULT;
	}

	return 0;
}

int mtk_dvr2_detach_ca(struct mtk_dvr2 *dvr, struct dmx_ca_info info)
{
	int ret;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(cast_to_dvr_parent(dvr)->fh);

	// Check if pvr is enabled, set pvr id
	if (ctx != NULL && IS_OUTPUT_DEVICE(ctx->dir))
		info.pvr_id = cast_to_dvr_parent(dvr)->index;
	else
		info.pvr_id = PVR_INVALID;

	ret = call_op(dvr, crypto, detach, dvr, info);
	if (ret < 0) {
		MTK_DMX_ERR("detach CA engine(%u) fail\n", info.eng_id);
		return -EFAULT;
	}

	return ret;
}

int mtk_dvr2_add_ca_pid(struct mtk_dvr2 *dvr, u16 pid, s32 type, u32 mask)
{
	struct dvr_ca_pid *ca_pid;
	struct dvr_crypto_config *cfg = &dvr->crypto_cfg;

	ca_pid = kzalloc(sizeof(struct dvr_ca_pid), GFP_KERNEL);
	if (!ca_pid) {
		MTK_DMX_ERR("allocate ca_pid fail\n");
		return -ENOMEM;
	}

	ca_pid->pid = pid;
	ca_pid->type = type;
	ca_pid->mask = mask;

	spin_lock(&cfg->lock);

	list_add(&ca_pid->list, &cfg->list_head);
	cfg->pid_num++;

	spin_unlock(&cfg->lock);

	return 0;
}

int mtk_dvr2_remove_ca_pid(struct mtk_dvr2 *dvr, u16 pid)
{
	struct dvr_ca_pid *ca_pid, *q;
	struct dvr_crypto_config *cfg = &dvr->crypto_cfg;

	spin_lock(&cfg->lock);

	list_for_each_entry_safe(ca_pid, q, &cfg->list_head, list)
		if (ca_pid->pid == pid) {
			list_del(&ca_pid->list);
			kfree(ca_pid);
			cfg->pid_num--;
			break;
		}

	spin_unlock(&cfg->lock);

	return 0;
}

int mtk_dvr2_init(struct mtk_dvr2 *dvr)
{
	int ret;

	if (!dvr || !dvr->rm || !dvr->pm) {
		MTK_DMX_ERR("invalid params\n");
		return -EINVAL;
	}

	ret = _init(dvr);
	if (ret < 0) {
		MTK_DMX_ERR("__init fail\n");
		return -EFAULT;
	}

	return dvb_dvr2_init(cast_to_dvr_parent(dvr));
}

int mtk_dvr2_exit(struct mtk_dvr2 *dvr)
{
	int ret;

	ret = _deinit(dvr);
	if (ret < 0) {
		MTK_DMX_ERR("__deinit fail\n");
		return -EFAULT;
	}

	return dvb_dvr2_exit(cast_to_dvr_parent(dvr));
}
