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

#include <linux/version.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/of.h>
#include <linux/interrupt.h>

#include <linux/dvb/dmx2_mtk.h>

#include <media/dvb_fh.h>
#include <media/dvb_event.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_tm.h"
#include "mtk_dmx_bufctrl.h"
#include "mtk_dmx_sysfs.h"
#include "mtk_dmx_dev.h"
#include "mtk_dmx_io.h"
#include "mtk_dmx_pvr.h"
#include "mtk_dmx_mediactrl.h"
#include "mtk_dmx_drv.h"
#include "mtk_dmx_tsodev.h"
#include "mtk_dmx_stc_dev.h"
#include "mtk_dmx_dvrdrv.h"
#include "mtk_dmx_atsc3.h"
#include "mtk_mvd_parser.h"
#include "mtk_mad_parser.h"
#include "mtk_dmx_alp.h"

#include "mdrv_adapter.h"

#define MTK_DMX_DEV_PROFILE_LOG_US_MIN (5000)
#define REC_BUFFER_SIZE (22*188*1024)
// 192 = 2 * 2 * 2 * 2 * 2 * 2 * 3
// 188 = 2 * 2 * 47
#define LCM_188_192 (9024) // 2 * 2 * 2 * 2 * 2 * 2 * 3 * 47

#define DEV_CHECK_RANGE(min, max, value) \
	({ \
		int __ret = MTK_DMX_SUCCESS; \
		if (((value) < (min)) || \
			((value) > (max))) { \
			__ret = MTK_DMX_FAIL; \
			MTK_DMX_ERR( \
				"value(0x%x) is not within range(0x%x, 0x%x)\n", \
				value, \
				min, \
				max); \
		} \
		__ret; \
	})

struct mtk_dmx2dev *gDev;

enum used_to_init_type {
	USED_TO_INIT_NONE,
	USED_TO_INIT_RINGBUFF,
	USED_TO_INIT_SECURE_RINGBUFF,
};

struct mmap_buf_info {
	enum mem_info_type mem_info_type;
	enum mmap_buf_type mmap_buf_type;
	enum used_to_init_type used_to_init_type;
	u32 buf_size;
};

static u16 path_id_ranges[PATH_ID_TYPE_MAX][PATH_ID_RANGE_MAX] = {
	{ PATH_ID_INVALID, PATH_ID_INVALID },
	{ PATH_ID_INVALID, PATH_ID_INVALID },
	{ PATH_ID_INVALID,  PATH_ID_INVALID },
};

static int allocate_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf,
		int mmap_buf_info_count,
		struct mmap_buf_info *mmap_buf_info);
static int release_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf);

static int init_dvr(struct dvb_fh *fh, void *sneaky);
static int allocate_pvr_buf(struct dvb_fh *fh);
static int start_dvr(struct dvb_fh *fh);
static int stop_dvr(struct dvb_fh *fh);
static int flush_dvr(struct dvb_fh *fh);
static int release_dvr(struct dvb_fh *fh);
static void mtk_dvr_filein_dmaw_protect(struct dvb_fh *fh, bool enable);

static int put_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx)
{
	int ret = 0;
	int tmp_ret = 0;
	struct mem_info mem_info = { 0 };
	int i = 0;

	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;

	for (i = MEM_INFO_TYPE_MIN; i < MEM_INFO_TYPE_MAX; i++) {
		tmp_ret = mtk_bc_get_mem_info(
				bc_ctx,
				i,
				&mem_info);
		if (tmp_ret < 0) {
			MTK_DMX_ERR("get meminfo fail\n");
			ret = -EFAULT;
			continue;
		}

		if (mem_info.size > 0)	{
			tmp_ret = mtk_dmx_rm_put_buf(rm, &mem_info);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("put rm buf fail\n");
				ret = -EFAULT;
				continue;
			}
		}
	}

	return ret;
}

static int get_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		int mmap_buf_info_count,
		struct mmap_buf_info *mmap_buf_info)
{
	int ret = 0;
	int tmp_ret = 0;
	struct mem_info mem_info = { 0 };
	int i = 0;

	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mmap_buf_info == NULL))
		return -EINVAL;

	tmp_ret = mtk_bc_reset_mem_info(bc_ctx);
	if (tmp_ret < 0) {
		MTK_DMX_ERR("reset meminfo fail\n");
		return -EFAULT;
	}

	for (i = 0; i < mmap_buf_info_count; i++) {
		tmp_ret = mtk_dmx_rm_get_buf(
				rm,
				mmap_buf_info[i].mmap_buf_type,
				mmap_buf_info[i].buf_size,
				&mem_info);
		if (tmp_ret < 0) {
			MTK_DMX_ERR("get rm buf fail, type:%d size:0x%x\n",
						mmap_buf_info[i].mmap_buf_type, mem_info.size);
			ret = -EFAULT;
			break;
		}

		tmp_ret = mtk_bc_set_mem_info(
				bc_ctx,
				mmap_buf_info[i].mem_info_type,
				&mem_info);
		if (tmp_ret < 0) {
			MTK_DMX_ERR("set meminfo fail\n");
			// put_mem_info relies on mem_info, so put it immediately.
			tmp_ret = mtk_dmx_rm_put_buf(rm, &mem_info);
			if (tmp_ret < 0)
				MTK_DMX_ERR("put rm buf fail\n");
			ret = -EFAULT;
			break;
		}
	}

	if (ret < 0) {
		tmp_ret = put_buf(rm, bc_ctx);
		if (tmp_ret < 0)
			MTK_DMX_DEBUG("put meminfo fail\n");
	}

	return ret;
}

static int init_ringbuf(
	struct mtk_bc_ctx *bc_ctx,
	int mmap_buf_info_count,
	struct mmap_buf_info *mmap_buf_info)
{
	int ret = 0;
	int tmp_ret = 0;
	struct mem_info mem_info = { 0 };
	int i = 0;

	// It is expected that at least one is used.
	ret = -EFAULT;
	MTK_DMX_DEBUG("mmap_buf_info_count(%d)\n",mmap_buf_info_count);
	for (i = 0; i < mmap_buf_info_count; i++) {
		// check if mmap_buf_info is used to init ring / secure_ring buffer
		MTK_DMX_DEBUG("used_to_init_type[%d](%d) ", i, mmap_buf_info[i].used_to_init_type);
		if (mmap_buf_info[i].used_to_init_type != USED_TO_INIT_NONE) {
			tmp_ret = mtk_bc_get_mem_info(
					bc_ctx,
					mmap_buf_info[i].mem_info_type,
					&mem_info);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("get meminfo fail\n");
				ret = -EFAULT;
				break;
			}

			switch (mmap_buf_info[i].used_to_init_type) {
				case USED_TO_INIT_RINGBUFF: {
					tmp_ret = mtk_bc_ringbuf_init(
							bc_ctx,
							mem_info.vaddr,
							mem_info.size);
					if (tmp_ret < 0) {
						MTK_DMX_ERR("init ringbuf fail\n");
						ret = -EFAULT;
					}
					else {
						ret = 0;
					}
					break;
				}
				case USED_TO_INIT_SECURE_RINGBUFF: {
					tmp_ret = mtk_bc_secure_ringbuf_init(
							bc_ctx,
							mem_info.vaddr,
							mem_info.size);
					if (tmp_ret < 0) {
						MTK_DMX_ERR("init secure ringbuf fail\n");
						ret = -EFAULT;
						break;
					}
					else {
						ret = 0;
					}
					break;
				}
				default : {
					MTK_DMX_ERR("incorrect init buffer type\n");
					ret = -EFAULT;
					break;
				}
			}

			MTK_DMX_DEBUG("mmapType(%d) buf_size(%u) va(0x%llX)", mmap_buf_info[i].mmap_buf_type, mem_info.size, (u64)(size_t)(mem_info.vaddr));
		}
	}

	return ret;
}

static int do_buf_release(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf)
{
	int ret = 0;
	int tmp_ret = 0;

	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;

	do {
		if (is_need_ringbuf) {
			tmp_ret = mtk_bc_ringbuf_free(bc_ctx);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("free ringbuf fail\n");
				ret = -EFAULT;
				break;
			}

			tmp_ret = mtk_bc_secure_ringbuf_free(bc_ctx);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("free ringbuf fail\n");
				ret = -EFAULT;
				break;
			}

			tmp_ret = put_buf(rm, bc_ctx);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("put meminfo fail\n");
				ret = -EFAULT;
				break;
			}
		} else { // vb2
			if (mtk_bc_vb2_is_streaming(bc_ctx)) {
				tmp_ret = mtk_bc_vb2_stream_off(bc_ctx);
				if (tmp_ret < 0) {
					MTK_DMX_ERR("vb2 stream off fail\n");
					ret = -EFAULT;
					break;
				}
			}

			tmp_ret = mtk_bc_vb2_release(bc_ctx);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("vb2 release fail\n");
				ret = -EFAULT;
				break;
			}

			if (mtk_bc_vb2_is_ringbuf_mode(bc_ctx)) {
				// @TODO: need to modify
				//
			}
		}

		tmp_ret = mtk_bc_reset_mem_info(bc_ctx);
		if (tmp_ret < 0) {
			MTK_DMX_ERR("reset meminfo fail\n");
			ret = -EFAULT;
		}
	} while (0);

	return ret;
}

static int do_buf_allocation(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf,
		int mmap_buf_info_count,
		struct mmap_buf_info *mmap_buf_info)
{
	int ret = 0;
	int tmp_ret = 0;
	struct mem_info mem_info = { 0 };

	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mmap_buf_info_count <= 0))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mmap_buf_info == NULL))
		return -EINVAL;

	do {
		if (is_need_ringbuf) {
			tmp_ret = get_buf(
					rm,
					bc_ctx,
					mmap_buf_info_count,
					mmap_buf_info);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("get buf fail\n");
				ret = -EFAULT;
				break;
			}

			tmp_ret = init_ringbuf(
					bc_ctx,
					mmap_buf_info_count,
					mmap_buf_info);
			if (tmp_ret < 0) {
				MTK_DMX_ERR("init ringbuf fail\n");
				ret = -EFAULT;
				break;
			}
			bc_ctx->ringbuf = true;
		} else { // vb2
			if (mtk_bc_vb2_is_ringbuf_mode(bc_ctx)) {
				if (MTK_DMX_VERIFY(mmap_buf_info_count != 1)) {
					ret = -EINVAL;
					break;
				}

				tmp_ret = mtk_bc_vb2_query_dmabuf(
						bc_ctx,
						&mem_info);
				if (tmp_ret < 0) {
					MTK_DMX_ERR("get meminfo fail\n");
					ret = -EFAULT;
					break;
				}

				tmp_ret = mtk_bc_set_mem_info(
						bc_ctx,
						mmap_buf_info[0].mem_info_type,
						&mem_info);
				if (tmp_ret < 0) {
					MTK_DMX_ERR("set meminfo fail\n");
					ret = -EFAULT;
					break;
				}
			} else {
				// @TODO: need to modify
			}
		}
	} while (0);

	return ret;
}

static int allocate_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf,
		int mmap_buf_info_count,
		struct mmap_buf_info *mmap_buf_info)
{
	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mmap_buf_info_count <= 0))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mmap_buf_info == NULL))
		return -EINVAL;

	if (is_need_ringbuf
		&& (mtk_bc_ringbuf_is_initialized(bc_ctx))) {
		MTK_DMX_DEBUG("ringbuf is initialized\n");
		return 0;
	}

	if (do_buf_allocation(
		rm,
		bc_ctx,
		is_need_ringbuf,
		mmap_buf_info_count,
		mmap_buf_info) < 0) {
		MTK_DMX_ERR("allocate buffer fail\n");
		if (do_buf_release(
			rm,
			bc_ctx,
			is_need_ringbuf) < 0)
			MTK_DMX_DEBUG("free buffer fail\n");
		return -EFAULT;
	}

	return 0;
}

static int release_buf(
		struct mtk_dmx_rm *rm,
		struct mtk_bc_ctx *bc_ctx,
		bool is_need_ringbuf)
{
	if (MTK_DMX_VERIFY(rm == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;

	if (is_need_ringbuf
		&& (!mtk_bc_ringbuf_is_initialized(bc_ctx))) {
		MTK_DMX_DEBUG("ringbuf is not initialized\n");
		return 0;
	}

	return do_buf_release(rm, bc_ctx, is_need_ringbuf);
}

static int mtk_dmx_open(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dmx2dev *dmx2dev = dvbdev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	struct mtk_demux2 *demux = &path->demux2;
	struct mtk_demux2_fh_ctx *ctx;

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

	fh_dmxctx_init(ctx, demux);
	if (mtk_bc_set_buf_size(
		fh_dmxctx_get_bc_ctx(ctx),
		DVB_DEMUX2_BUFFER_SIZE) < 0) {
		MTK_DMX_ERR("set buf size fail\n");
		MTK_DMX_KVFREE(ctx);

		return -EFAULT;
	}

	dvb_fh_init(&ctx->fh, DVB_DEVICE_DEMUX2, &dmx2dev->fh_mgr, &dmx2dev->ctrl_hdl);
	dvb_fh_add(&ctx->fh);
	file->private_data = &ctx->fh;

	spin_lock_init(&ctx->ev_ctx.ev_ctx_lock);
	INIT_LIST_HEAD(&ctx->ev_ctx.ev_list_head);

	mtk_dmx_pm_open(demux->pm);
	return 0;
}

static int mtk_dmx_release(struct inode *inode, struct file *file)
{
	struct dvb_fh *fh = file->private_data;
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct mtk_bc_ctx *buf_ctx = fh_dmxctx_get_bc_ctx(ctx);
	struct mtk_demux2 *demux = fh_to_demux(fh);
	int ret = 0;

	if (demux == NULL)
		return -EINVAL;

	FH_WRITE_LOCK(&demux->tm->fh_lock);

	if (fh_dmxctx_get_flt_type(ctx) != FILTER_MAIN_TYPE_NONE) {
		bool is_running = mtk_demux2_is_filter_running(demux, fh);

		if (is_running) {
			MTK_DMX_INFO("still running but release called\n");
			ret = mtk_demux2_stop_filter(demux, fh);
			if (ret < 0) {
				MTK_DMX_ERR("filter stop fail (type = %x)\n",
					fh_dmxctx_get_flt_type(ctx));
				goto EXIT;
			}
		}

		ret = mtk_demux2_close_filter(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("close filter fail (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
			goto EXIT;
		}
		ret = mtk_demux2_release_pipeline_id(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("close pipeline id (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
			goto EXIT;
		}

		ret = release_buf(
			demux->rm,
			buf_ctx,
			buf_ctx->ringbuf);
		if (ret < 0) {
			MTK_DMX_ERR(
				"free buffer fail (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
			// Continue even though it is failed.
		}
	}

	dvb_fh_del(fh);
	dvb_fh_exit(fh);

	kfree(ctx);

	mtk_dmx_pm_close(demux->pm);
EXIT:
	FH_WRITE_UNLOCK(&demux->tm->fh_lock);

	return ret;
}

static ssize_t mtk_dmx_read(
	struct file *file,
	char __user *buf, size_t count,
	loff_t *ppos)
{
	ssize_t ret;
	struct dvb_fh *fh = file->private_data;
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct mtk_bc_ctx *buf_ctx = fh_dmxctx_get_bc_ctx(ctx);
	int non_blocking = file->f_flags & O_NONBLOCK;
	struct mtk_dmx_profile profile = {
			.log_level = LOGLEVEL_INFO,
			.log_prefix = __func__,
			.log_us = MTK_DMX_DEV_PROFILE_LOG_US_MIN,
	};

	if ((file->f_flags & O_ACCMODE) != O_RDONLY &&
		(file->f_flags & O_ACCMODE) != O_RDWR) {
		MTK_DMX_ERR("file access mode fail\n");
		return -EINVAL;
	}

	mtk_dmx_profile_get_begin_time(&profile);
	ret = mtk_bc_ringbuf_read_user(buf_ctx, buf, count, non_blocking);
	mtk_dmx_profile_get_end_time(&profile);

	return ret;
}

static int query_caps(struct dvb_fh *fh, struct dmx2_capability *info)
{
	int ret;
	struct dmx2_device_caps *temp = NULL, *ori;
	u32 size;

	if (MTK_DMX_VERIFY(info == NULL))
		return -EINVAL;

	if (info->count > 0) {
		size = info->count * sizeof(struct dmx2_device_caps);
		if (MTK_DMX_VERIFY(!size))
			return -EINVAL;

		temp = kmalloc(size, GFP_KERNEL);
		if (MTK_DMX_VERIFY(!temp))
			return -EFAULT;

		ret = copy_from_user(temp, (void __user *)info->device_info, size);
		if (MTK_DMX_VERIFY(ret))
			goto exit;

		ori = info->device_info;
		info->device_info = temp;
	}

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2 *demux = fh_to_demux(fh);

		ret = mtk_demux2_query_caps(demux, info);
	} else {
		struct mtk_dvr2 *dvr = fh_to_dvr(fh);

		ret = mtk_dvr2_query_caps(dvr, info);
	}

	if (ret < 0) {
		MTK_DMX_ERR("query caps fail\n");
		goto exit;
	}

	if (info->count > 0) {
		ret = copy_to_user((void __user *)ori, temp, size) ? -EFAULT : 0;
		info->device_info = ori;
		(void)MTK_DMX_VERIFY(ret);
	}
exit:
	kfree(temp);
	return ret;
}

static int set_buf_size(struct dvb_fh *fh, u32 size)
{
	bool is_running;
	struct mtk_bc_ctx *bc_ctx = NULL;
	u32 buf_size = 0;

	if (!size) {
		MTK_DMX_ERR("size is zero\n");
		return -EINVAL;
	}
	if (mtk_bc_get_ctx(fh, &bc_ctx) < 0) {
		MTK_DMX_ERR("get bc ctx fail\n");
		return -EINVAL;
	}
	if (mtk_bc_get_buf_size(
		bc_ctx,
		&buf_size) < 0) {
		MTK_DMX_ERR("get buf size fail\n");
		return -EFAULT;
	}
	if (buf_size == size)
		return 0;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2 *demux = fh_to_demux(fh);

		is_running = mtk_demux2_is_filter_running(demux, fh);
		if (is_running) {
			MTK_DMX_ERR("filter is running\n");
			return -EBUSY;
		}
	} else {
		struct mtk_dvr2 *dvr = fh_to_dvr(fh);
		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_is_need_ringbuf(fh)) {
			MTK_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		is_running = mtk_dvr2_is_running(dvr);
		if (is_running) {
			MTK_DMX_ERR("dvr is running\n");
			return -EBUSY;
		}

		if (mtk_bc_ringbuf_is_initialized(bc_ctx)) {
			MTK_DMX_ERR("no change after dvr buf is initialized\n");
			return -EBUSY;
		}
		size = (size / LCM_188_192) * LCM_188_192;
	}

	if (mtk_bc_set_buf_size(
		bc_ctx,
		size) < 0) {
		MTK_DMX_ERR("set buf size fail\n");
		return -EFAULT;
	}

	return 0;
}

static int get_stc(struct dvb_fh *fh, struct dmx2_stc *info)
{
	int ret;
	struct mtk_demux2 *demux = fh_to_demux(fh);

	ret = mtk_demux2_get_stc(demux, fh, info);
	if (ret < 0) {
		MTK_DMX_ERR("get stc fail\n");
		return -EFAULT;
	}

	return 0;
}

static int set_filter(
	struct dvb_fh *fh,
	enum filter_main_type type,
	struct dmx2_filter_settings *setting)
{
	int ret;
	bool is_running = false;
	struct mtk_demux2 *demux = fh_to_demux(fh);
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct dmx2_filter *filter;

	is_running = mtk_demux2_is_filter_running(demux, fh);
	if (is_running) {
		ret = mtk_demux2_stop_filter(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("filter already stop (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
		}
	}

	filter = dvb_dmx2_filter_find(cast_to_dmx_parent(demux), fh);
	if (filter) {
		MTK_DMX_DEBUG("always secure mode reconfig close filter\n");
		ret = mtk_demux2_close_filter(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("close filter fail (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
			return -EFAULT;
		}
		// reuse it, only flush old data
		ret = mtk_bc_ringbuf_flush(fh_dmxctx_get_bc_ctx(ctx));
		if (ret < 0) {
			MTK_DMX_ERR("flush filter ringbuf fail (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));
			return -EFAULT;
		}
	}

	fh_dmxctx_set_flt_type(ctx, type);

	ret = mtk_demux2_open_filter(demux, fh, setting);
	if (ret < 0) {
		MTK_DMX_ERR("set filter fail (type = %x)\n", type);
		return -EFAULT;
	}

	if (is_running) {
		ret = mtk_demux2_start_filter(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("start filter fail (type = %x)\n", type);
			return -EFAULT;
		}
	}

	return 0;
}

static int start_filter(struct dvb_fh *fh)
{
	int ret;
	int i;
	u32 alloc_buf_num = 1;
	u32 buf_size = 0;
	struct mtk_demux2 *demux = fh_to_demux(fh);
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct mtk_bc_ctx *buf_ctx = fh_dmxctx_get_bc_ctx(ctx);
	bool is_need_ringbuf;
	struct mmap_buf_info mmap_buf_info[] = {
		{
			.mem_info_type = MEM_INFO_TYPE_NORMAL,
			.mmap_buf_type = MMAP_BUF_TYPE_SEC,
			.used_to_init_type = USED_TO_INIT_RINGBUFF,
		},
	};

	if (demux == NULL)
		return -EINVAL;
	is_need_ringbuf = mtk_demux2_is_filter_need_ringbuf(demux, fh);

	alloc_buf_num = MTK_DMX_NELEMS(mmap_buf_info);
	for (i = 0; i < alloc_buf_num; i++) {
		ret = mtk_bc_get_buf_size(buf_ctx, &buf_size);
		if (ret < 0) {
			MTK_DMX_ERR("get buf size %d fail\n", buf_size);
			return -EFAULT;
		}
		mmap_buf_info[i].buf_size = buf_size;
	}

	do {
		ret = allocate_buf(
			demux->rm,
			buf_ctx,
			is_need_ringbuf,
			alloc_buf_num,
			mmap_buf_info);
		if (ret < 0) {
			MTK_DMX_ERR("allocate buffer fail\n");
			ret = -EFAULT;
			break;
		}

		ret = mtk_demux2_start_filter(demux, fh);
		if (ret < 0) {
			MTK_DMX_ERR("start filter fail (type = %x)\n",
				fh_dmxctx_get_flt_type(ctx));

			if (release_buf(
				demux->rm,
				buf_ctx,
				buf_ctx->ringbuf) < 0)
				MTK_DMX_ERR("free buffer fail\n");
		}
	} while (0);

	return ret;
}

static int stop_filter(struct dvb_fh *fh)
{
	int ret;
	struct mtk_demux2 *demux = fh_to_demux(fh);
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

	ret = mtk_demux2_stop_filter(demux, fh);
	if (ret < 0) {
		MTK_DMX_ERR("stop filter fail (type = %x)\n",
			fh_dmxctx_get_flt_type(ctx));
		return -EFAULT;
	}

	return 0;
}

static int flush_filter(struct dvb_fh *fh)
{
	int ret;
	struct mtk_demux2 *demux = fh_to_demux(fh);
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

	ret = mtk_demux2_flush_filter(demux, fh);
	if (ret < 0) {
		MTK_DMX_ERR("flush filter fail (type = %x)\n",
			fh_dmxctx_get_flt_type(ctx));
		return -EFAULT;
	}

	if (mtk_demux2_is_filter_need_ringbuf(demux, fh)) {
		struct mtk_bc_ctx *buf_ctx = fh_dmxctx_get_bc_ctx(ctx);

		ret = mtk_bc_ringbuf_reset(buf_ctx, true);
		if (ret < 0) {
			MTK_DMX_ERR("ringbuf flush fail\n");
			return -EFAULT;
		}
	}

	return 0;
}

static int set_ctrl(struct dvb_fh *fh, struct dmx2_control *ctrl)
{
	int ret;

	ret = dvb_ctrl_set(fh, fh->hdl, ctrl);
	if (ret < 0) {
		MTK_DMX_ERR("set ctrl fail (id = %u)\n", ctrl->id);
		return -EFAULT;
	}

	return 0;
}

static int get_ctrl(struct dvb_fh *fh, struct dmx2_control *ctrl)
{
	int ret;

	ret = dvb_ctrl_get(fh, fh->hdl, ctrl);
	if (ret < 0) {
		if (ctrl->id == DMX2_CID_GET_METADATA)
			MTK_DMX_DEBUG("get ctrl fail (id = %u)\n", ctrl->id);
		else
			MTK_DMX_ERR("get ctrl fail (id = %u)\n", ctrl->id);
		return -EFAULT;
	}

	return 0;
}

static int set_ctrls(struct dvb_fh *fh, struct dmx2_controls *ctrls)
{
	int ret;

	ret = dvb_ctrls_set(fh, fh->hdl, ctrls);
	if (ret < 0) {
		MTK_DMX_ERR("set ctrls fail\n");
		return -EFAULT;
	}

	return 0;
}

static int get_ctrls(struct dvb_fh *fh, struct dmx2_controls *ctrls)
{
	int ret;

	ret = dvb_ctrls_get(fh, fh->hdl, ctrls);
	if (ret < 0) {
		MTK_DMX_DEBUG("get ctrls fail\n");
		return -EFAULT;
	}

	return 0;
}

static int query_ctrl(struct dvb_fh *fh, struct dmx2_queryctrl *query)
{
	int ret;

	ret = dvb_ctrl_query(fh, fh->hdl, query);
	if (ret < 0) {
		MTK_DMX_ERR("query ctrl fail\n");
		return -EFAULT;
	}

	return 0;
}

static int dqueue_event(struct dvb_fh *fh, struct dmx2_event *ev, int non_blocking)
{
	int ret;

	ret = dvb_event_dequeue(fh, ev, non_blocking);
	if (ret < 0) {
		MTK_DMX_DEBUG("dequeue event fail, (type = %x)\n", ev->type);
		return ret;
	}

	return 0;
}

static int subscribe_event(struct dvb_fh *fh, const struct dmx2_event_subscription *sub)
{
	if (fh->type != DVB_DEVICE_DEMUX2) {
		MTK_DMX_ERR("subscribe event only support for demux device\n");
		return -EFAULT;
	}

	return mtk_demux2_subscribe_event(fh, sub);
}

static int unsubscribe_event(struct dvb_fh *fh, const struct dmx2_event_subscription *sub)
{
	if (fh->type != DVB_DEVICE_DEMUX2) {
		MTK_DMX_ERR("unsubscribe event only support for demux device\n");
		return -EFAULT;
	}

	return mtk_demux2_unsubscribe_event(fh, sub);
}

static int request_bufs(struct dvb_fh *fh, struct dmx2_requestbuffers *req, int non_blocking)
{
	int ret;
	struct mtk_bc_ctx *bc_ctx;
	bool wo_memcpy = (req->memory == DMX2_MEMORY_DMABUF);

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

		bc_ctx = fh_dmxctx_get_bc_ctx(ctx);

		ret = mtk_bc_vb2_init(bc_ctx, "demux2_filter", non_blocking, wo_memcpy);
	} else {
		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_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
		ret = mtk_bc_vb2_init(bc_ctx, "dvr2", non_blocking, wo_memcpy);
	}

	if (ret < 0) {
		MTK_DMX_ERR("vb2 init fail\n");
		return -EFAULT;
	}

	ret = mtk_bc_vb2_reqbufs(bc_ctx, req);
	if (ret < 0) {
		MTK_DMX_ERR("vb2 reqbufs fail\n");
		return -EFAULT;
	}

	return 0;
}

static int query_buf(struct dvb_fh *fh, struct dmx2_buffer *buf)
{
	int ret;
	struct mtk_bc_ctx *bc_ctx;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

		bc_ctx = fh_dmxctx_get_bc_ctx(ctx);
	} else {
		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_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
	}

	ret = mtk_bc_vb2_querybuf(bc_ctx, buf);
	if (ret < 0) {
		MTK_DMX_ERR("vb2 querybuf fail\n");
		return -EFAULT;
	}

	return 0;
}

static int export_buf(struct dvb_fh *fh, struct dmx2_exportbuffer *exp)
{
	int ret;
	struct mtk_bc_ctx *bc_ctx;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

		bc_ctx = fh_dmxctx_get_bc_ctx(ctx);
	} else {
		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_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
	}

	ret = mtk_bc_vb2_expbuf(bc_ctx, exp);
	if (ret < 0) {
		MTK_DMX_ERR("vb2 expbuf fail\n");
		return -EFAULT;
	}

	return 0;
}

static int queue_buf(struct dvb_fh *fh, struct dmx2_buffer *buf)
{
	int ret;
	struct mtk_bc_ctx *bc_ctx;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

		bc_ctx = fh_dmxctx_get_bc_ctx(ctx);
	} else {
		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_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
	}

	if (!mtk_bc_vb2_is_streaming(bc_ctx) && mtk_bc_vb2_stream_on(bc_ctx)) {
		MTK_DMX_ERR("vb2 stream on fail\n");
		return -EFAULT;
	}

	ret = mtk_bc_vb2_qbuf(bc_ctx, buf);
	if (ret < 0) {
		MTK_DMX_DEBUG("vb2 qbuf fail\n");
		return -EFAULT;
	}

	return 0;
}

static int dequeue_buf(struct dvb_fh *fh, struct dmx2_buffer *buf)
{
	int ret;
	struct mtk_bc_ctx *bc_ctx;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);

		bc_ctx = fh_dmxctx_get_bc_ctx(ctx);
	} else {
		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_DMX_ERR("not support !!!\n");
			return -EOPNOTSUPP;
		}

		bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
	}

	ret = mtk_bc_vb2_dqbuf(bc_ctx, buf);
	if (ret < 0) {
		MTK_DMX_DEBUG("vb2 dqbuf fail\n");
		return -EFAULT;
	}

	return 0;
}

static long mtk_dmx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct dvb_fh *fh = file->private_data;
	int non_blocking = file->f_flags & O_NONBLOCK;

	MTK_DMX_DEBUG(
			"cmd = %s\n",
			dmx2_ioctl_2_str(cmd));

	switch (cmd) {
	case DMX2_QUERYCAP:
		ret = query_caps(fh, (struct dmx2_capability *)arg);
		break;
	case DMX2_SET_BUFFER_SIZE:
		ret = set_buf_size(fh, (u32)arg);
		break;
	case DMX2_GET_STC:
		ret = get_stc(fh, (struct dmx2_stc *)arg);
		break;
	case DMX2_SET_TS_FILTER:
		ret = set_filter(fh, FILTER_MAIN_TYPE_TS, (struct dmx2_filter_settings *)arg);
		break;
	case DMX2_SET_TLV_FILTER:
		ret = set_filter(fh, FILTER_MAIN_TYPE_TLV, (struct dmx2_filter_settings *)arg);
		break;
	case DMX2_SET_ALP_FILTER:
		ret = set_filter(fh, FILTER_MAIN_TYPE_ALP, (struct dmx2_filter_settings *)arg);
		break;
	case DMX2_SET_IP_FILTER:
		ret = set_filter(fh, FILTER_MAIN_TYPE_IP, (struct dmx2_filter_settings *)arg);
		break;
	case DMX2_SET_MMTP_FILTER:
		ret = set_filter(fh, FILTER_MAIN_TYPE_MMTP, (struct dmx2_filter_settings *)arg);
		break;
	case DMX2_START:
		ret = start_filter(fh);
		break;
	case DMX2_STOP:
		ret = stop_filter(fh);
		break;
	case DMX2_FLUSH:
		ret = flush_filter(fh);
		break;
	case DMX2_SET_CTRL:
		ret = set_ctrl(fh, (struct dmx2_control *)arg);
		break;
	case DMX2_GET_CTRL:
		ret = get_ctrl(fh, (struct dmx2_control *)arg);
		break;
	case DMX2_SET_CTRLS:
		ret = set_ctrls(fh, (struct dmx2_controls *)arg);
		break;
	case DMX2_GET_CTRLS:
		ret = get_ctrls(fh, (struct dmx2_controls *)arg);
		break;
	case DMX2_QUERYCTRL:
		ret = query_ctrl(fh, (struct dmx2_queryctrl *)arg);
		break;
	case DMX2_DQEVENT:
		ret = dqueue_event(fh, (struct dmx2_event *)arg, non_blocking);
		break;
	case DMX2_SUBSCRIBE_EVENT:
		ret = subscribe_event(fh, (const struct dmx2_event_subscription *)arg);
		break;
	case DMX2_UNSUBSCRIBE_EVENT:
		ret = unsubscribe_event(fh, (const struct dmx2_event_subscription *)arg);
		break;
	case DMX2_REQBUFS:
		ret = request_bufs(fh, (struct dmx2_requestbuffers *)arg, non_blocking);
		break;
	case DMX2_QUERYBUF:
		ret = query_buf(fh, (struct dmx2_buffer *)arg);
		break;
	case DMX2_EXPBUF:
		ret = export_buf(fh, (struct dmx2_exportbuffer *)arg);
		break;
	case DMX2_QBUF:
		ret = queue_buf(fh, (struct dmx2_buffer *)arg);
		break;
	case DMX2_DQBUF:
		ret = dequeue_buf(fh, (struct dmx2_buffer *)arg);
		break;
	default:
		return -EINVAL;
	}

	return ret;
}

static __poll_t mtk_dmx_poll(struct file *file, poll_table *wait)
{
	struct dvb_fh *fh = file->private_data;
	struct dmx2dev *dmx2dev = fh_to_dmx2dev(fh);
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct mtk_bc_ctx *bc_ctx = fh_dmxctx_get_bc_ctx(ctx);
	__poll_t mask = 0;

	if (dmx2dev->exit)
		return -EPOLLERR;

	if (mtk_bc_vb2_is_streaming(bc_ctx))
		mask = mtk_bc_vb2_poll(bc_ctx, file, wait);
	else
		mask = mtk_bc_ringbuf_poll(bc_ctx, file, wait);

	return mask;
}

#ifdef CONFIG_DVB_MMAP
static int mtk_dmx_mmap(struct file *file, struct vm_area_struct *vma)
{
	int ret;
	struct dvb_fh *fh = file->private_data;
	struct mtk_demux2_fh_ctx *ctx = fh_to_dmxctx(fh);
	struct mtk_bc_ctx *bc_ctx = fh_dmxctx_get_bc_ctx(ctx);

	ret = mtk_bc_vb2_mmap(bc_ctx, vma);
	if (ret < 0) {
		MTK_DMX_ERR("mmap fail\n");
		return -EFAULT;
	}

	return 0;
}
#endif

static const struct file_operations mtk_dmx_fops = {
	.owner			= THIS_MODULE,
	.open			= mtk_dmx_open,
	.release		= mtk_dmx_release,
	.read			= mtk_dmx_read,
	.unlocked_ioctl = mtk_dmx_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= mtk_dmx_ioctl,
#endif
	.poll			= mtk_dmx_poll,
#ifdef CONFIG_DVB_MMAP
	.mmap			= mtk_dmx_mmap,
#endif
};

static int init_dvr(struct dvb_fh *fh, void *sneaky)
{
	int ret;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	struct mtk_bc_ctx *bc_ctx = NULL;
	u32 size = (DVB_DVR2_BUFFER_SIZE / LCM_188_192) * LCM_188_192;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	int filein_eng;
	struct dvb_dvr2 *base = cast_to_dvr_parent(dvr);

	if (dir == DVR_DATA_TO_DEVICE) {
		if (base->protocol == DVR2_FORMAT_ISDB3) {
			filein_eng = mtk_dmx_rm_tlv_filein_alloc(dvr->rm, dvr->path_id);
			if (filein_eng < 0) {
				MTK_DMX_ERR("failed to allocate tlv filein with path %d\n",
					dvr->path_id);
				return -EBUSY;
			}
		}
		filein_eng = mtk_dmx_rm_filein_alloc_engine(dvr->rm, dvr->path_id);
		if (filein_eng < 0) {
			MTK_DMX_ERR("failed to allocate filein engine with path %d\n",
				dvr->path_id);
			return -EBUSY;
		}

		memset(&dvr->filein_cfg, 0, sizeof(struct dvr_filein_config));
	} else {
		size = REC_BUFFER_SIZE;
	}

	if (mtk_bc_get_ctx(fh, &bc_ctx) < 0) {
		MTK_DMX_ERR("get bc ctx fail\n");
		return -EINVAL;
	}

	ret = mtk_dvr2_open(dvr, fh);
	if (ret < 0)
		return -EFAULT;

	if (sneaky)
		size = *(u32 *)sneaky;
	ret = mtk_bc_set_buf_size(bc_ctx, size);
	if (ret < 0) {
		MTK_DMX_ERR("set buf size fail\n");
		return -EFAULT;
	}

	return 0;
}

static int mtk_dvr_open(struct inode *inode, struct file *file)
{
	int ret;
	struct dvb_device *dvbdev = file->private_data;
	struct dmx2dev *dmx2dev = dvbdev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	struct mtk_dvr2 *dvr;
	struct mtk_dvr2_fh_ctx *ctx;
	struct dvb_fh *fh;
	enum dvr_data_direction dir = DVR_DATA_NONE;

	switch (file->f_flags & O_ACCMODE) {
	case O_RDONLY:
		dir = DVR_DATA_FROM_DEVICE;
		dvr = &path->dvr_record;
		break;
	case O_WRONLY:
		dir = DVR_DATA_TO_DEVICE;
		dvr = &path->dvr_playback;
		break;
	case O_RDWR:
		MTK_DMX_ERR("not support duplex, cast to 'O_RDONLY'\n");
		dir = DVR_DATA_FROM_DEVICE;
		dvr = &path->dvr_record;
		break;
	default:
		MTK_DMX_ERR("invalid file access mode\n");
		return -EINVAL;
	}

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

	fh_dvrctx_init(ctx, dvr);
	fh_dvrctx_set_dir(ctx, dir);

	fh = &ctx->fh;
	dvb_fh_init(fh, DVB_DEVICE_DVR2, &dmx2dev->fh_mgr, &dmx2dev->ctrl_hdl);
	dvb_fh_add(fh);
	file->private_data = fh;

	ret = init_dvr(fh, NULL);
	if (ret < 0) {
		if (ret == -EBUSY)
			MTK_DMX_ERR("no resource for filein\n");
		MTK_DMX_ERR("init dvr fail\n");
		file->private_data = NULL;
		dvb_fh_del(fh);
		dvb_fh_exit(fh);
		kfree(ctx);
		return ret;
	}

	mtk_dmx_pm_open(dvr->pm);

	return 0;
}

static int release_dvr(struct dvb_fh *fh)
{
	int ret, filein_eng;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	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);
	bool is_need_output_ringbuf = IS_OUTPUT_DEVICE(dir) && mtk_dvr2_is_need_ringbuf(fh);
	struct dvb_dvr2 *base = cast_to_dvr_parent(dvr);
	struct mtk_dmx_path *path = dvr2_to_path(dvr);
	struct mtk_atsc3_dvr_private *mtk_atsc3_dvr_private = NULL;
	struct mtk_demux2 *mtk_demux2 = NULL;
	u32 buf_num = buf_ctx->d_ctx.buf_num;
	int i = 0;

	if (base->state == DVR2_STATE_GO) {
		ret = stop_dvr(fh);
		if (ret < 0)
			MTK_DMX_ERR("stop dvr fail\n");
	}

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

	if (dir == DVR_DATA_TO_DEVICE) {
		if (mtk_dvr2_is_filein_tee_mode(dvr)) {
			struct mem_info mem_info = { 0 };

			MTK_DMX_INFO("secure playback, unlock buffer\n");
			if (mtk_bc_get_mem_info(
						buf_ctx,
						MEM_INFO_TYPE_NORMAL,
						&mem_info) < 0) {
				MTK_DMX_ERR("get meminfo fail\n");
				return -EFAULT;
			}

			ret = mtk_dmx_tee_if_play_unlock_buffer(0, &mem_info);
			if (ret < 0) {
				MTK_DMX_ERR("unlock filein buffer fail\n");
				return -EFAULT;
			}
		}

		if (base->protocol == DVR2_FORMAT_ISDB3) {
			filein_eng = mtk_dmx_rm_tlv_filein_find(dvr->rm, dvr->path_id);
			if (filein_eng >= 0)
				mtk_dmx_rm_tlv_filein_free(dvr->rm, filein_eng);
		}
		filein_eng = mtk_dmx_rm_filein_find_engine_by_path(dvr->rm, dvr->path_id);
		if (filein_eng >= 0)
			mtk_dmx_rm_filein_free_engine(dvr->rm, filein_eng);
		path->fe_prev = (void *)(-EFAULT);

		memset(&dvr->filein_cfg, 0, sizeof(struct dvr_filein_config));

		mtk_dvr_filein_dmaw_protect(fh, false);
	}

	for (i = 0; i < buf_num; i++) {
		MTK_DMX_INFO("[release_buf]buf_type: %d paddr:%llx, vaddr:%p, size:%x\n",
			i, buf_ctx->d_ctx.mem_info[i].paddr, buf_ctx->d_ctx.mem_info[i].vaddr,
			buf_ctx->d_ctx.mem_info[i].size);
	}

	ret = release_buf(
		dvr->rm,
		buf_ctx,
		IS_INPUT_DEVICE(dir) || is_need_output_ringbuf);
	if (ret < 0) {
		MTK_DMX_ERR("free buffer fail\n");
		// Continue even though it is failed.
	}
	mtk_demux2 = fh_to_demux(fh);

	if (mtk_demux2->base.protocol == DMX2_FORMAT_ATSC3) {
		mtk_atsc3_dvr_private = (struct mtk_atsc3_dvr_private *)base->priv;

		if (mtk_atsc3_dvr_private->is_austin) {
			buf_ctx = &mtk_atsc3_dvr_private->bc_ctx_austin;
			ret = release_buf(
				dvr->rm,
				buf_ctx,
				IS_INPUT_DEVICE(dir) || is_need_output_ringbuf);
			if (ret < 0) {
				MTK_DMX_ERR("free austin buffer fail\n");
			}
		}
	}
	return ret;
}

static int mtk_dvr_release(struct inode *inode, struct file *file)
{
	int ret;
	struct dvb_fh *fh = file->private_data;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);

	ret = release_dvr(fh);
	if (ret < 0) {
		MTK_DMX_ERR("release dvr fail\n");
		return -EFAULT;
	}

	mtk_dmx_pm_close(dvr->pm);

	dvb_fh_del(fh);
	dvb_fh_exit(fh);

	kfree(ctx);

	return 0;
}

static ssize_t mtk_dvr_read(
	struct file *file,
	char __user *buf, size_t count,
	loff_t *ppos)
{
	ssize_t ret;
	struct dvb_fh *fh = file->private_data;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	struct mtk_bc_ctx *buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	int non_blocking = file->f_flags & O_NONBLOCK;

	if (!IS_OUTPUT_DEVICE(dir) || !mtk_dvr2_is_need_ringbuf(fh)) {
		MTK_DMX_ERR("not support !!!\n");
		return -EINVAL;
	}

	ret = mtk_bc_ringbuf_read_user(buf_ctx, buf, count, non_blocking);

	return ret;
}

static void mtk_dvr_filein_dmaw_protect(struct dvb_fh *fh, bool enable)
{
#define UBOUND_DEFAULT (0xFFFFFFFF)
#define LBOUND_DEFAULT (0x00000000)

	struct mtk_dvr2_fh_ctx *ctx;
	struct mtk_bc_ctx *buf_ctx;
	struct mtk_dvr2 *dvr;
	int eng;
	u64 paddr;
	u32 size;
	int index;

	if (fh == NULL)
		return;

	ctx = fh_to_dvrctx(fh);
	buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	dvr = fh_to_dvr(fh);

	eng = mtk_dmx_rm_filein_find_engine_by_path(dvr->rm, dvr->path_id);
	if (eng < 0)
		return;

	if (enable) {
		if (mtk_dvr2_is_filein_tee_mode(dvr))
			index = 1;
		else
			index = 0;

		paddr = buf_ctx->d_ctx.mem_info[index].paddr;
		size = buf_ctx->d_ctx.mem_info[index].size;

		mtk_dmx_filein_dmaw_protect(eng, paddr, paddr + size);
	} else {
		mtk_dmx_filein_dmaw_protect(eng, LBOUND_DEFAULT, UBOUND_DEFAULT);
	}

#undef UBOUND_DEFAULT
#undef LBOUND_DEFAULT
}

static ssize_t mtk_dvr_write(
	struct file *file,
	const char __user *buf, size_t count,
	loff_t *ppos)
{
	int ret;
	int i;
	u32 alloc_buf_num = 1;
	u32 buf_size = 0;
	struct dvb_fh *fh = file->private_data;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	struct mtk_bc_ctx *buf_ctx = fh_dvrctx_get_bc_ctx(ctx);
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	struct mmap_buf_info mmap_buf_info[] = {
		{
			.mem_info_type = MEM_INFO_TYPE_NORMAL,
			.mmap_buf_type = MMAP_BUF_TYPE_FILEIN,
			.used_to_init_type = USED_TO_INIT_RINGBUFF,
		},
		{
			.mem_info_type = MEM_INFO_TYPE_CRYPTO,
			.mmap_buf_type = MMAP_BUF_TYPE_AESDMA,
			.used_to_init_type = USED_TO_INIT_NONE,
		},
	};

	struct dvb_ringbuffer *ringbuf = NULL;
	int total_written_size = 0;
	size_t ring_buff_free_size = 0;
	size_t input_size = 0;
	size_t remain_input_size = count;
	int eng_id;

	if (!IS_INPUT_DEVICE(dir)) {
		MTK_DMX_ERR("file access mode fail\n");
		return -EINVAL;
	}

	if (!mtk_bc_ringbuf_is_initialized(buf_ctx)) {
		// mmap_buf_info[1] is not used in non-TEE mode,
		// so use mmap_buf_info[0] instead.

		// tee mode :
		if (mtk_dvr2_is_filein_tee_mode(dvr)) {
			MTK_DMX_DEBUG("apply secure mode buffer setting");
			mmap_buf_info[0].used_to_init_type = USED_TO_INIT_SECURE_RINGBUFF;
			mmap_buf_info[1].used_to_init_type = USED_TO_INIT_RINGBUFF;
			alloc_buf_num = MTK_DMX_NELEMS(mmap_buf_info);
		} else {
			MTK_DMX_DEBUG("apply non secure mode buffer setting");
			alloc_buf_num = 1;
		}

		for (i = 0; i < alloc_buf_num; i++) {
			ret = mtk_bc_get_buf_size(buf_ctx, &buf_size);
			if (ret < 0) {
				MTK_DMX_ERR("get buf size %d fail\n", buf_size);
				return -EFAULT;
			}
			mmap_buf_info[i].buf_size = buf_size;
		}

		buf_ctx->d_ctx.buf_num = alloc_buf_num;
		ret = allocate_buf(
			dvr->rm,
			buf_ctx,
			true,
			alloc_buf_num,
			mmap_buf_info);

		if (ret < 0) {
			MTK_DMX_ERR("allocate buffer fail\n");
			return -EFAULT;
		}

		for (i = 0; i < alloc_buf_num; i++) {
			if (buf_ctx->d_ctx.mem_info[i].size > 0) {
				MTK_DMX_INFO("[FILEIN][alloc_buf]buf_type: %d paddr:%llx,"
					" vaddr:%p, size:%x\n",
					i, buf_ctx->d_ctx.mem_info[i].paddr,
					buf_ctx->d_ctx.mem_info[i].vaddr,
					buf_ctx->d_ctx.mem_info[i].size);
			}
		}

		if (mtk_dvr2_is_filein_tee_mode(dvr)) {
			struct mem_info mem_info = { 0 };

			MTK_DMX_INFO("secure playback, lock buffer\n");
			if (mtk_bc_get_mem_info(buf_ctx, MEM_INFO_TYPE_NORMAL, &mem_info)
				|| mem_info.size == 0) {
				MTK_DMX_ERR("get secure_mem_info fail\n");
				return -EFAULT;
			}

			//invoke DMX_TA:bind pipe, lock buffer
			eng_id = mtk_dmx_rm_filein_find_engine_by_path(dvr->rm, dvr->path_id);
			ret = mtk_dmx_tee_if_play_lock_buffer(eng_id
				, dvr->filein_cfg.pipeline_id, &mem_info);
			if (ret != 0) {
				MTK_DMX_ERR("mtk_dmx_tee_if_play_lock_buffer failed, ret: %d\n"
					, ret);
				return ret;
			}
		}

		mtk_dvr_filein_dmaw_protect(fh, true);
	}

	ringbuf = &buf_ctx->d_ctx.buf.ring;
	MTK_DMX_DEBUG("DMA ring before write: avail(%7lld) free(%7lld), remain_input_size(%llu)",
		(u64)dvb_ringbuffer_avail(ringbuf), (u64)dvb_ringbuffer_free(ringbuf), (u64)remain_input_size);

	ret = mtk_dvr2_prewrite(dvr);
	if (ret) {
		MTK_DMX_ERR("failed to prewrite\n");
		return -EFAULT;
	}

	// O_NONBLOCK : copy as much as possible then return. might return partial success written size
	if (file->f_flags & O_NONBLOCK) {
		// copy from user
		ring_buff_free_size = dvb_ringbuffer_free(ringbuf);
		input_size = (remain_input_size > ring_buff_free_size) ? ring_buff_free_size : remain_input_size;
		total_written_size = mtk_bc_ringbuf_write_user(buf_ctx, buf, input_size);
		MTK_DMX_DEBUG("non-block input(%llu) actually copy_from_user(%d)", (u64)input_size, total_written_size);

		// filein
		ret = mtk_dvr2_write(dvr, true);
		if (ret < 0) {
			MTK_DMX_ERR("write dvr fail\n");
			return -EFAULT;
		}
	}
	else { // BLOCK

		ret = mtk_bc_ringbuf_write_user(buf_ctx, buf, count);
		if (ret != count) {
			MTK_DMX_ERR("write to buffer fail\n");
			return ret;
		}

		ret = mtk_dvr2_write(dvr, false);
		if (ret < 0) {
			MTK_DMX_ERR("write dvr fail\n");
			return -EFAULT;
		}

		MTK_DMX_DEBUG("block input total actually copy_from_user(%lld)", (u64)count);
		total_written_size = count;
	}
	MTK_DMX_DEBUG("DMA ring after write: size(+%5d) avail(%7lld) free(%7lld)"
		,total_written_size, (u64)dvb_ringbuffer_avail(ringbuf), (u64)dvb_ringbuffer_free(ringbuf));

	return total_written_size;
}

static int allocate_pvr_buf(struct dvb_fh *fh)
{
	int ret = 0, ret_austin = 0;
	u32 alloc_buf_num = 1;
	u32 buf_size = 0;
	int i;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	struct mtk_bc_ctx *bc_ctx = NULL;
	struct dvb_dvr2 *dvb_dvr2 = NULL;
	struct mtk_atsc3_dvr_private *mtk_atsc3_dvr_private = NULL;
	struct mtk_demux2 *mtk_demux2 = NULL;
	struct mmap_buf_info mmap_buf_info[] = {
		{
			.mem_info_type = MEM_INFO_TYPE_NORMAL,
			.mmap_buf_type = MMAP_BUF_TYPE_PVR,
			.used_to_init_type = USED_TO_INIT_NONE,
		},
		{
			.mem_info_type = MEM_INFO_TYPE_CRYPTO,
			.mmap_buf_type = MMAP_BUF_TYPE_AESDMA,
			.used_to_init_type = USED_TO_INIT_RINGBUFF,
		},
		{
			.mem_info_type = MEM_INFO_TYPE_NORMAL_FR,
			.mmap_buf_type = MMAP_BUF_TYPE_TLVFR,
			.used_to_init_type = USED_TO_INIT_NONE,
		}
	};

	struct mmap_buf_info mmap_buf_info_austin[] = {
		{
			.mem_info_type = MEM_INFO_TYPE_NORMAL,
			.mmap_buf_type = MMAP_BUF_TYPE_PREPARSER,
			.used_to_init_type = USED_TO_INIT_NONE,
		},
		{
			.mem_info_type = MEM_INFO_TYPE_CRYPTO,
			.mmap_buf_type = MMAP_BUF_TYPE_AESDMA,
			.used_to_init_type = USED_TO_INIT_RINGBUFF,
		},
	};

	if (mtk_bc_get_ctx(fh, &bc_ctx) < 0) {
		MTK_DMX_ERR("get bc ctx fail\n");
		return -EINVAL;
	}

	if (!IS_OUTPUT_DEVICE(dir)) {
		MTK_DMX_DEBUG("not output device\n");
		return 0;
	}
	if (!mtk_dvr2_is_need_ringbuf(fh)) {
		MTK_DMX_DEBUG("no need ringbuf\n");
		return 0;
	}

	if (mtk_dvr2_is_tee_mode(dvr)) {
		if (mtk_dmx_rm_get_hwcap_by_id(dvr->rm, HWCPAS_TYPE_TLV_FR_PATCH_ENABLE))
			alloc_buf_num = MTK_DMX_NELEMS(mmap_buf_info);
		else
			alloc_buf_num = MTK_DMX_NELEMS(mmap_buf_info) - 1;
		MTK_DMX_INFO("[PVR][alloc_buf] tee mode, alloc buf num %d\n", alloc_buf_num);
	} else {
		// mmap_buf_info[1] is not used in non-TEE mode,
		// so use mmap_buf_info[0] instead.
		mmap_buf_info[0].used_to_init_type = USED_TO_INIT_RINGBUFF;
		mmap_buf_info[1].used_to_init_type = USED_TO_INIT_NONE;
		alloc_buf_num = 1;
		MTK_DMX_INFO("[PVR][alloc_buf] non-tee mode, just only alloc buf num 1\n");
	}

	for (i = 0; i < alloc_buf_num; i++) {
		if (mmap_buf_info[i].mmap_buf_type == MMAP_BUF_TYPE_TLVFR)
			buf_size = TLV_FR_SIZE;
		else
			ret = mtk_bc_get_buf_size(bc_ctx, &buf_size);

		if (ret < 0) {
			MTK_DMX_ERR("get buf size %d fail\n", buf_size);
			return -EFAULT;
		}
		mmap_buf_info[i].buf_size = buf_size;
	}

	bc_ctx->d_ctx.buf_num = alloc_buf_num;
	ret = allocate_buf(
		dvr->rm,
		bc_ctx,
		true,
		alloc_buf_num,
		mmap_buf_info);
	if (ret < 0)
		MTK_DMX_ERR("allocate buffer fail\n");

	for (i = 0; i < alloc_buf_num; i++) {
		if (bc_ctx->d_ctx.mem_info[i].size > 0) {
			MTK_DMX_INFO("[PVR][alloc_buf]buf_type: %d paddr:%llx, vaddr:%p, size:%x\n",
				i, bc_ctx->d_ctx.mem_info[i].paddr, bc_ctx->d_ctx.mem_info[i].vaddr,
				bc_ctx->d_ctx.mem_info[i].size);
		}
	}

	mtk_demux2 = fh_to_demux(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) {
			if (mtk_dvr2_is_tee_mode(dvr))
				alloc_buf_num = MTK_DMX_NELEMS(mmap_buf_info_austin);
			else
				alloc_buf_num = 1;

			mtk_atsc3_dvr_private->bc_ctx_austin.d_ctx.buf_size =
								bc_ctx->d_ctx.buf_size;

			for (i = 0; i < alloc_buf_num; i++) {
				ret_austin = mtk_bc_get_buf_size(
					&(mtk_atsc3_dvr_private->bc_ctx_austin), &buf_size);
				if (ret_austin < 0) {
					MTK_DMX_ERR("get buf size %d fail\n", buf_size);
					return -EFAULT;
				}
				mmap_buf_info_austin[i].buf_size = buf_size;
			}

			if (!mtk_dvr2_is_tee_mode(dvr)) {
				mmap_buf_info_austin[0].used_to_init_type = USED_TO_INIT_RINGBUFF;
				mmap_buf_info_austin[1].used_to_init_type = USED_TO_INIT_NONE;
			}

			ret_austin = allocate_buf(
				dvr->rm,
				&(mtk_atsc3_dvr_private->bc_ctx_austin),
				true,
				alloc_buf_num,
				mmap_buf_info_austin);
			if (ret_austin < 0) {
				MTK_DMX_ERR("allocate austin buffer fail\n");
				ret = ret_austin;
			}
		}
	}
	return ret;
}

static int start_dvr(struct dvb_fh *fh)
{
	int ret;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	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);
	bool is_need_output_ringbuf = IS_OUTPUT_DEVICE(dir) && mtk_dvr2_is_need_ringbuf(fh);

	if (!IS_OUTPUT_DEVICE(dir)) {
		MTK_DMX_ERR("not output device\n");
		return -EOPNOTSUPP;
	}

	ret = allocate_pvr_buf(fh);
	if (ret < 0) {
		MTK_DMX_ERR("allocate pvr buf fail\n");
		if (release_buf(
			dvr->rm,
			buf_ctx,
			IS_INPUT_DEVICE(dir) || is_need_output_ringbuf) < 0)
			MTK_DMX_ERR("free buffer fail\n");
		return -EFAULT;
	}

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

	return 0;
}

static int stop_dvr(struct dvb_fh *fh)
{
	int ret;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);
	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_DMX_ERR("not output device\n");
		return -EOPNOTSUPP;
	}

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

	return 0;
}

static int flush_dvr(struct dvb_fh *fh)
{
	int ret;
	struct mtk_dvr2 *dvr = fh_to_dvr(fh);

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

	return 0;
}

static long mtk_dvr_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	struct dvb_fh *fh = file->private_data;
	int non_blocking = file->f_flags & O_NONBLOCK;

	MTK_DMX_DEBUG(
			"cmd = %s\n",
			dmx2_ioctl_2_str(cmd));

	switch (cmd) {
	case DMX2_QUERYCAP:
		ret = query_caps(fh, (struct dmx2_capability *)arg);
		break;
	case DMX2_SET_BUFFER_SIZE:
		ret = set_buf_size(fh, (u32)arg);
		break;
	case DMX2_START:
		ret = start_dvr(fh);
		break;
	case DMX2_STOP:
		ret = stop_dvr(fh);
		break;
	case DMX2_FLUSH:
		ret = flush_dvr(fh);
		break;
	case DMX2_SET_CTRL:
		ret = set_ctrl(fh, (struct dmx2_control *)arg);
		break;
	case DMX2_GET_CTRL:
		ret = get_ctrl(fh, (struct dmx2_control *)arg);
		break;
	case DMX2_SET_CTRLS:
		ret = set_ctrls(fh, (struct dmx2_controls *)arg);
		break;
	case DMX2_GET_CTRLS:
		ret = get_ctrls(fh, (struct dmx2_controls *)arg);
		break;
	case DMX2_QUERYCTRL:
		ret = query_ctrl(fh, (struct dmx2_queryctrl *)arg);
		break;
	case DMX2_DQEVENT:
		ret = dqueue_event(fh, (struct dmx2_event *)arg, non_blocking);
		break;
	case DMX2_SUBSCRIBE_EVENT:
		ret = subscribe_event(fh, (const struct dmx2_event_subscription *)arg);
		break;
	case DMX2_UNSUBSCRIBE_EVENT:
		ret = unsubscribe_event(fh, (const struct dmx2_event_subscription *)arg);
		break;
	case DMX2_REQBUFS:
		ret = request_bufs(fh, (struct dmx2_requestbuffers *)arg, non_blocking);
		break;
	case DMX2_QUERYBUF:
		ret = query_buf(fh, (struct dmx2_buffer *)arg);
		break;
	case DMX2_EXPBUF:
		ret = export_buf(fh, (struct dmx2_exportbuffer *)arg);
		break;
	case DMX2_QBUF:
		ret = queue_buf(fh, (struct dmx2_buffer *)arg);
		break;
	case DMX2_DQBUF:
		ret = dequeue_buf(fh, (struct dmx2_buffer *)arg);
		break;
	default:
		return -EINVAL;
	}

	return ret;
}

static __poll_t mtk_dvr_poll(struct file *file, poll_table *wait)
{
	struct dvb_fh *fh = file->private_data;
	struct dmx2dev *dmx2dev = fh_to_dmx2dev(fh);
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	struct mtk_bc_ctx *bc_ctx = fh_dvrctx_get_bc_ctx(ctx);
	__poll_t mask = 0;

	if (dmx2dev->exit)
		return -EPOLLERR;

	if (!IS_OUTPUT_DEVICE(dir)) {
		MTK_DMX_ERR("file access mode fail\n");
		return -EINVAL;
	}

	if (mtk_bc_vb2_is_streaming(bc_ctx))
		mask = mtk_bc_vb2_poll(bc_ctx, file, wait);
	else
		mask = mtk_bc_ringbuf_poll(bc_ctx, file, wait);

	return mask;
}

#ifdef CONFIG_DVB_MMAP
static int mtk_dvr_mmap(struct file *file, struct vm_area_struct *vma)
{
	int ret;
	struct dvb_fh *fh = file->private_data;
	struct mtk_dvr2_fh_ctx *ctx = fh_to_dvrctx(fh);
	enum dvr_data_direction dir = fh_dvrctx_get_dir(ctx);
	struct mtk_bc_ctx *bc_ctx = fh_dvrctx_get_bc_ctx(ctx);

	if (!IS_OUTPUT_DEVICE(dir)) {
		MTK_DMX_ERR("file access mode fail\n");
		return -EINVAL;
	}

	ret = mtk_bc_vb2_mmap(bc_ctx, vma);
	if (ret < 0) {
		MTK_DMX_ERR("mmap fail\n");
		return -EFAULT;
	}

	return 0;
}
#endif

static const struct file_operations mtk_dvr_fops = {
	.owner			= THIS_MODULE,
	.open			= mtk_dvr_open,
	.release		= mtk_dvr_release,
	.read			= mtk_dvr_read,
	.write			= mtk_dvr_write,
	.unlocked_ioctl	= mtk_dvr_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl	= mtk_dvr_ioctl,
#endif
	.poll			= mtk_dvr_poll,
#ifdef CONFIG_DVB_MMAP
	.mmap			= mtk_dvr_mmap,
#endif
};

static inline const char *codec_type_descr(u32 fourCC)
{
	const char *descr = NULL;

	switch (fourCC) {
	case DMX2_CODEC_VIDEO_MPEG:
		descr = "MPEG";
		break;
	case DMX2_CODEC_VIDEO_HEVC:
		descr = "HEVC";
		break;
	case DMX2_CODEC_VIDEO_H264:
		descr = "H264";
		break;
	case DMX2_CODEC_VIDEO_AVS:
		descr = "AVS0";
		break;
	case DMX2_CODEC_VIDEO_AVS2:
		descr = "AVS2";
		break;
	case DMX2_CODEC_VIDEO_AVS3:
		descr = "AVS3";
		break;
	default:
		break;
	}

	return descr;
}

static int g_ctrl_ops(struct dvb_ctrl *ctrl)
{
	int ret = 0;
	struct dvb_fh *fh = ctrl->priv;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		switch (ctrl->id) {
		case DMX2_CID_GET_METADATA:
			{
				void *arg = ctrl->p_new.p;

				MTK_DMX_DEBUG("**** DMX2_CID_GET_METADATA ****\n");
				ret = mtk_demux2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_DQBUF_DMA:
			{
				void *arg = static_cast(struct dmx2_buffer *, ctrl->p_new.p);

				MTK_DMX_DEBUG("**** DMX2_CID_DQBUF_DMA ****\n");
				ret = mtk_demux2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_GET_HW_FILTER_ID:
		case DMX2_CID_GET_FIFO_ID:
		case DMX2_CID_GET_STC_ID:
		case DMX2_CID_GET_DMABUF_DATA:
			{
				void *arg = static_cast(void *, ctrl->p_new.p_u32);

				MTK_DMX_DEBUG("**** %s ****\n", ctrl->name);
				ret = mtk_demux2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_GET_REMAINING_SIZE:
			{
				void *arg = static_cast(void *, ctrl->p_new.p_u32);

				MTK_DMX_DEBUG("**** %s ****\n", ctrl->name);
				ret = mtk_demux2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		default:
			ret = -EINVAL;
			break;
		}
	} else if (fh->type == DVB_DEVICE_DVR2) {
		switch (ctrl->id) {
		case DMX2_CID_GET_METADATA:
			{
				void *arg = ctrl->p_new.p;

				MTK_DMX_DEBUG("**** DMX2_CID_GET_METADATA ****\n");
				ret = mtk_dvr2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_GET_REMAINING_SIZE:
			{
				void *arg = ctrl->p_new.p_u32;
				MTK_DMX_DEBUG("**** FILEIN DMX2_CID_GET_REMAINING_SIZE ****\n");
				ret = mtk_dvr2_get_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		default:
			ret = -EINVAL;
			break;
		}
	} else {
		MTK_DMX_ERR("not support device type\n");
		ret = -EINVAL;
	}

	return ret;
}

static int s_ctrl_ops(struct dvb_ctrl *ctrl)
{
	int ret;
	struct dvb_fh *fh = ctrl->priv;

	if (fh->type == DVB_DEVICE_DEMUX2) {
		switch (ctrl->id) {
		case DMX2_CID_SET_STC:
			{
				struct dmx2_stc *stc =
					static_cast(struct dmx2_stc *, ctrl->p_new.p);
				void *arg = static_cast(void *, stc);

				MTK_DMX_DEBUG("**** DMX2_CID_SET_STC ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("base = %d\n", stc->base);
				MTK_DMX_DEBUG("stc = 0x%llx\n", stc->stc);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_demux2_set_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_RECORD_PARSER:
			{
				struct dmx2_record_parser_params *param =
					static_cast(struct dmx2_record_parser_params *,
							ctrl->p_new.p);
				void *arg = static_cast(void *, param);

				MTK_DMX_DEBUG("**** DMX2_CID_RECORD_PARSER ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("codec_type = %s\n", codec_type_descr(param->codec_type));
				MTK_DMX_DEBUG("sc_type = %x\n", param->sc_type);
				MTK_DMX_DEBUG("sc_index = %x\n", param->sc_index);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_demux2_set_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_SET_PID_SECURE:
			{

				u32 *param = static_cast(u32 *,	ctrl->p_new.p);
				void *arg = static_cast(void *, param);
				MTK_DMX_DEBUG("**** DMX2_CID_SET_PID_SECURE ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_demux2_set_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		case DMX2_CID_QBUF_DMA:
			{
				struct dmx2_buffer *buf =
					static_cast(struct dmx2_buffer *, ctrl->p_new.p);
				void *arg = static_cast(void *, buf);

				MTK_DMX_DEBUG("**** DMX2_CID_QBUF_DMA ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("bytesused = 0x%x\n", buf->bytesused);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_demux2_set_ext_ctrl(fh, ctrl->id, arg);
			}
			break;
		default:
			ret = -EINVAL;
			break;
		}
	} else if (fh->type == DVB_DEVICE_DVR2) { //for DVR2 device
		switch (ctrl->id) {
		case DMX2_CID_SET_SOURCE:
			{
				struct dmx2_source_settings *src_settings =
					static_cast(struct dmx2_source_settings *, ctrl->p_new.p);
				struct mtk_dvr2 *dvr2 = fh_to_dvr(fh);
				void *arg = static_cast(void *, src_settings);

				MTK_DMX_DEBUG("**** DMX2_CID_SET_SOURCE ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("input_type = %x\n", src_settings->input_type);
				MTK_DMX_DEBUG("port_type = %x\n", src_settings->port_type);
				MTK_DMX_DEBUG("port_num = %x\n", src_settings->port_num);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_dvr2_set_ext_ctrl(dvr2, ctrl->id, arg);
			}
			break;
		case DMX2_CID_PLAYBACK_SETTING:
			{
				struct dvr2_playback_settings *cfg =
					static_cast(struct dvr2_playback_settings *, ctrl->p_new.p);
				struct mtk_dvr2 *dvr2 = fh_to_dvr(fh);
				void *arg = static_cast(void *, cfg);

				MTK_DMX_DEBUG("**** DMX2_CID_PLAYBACK_SETTING ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("format = %x\n", cfg->format);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_dvr2_set_ext_ctrl(dvr2, ctrl->id, arg);
			}
			break;
		case DMX2_CID_RECORD_SETTINGS:
			{
				struct dvr2_record_settings *cfg =
					static_cast(struct dvr2_record_settings *, ctrl->p_new.p);
				struct mtk_dvr2 *dvr2 = fh_to_dvr(fh);
				void *arg = static_cast(void *, cfg);

				MTK_DMX_DEBUG("**** DMX2_CID_RECORD_SETTINGS ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("record_all = %x\n", cfg->rec_all_input);
				MTK_DMX_DEBUG("format = %x\n", cfg->format);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_dvr2_set_ext_ctrl(dvr2, ctrl->id, arg);
			}
			break;
		case DMX2_CID_RECORD_PAUSE:
			{
				u32 *cfg = static_cast(u32 *, ctrl->p_new.p_u32);
				struct mtk_dvr2 *dvr2 = fh_to_dvr(fh);
				void *arg = static_cast(void *, cfg);
				MTK_DMX_DEBUG("**** DMX2_CID_RECORD_PAUSE ****\n");
				MTK_DMX_DEBUG("###############################################\n");
				MTK_DMX_DEBUG("pause = %d\n", *cfg);
				MTK_DMX_DEBUG("###############################################\n");
				ret = mtk_dvr2_set_ext_ctrl(dvr2, ctrl->id, arg);
			}
			break;
		default:
			ret = -EINVAL;
			break;
		}
	} else {
		MTK_DMX_ERR("not support device type\n");
		ret = -EINVAL;
	}

	return ret;
}

static const struct dvb_ctrl_ops dmx_ctrl_ops = {
	.g_ctrl = g_ctrl_ops,
	.s_ctrl = s_ctrl_ops,
};

static const struct dvb_ctrl_type_ops dmx_ctrl_type_ops = {
	.equal = NULL,
	.init = NULL,
	.validate = NULL,
};

static const struct dvb_ctrl_cfg dmx_ctrls[] = {
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_METADATA,
		.name = "GET_METADATA",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = MTK_BC_METADATA_BUF_SIZE,
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_HW_FILTER_ID,
		.name = "GET_HW_FILTER_ID",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 0,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_FIFO_ID,
		.name = "GET_FIFO_ID",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 255,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_STC_ID,
		.name = "GET_STC_ID",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 255,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_PRESET_IP_CID,
		.name = "PRESET_IP_CID",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 1,
		.step = 1,
		.def = 0,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_SET_SOURCE,
		.name = "SET_SOURCE",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dmx2_source_settings),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_PLAYBACK_SETTING,
		.name = "SET_FILE_IN__PLAY_CFG",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dvr2_playback_settings),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_SET_STC,
		.name = "SET_STC",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dmx2_stc),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_RECORD_SETTINGS,
		.name = "RECORD_SETTINGS",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dvr2_record_settings),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_RECORD_PARSER,
		.name = "RECORD_SETTINGS",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dmx2_record_parser_params),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_REMAINING_SIZE,
		.name = "DVR_DMX_REMAINING_DATA_SIZE",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 0,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_SET_PID_SECURE,
		.name = "SET_PID_SECURE",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(bool),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_RECORD_PAUSE,
		.name = "DVR_RECORD_PAUSE_RESUME",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 0,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_GET_DMABUF_DATA,
		.name = "GET_DMBUF_METADATA",
		.type = DVB_CTRL_TYPE_UNSIGNED_NUM,
		.min = 0,
		.max = 255,
		.step = 1,
		.def = 0,
		.elem_size = sizeof(u32),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_QBUF_DMA,
		.name = "QBUF_DMA",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dmx2_buffer),
		.flags = DMX2_CTRL_FLAG_WRITE_ONLY,
	},
	{
		.ops = &dmx_ctrl_ops,
		.type_ops = &dmx_ctrl_type_ops,
		.id = DMX2_CID_DQBUF_DMA,
		.name = "DQBUF_DMA",
		.type = DVB_CTRL_TYPE_PTR,
		.elem_size = sizeof(struct dmx2_buffer),
		.flags = DMX2_CTRL_FLAG_READ_ONLY,
	},
};

static int init_ctrl_hdl(struct dvb_ctrl_handler *hdl)
{
	int i;
	int ctrl_cnt = sizeof(dmx_ctrls)/sizeof(struct dvb_ctrl_cfg);

	dvb_ctrl_handler_init(hdl, ctrl_cnt);
	if (hdl->error < 0) {
		MTK_DMX_ERR("init ctrl hdl fail\n");
		return -EFAULT;
	}

	for (i = 0; i < ctrl_cnt; i++) {
		dvb_ctrl_new_item(hdl, &dmx_ctrls[i]);
		if (hdl->error < 0) {
			MTK_DMX_ERR("add ctrl(%s) fail\n", dmx_ctrls[i].name);
			goto fail;
		}
	}

	return 0;

fail:
	dvb_ctrl_handler_free(hdl);
	return -EFAULT;
}

static inline int release_ctrl_hdl(struct dvb_ctrl_handler *hdl)
{
	dvb_ctrl_handler_free(hdl);

	return hdl->error;
}

static int mtk_dmx_notifier_link_notify(struct dvb_device *dev,
	struct dvbdev_link *link)
{
	struct media_entity *local = dev->entity;
	struct media_entity *remote = link->remote_entity;
	struct dmx2dev *dmx2dev = dev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	bool is_ci = false;
	bool is_cifpga = false;
	struct mtk_demux2_notify_args *args = NULL;

	args = kzalloc(sizeof(struct mtk_demux2_notify_args),
		GFP_KERNEL);
	if (args) {
		args->path = path;
		args->local = local;
		args->remote = remote;
		args->state = link->state;
	} else
		return -ENOMEM;

	if (is_ci_device(remote))
		is_ci = true;

	if (is_cifpga_device(remote))
		is_cifpga = true;

	if (link->state == LINK_STATE_CONNECT) {
		path->cicam = (is_ci) ? true : false;
		path->cifpga = (is_cifpga) ? true : false;
		mtk_dmx_pm_open(path->demux2.pm);
		path->demux2.media_ops->notify(args);
	} else if (link->state == LINK_STATE_DISCONNECT) {
		path->cicam = false;
		path->cifpga = false;
		path->demux2.media_ops->notify(args);
		mtk_dmx_pm_close(path->demux2.pm);
	}
	kfree(args);

	MTK_DMX_INFO("local(%s) -- remote(%s) state: %s cicam: %d cifpga: %d\n",
		local->name, remote->name,
		(link->state == LINK_STATE_DISCONNECT) ? "Disconnect" : "Connect",
		path->cicam, path->cifpga);

	return 0;
}

static int mtk_dmx_notifier_query_notify(struct dvb_device *dev,
	struct dvbdev_properties *prop)
{
	struct dmx2dev *dmx2dev = dev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	struct mtk_demux2_query_args *args = NULL;
	int ret;

	args = kzalloc(sizeof(struct mtk_demux2_query_args),
		GFP_KERNEL);
	if (args) {
		args->path = path;
		args->prop = prop;
	} else
		return -ENOMEM;
	ret = path->demux2.media_ops->query(args);
	kfree(args);
	return ret;
}

static int mtk_dmx_notifier_streaming_event(struct dvb_device *dev,
	enum cfm_notify_event_type_t ev_type,
	struct cfm_properties *props, void *notify_data)
{
	struct dmx2dev *dmx2dev = dev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	struct dvb_frontend *fe = (struct dvb_frontend *)notify_data;
	struct mtk_demux2_streaming_args *args = NULL;
	struct media_pad *pad = mtk_mc_remote_pad(&dev->entity->pads[1]);

	if (PTR_ERR(path->fe_prev) == -EFAULT)
		path->fe_prev = fe;
	else {
		if ((fe == NULL) && (fe == path->fe_prev) && !pad)
			return 0;
		else
			path->fe_prev = fe;
	}

	MTK_DMX_INFO("path: %d evt: %d cicam: %d cifpga: %d\n",
		path->path_id, ev_type, path->cicam, path->cifpga);

	args = kzalloc(sizeof(struct mtk_demux2_streaming_args),
		GFP_KERNEL);
	if (args) {
		args->path = path;
		args->fe = fe;
		args->props = props;
		args->dvbdev = dev;
		path->demux2.media_ops->streaming(args);
		kfree(args);
	} else
		return -ENOMEM;
	return 0;
}

static int mtk_dmx_notifier_data_ready(struct dvb_device *dev,
	void *addr, u32 size, void *notify_data)
{
	struct dmx2dev *dmx2dev = dev->priv;
	struct mtk_dmx_path *path = dmx2dev_to_path(dmx2dev);
	struct mtk_demux2_data_ready_args *args = NULL;

	args = kzalloc(sizeof(struct mtk_demux2_data_ready_args),
		GFP_KERNEL);
	if (args) {
		args->path = path;
		args->addr = addr;
		args->size = size;
		args->data = notify_data;
	} else
		return -ENOMEM;
	path->demux2.media_ops->data_ready(args);
	kfree(args);
	return 0;
}

static int init_dmxdev(struct mtk_dmx2dev *dmxdev, struct mtk_dmx_path *path, u16 path_id)
{
	int ret;
	struct dvbdev_notifier *notifier = NULL;

	path->path_id = path_id;
	path->state = DMX_PATH_STATE_IDLE;
	path->fe_prev = (void *)(-EFAULT);
	path->dmx2dev.dmx_fops = &mtk_dmx_fops;
	path->dmx2dev.dvr_fops = &mtk_dvr_fops;
	ret = dvb_dmx2dev_init(&path->dmx2dev, dmxdev->adapter);
	if (ret < 0) {
		MTK_DMX_ERR("init dmx2dev fail\n");
		return -EFAULT;
	}

	ret = init_ctrl_hdl(&path->dmx2dev.ctrl_hdl);
	if (ret < 0) {
		MTK_DMX_ERR("ctrl hdl init fail\n");
		return -EFAULT;
	}

	path->demux2.path_id = path_id;
	path->demux2.path = path;
	path->demux2.rm = &dmxdev->rm;
	path->demux2.pm = &dmxdev->pm;
	path->demux2.tm = &dmxdev->tm;
	ret = mtk_demux2_init(&path->demux2);
	if (ret < 0) {
		MTK_DMX_ERR("init demux2 fail\n");
		return -EFAULT;
	}

	path->dvr_record.path_id = path_id;
	path->dvr_record.path = path;
	path->dvr_record.rm = &dmxdev->rm;
	path->dvr_record.pm = &dmxdev->pm;
	ret = mtk_dvr2_init(&path->dvr_record);
	if (ret < 0) {
		MTK_DMX_ERR("init dvr_record fail\n");
		return -EFAULT;
	}

	path->dvr_playback.path_id = path_id;
	path->dvr_playback.path = path;
	path->dvr_playback.rm = &dmxdev->rm;
	path->dvr_playback.pm = &dmxdev->pm;
	ret = mtk_dvr2_init(&path->dvr_playback);
	if (ret < 0) {
		MTK_DMX_ERR("init dvr_playback fail\n");
		return -EFAULT;
	}

	notifier = devm_kzalloc(dmxdev->dev, sizeof(struct dvbdev_notifier), GFP_KERNEL);
	if (notifier) {
		notifier->link_notify = &mtk_dmx_notifier_link_notify;
		notifier->query_notify = &mtk_dmx_notifier_query_notify;
		notifier->streaming_event_notify = &mtk_dmx_notifier_streaming_event;
		notifier->data_ready_notify = &mtk_dmx_notifier_data_ready;
		ret = mdrv_adapter_register_device_notifier(path->dmx2dev.dvbdev, notifier);
		if (ret < 0)
			MTK_DMX_ERR("register notifier fail\n");
	} else
		MTK_DMX_ERR("fail to create notifier\n");

	return 0;
}

static int release_dmxdev(struct mtk_dmx_path *path)
{
	int ret;

	ret = mtk_demux2_exit(&path->demux2);
	if (ret < 0) {
		MTK_DMX_ERR("exit demux2 fail\n");
		return -EFAULT;
	}

	ret = mtk_dvr2_exit(&path->dvr_record);
	if (ret < 0) {
		MTK_DMX_ERR("exit dvr_record fail\n");
		return -EFAULT;
	}

	ret = mtk_dvr2_exit(&path->dvr_playback);
	if (ret < 0) {
		MTK_DMX_ERR("exit dvr_playback fail\n");
		return -EFAULT;
	}

	ret = release_ctrl_hdl(&path->dmx2dev.ctrl_hdl);
	if (ret < 0) {
		MTK_DMX_ERR("ctrl hdl release fail\n");
		return -EFAULT;
	}

	dvb_dmx2dev_exit(&path->dmx2dev);

	return 0;
}

static bool is_suspend;

static int path_id_range_init(struct mtk_dmx2dev *dmxdev)
{
	int ret;
	u16 ts_num;
	u16 tlv_num;
	u16 alp_num;

	if (!dmxdev || !dmxdev->dev) {
		MTK_DMX_ERR("dev is NULL\n");
		return -EINVAL;
	}

	ts_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_LIVEIN);
	tlv_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_TLV);
	alp_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_ALP);

	ret = mtk_dmx_dev_set_path_id_range(
		PATH_ID_TYPE_M2TS,
		0,
		ts_num - 1);
	if (ret < 0)
		MTK_DMX_DEBUG("mtk_dmx_dev_set_path_id_range(PATH_ID_TYPE_M2TS, ...) fail\n");
	ret = mtk_dmx_dev_set_path_id_range(
		PATH_ID_TYPE_ISDB3,
		ts_num,
		ts_num + tlv_num - 1);
	if (ret < 0)
		MTK_DMX_DEBUG("mtk_dmx_dev_set_path_id_range(PATH_ID_TYPE_ISDB3, ...) fail\n");
	ret = mtk_dmx_dev_set_path_id_range(
		PATH_ID_TYPE_ATSC3,
		ts_num + tlv_num,
		ts_num + tlv_num + alp_num - 1);
	if (ret < 0)
		MTK_DMX_DEBUG("mtk_dmx_dev_set_path_id_range(PATH_ID_TYPE_ATSC3, ...) fail\n");

	return 0;
}
int mtk_dmx2dev_notify(void)
{
	return video_parser_print_bank();
}

int mtk_dmx2dev_init(struct mtk_dmx2dev *dmxdev)
{
	int i, ret;
	u16 ts_num, tlv_num, alp_num;
	struct device_node *np;
	u32 rpm_en = 0;

	if (!dmxdev || !dmxdev->dev) {
		MTK_DMX_ERR("dev is NULL\n");
		return -EINVAL;
	}

	/* @TODO: rename file */
	dmxdev->adapter = mdrv_get_dvb_adapter();
	if (!dmxdev->adapter) {
		MTK_DMX_ERR("get adapter fail\n");
		return -EFAULT;
	}

	dmxdev->rm.pvr_reset_threshold = MTK_BC_PVR_WATER_LEVEL_THRESHOLD;
	dmxdev->rm.fiq_rush_en = true;
	dmxdev->rm.fiq_rush_path = -1;
	dmxdev->rm.fiq_pre_rush_path = -1;
	ret = mtk_dmx_rm_init(&dmxdev->rm);
	if (ret < 0) {
		MTK_DMX_ERR("resource manager init fail\n");
		return -EFAULT;
	}

	np = of_find_node_by_name(dmxdev->dev->of_node, "swcaps");
	if (np != NULL) {
		ret = of_property_read_u32(np, "rpm_en", &rpm_en);
		if (ret)
			MTK_DMX_ERR("Failed to get swcaps: rpm_en\n");
	}
	of_node_put(np);

	dmxdev->pm.bRpmEn = (rpm_en == 1) ? true : false;
	ret = mtk_dmx_pm_init(&dmxdev->pm, true);
	if (ret < 0) {
		MTK_DMX_ERR("power manager init fail\n");
		return -EFAULT;
	}

	dmxdev->tm.rm = &dmxdev->rm;
	ret = mtk_dmx_tm_init(dmxdev->dev, &dmxdev->tm);
	if (ret < 0) {
		MTK_DMX_ERR("init mtk_dmx_tm fail\n");
		return -EFAULT;
	}

	ts_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_LIVEIN);
	tlv_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_TLV);
	alp_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_ALP);
	ret = path_id_range_init(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init path_id_range_init fail\n");
		return -EFAULT;
	}

	dmxdev->paths = devm_kcalloc(dmxdev->dev,
		(ts_num + tlv_num + alp_num),
		sizeof(struct mtk_dmx_path), GFP_KERNEL);
	if (!dmxdev->paths) {
		MTK_DMX_ERR("allocate paths fail\n");
		return -ENOMEM;
	}

	for (i = 0; i < (ts_num + tlv_num + alp_num); i++) {
		ret = init_dmxdev(dmxdev, &dmxdev->paths[i], i);
		if (ret < 0) {
			MTK_DMX_ERR("init dmxdev fail\n");
			goto fail;
		}
	}

	ret = mtk_dmx_tsodev_init(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init tsodev fail\n");
		goto fail;
	}

	ret = mtk_dmx_sysfs_init(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init sysfs fail\n");
		goto fail;
	}
	ret = mtk_dtv_parser_create_sysfs_attr(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init parser sysfs fail\n");
		goto fail;
	}

	ret = mtk_dtv_mad_parser_create_sysfs_attr(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init parser sysfs fail\n");
		goto fail;
	}

	ret = mtk_dmx_stc_dev_init(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("init stc device fail\n");
		goto fail;
	}

	gDev = dmxdev;

	is_suspend = false;
	return 0;

fail:
	for (i = i - 1; i >= 0; i--)
		release_dmxdev(&dmxdev->paths[i]);

	return ret;
}

int mtk_dmx2dev_exit(struct mtk_dmx2dev *dmxdev)
{
	int i, ret;
	u16 ts_num, tlv_num, alp_num;

	if (!dmxdev) {
		MTK_DMX_ERR("dev is NULL\n");
		return -EINVAL;
	}

	ts_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_LIVEIN);
	tlv_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_TLV);
	alp_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_ALP);

	for (i = 0; i < (ts_num + tlv_num + alp_num); i++)
		release_dmxdev(&dmxdev->paths[i]);

	ret = mtk_dmx_rm_exit(&dmxdev->rm);
	if (ret < 0) {
		MTK_DMX_ERR("resource manager exit fail\n");
		return -EFAULT;
	}

	ret = mtk_dmx_pm_exit(&dmxdev->pm);
	if (ret < 0) {
		MTK_DMX_ERR("clock manager exit fail\n");
		return -EFAULT;
	}

	ret = mtk_dmx_tm_exit();
	if (ret < 0) {
		MTK_DMX_ERR("exit mtk_dmx_tm fail\n");
		return -EFAULT;
	}

	ret = mtk_dmx_sysfs_exit(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("exit sysfs fail\n");
		return -EFAULT;
	}
	ret = mtk_dtv_parser_remove_sysfs_attr(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("exit parser sysfs fail\n");
		return -EFAULT;
	}

	ret = mtk_dtv_mad_parser_remove_sysfs_attr(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("exit parser sysfs fail\n");
		return -EFAULT;
	}

	ret = mtk_dmx_stc_dev_exit(dmxdev);
	if (ret < 0) {
		MTK_DMX_ERR("exit stc device fail\n");
		return -EFAULT;
	}

	gDev = NULL;

	return 0;
}

int mtk_dmx2dev_suspend(bool suspend, struct mtk_dmx2dev *dmxdev)
{
	int ret_suspend = 0, ret_resume = 0;
	u16 alp_num;

	if (suspend == false)
		goto RESUME;
	disable_irq((unsigned int)dmxdev->tm.irq);
	ret_suspend |= reg_save(REG_BASE_TS_SAMPLE_MIN);
	if (ret_suspend)
		goto IRQ_EN;
	MTK_DMX_SUSPEND(dmxdev, "regs save ok\n");
	ret_suspend = mtk_dmx_io_suspend(true, dmxdev);
	if (ret_suspend < 0)
		goto IRQ_EN;
	MTK_DMX_SUSPEND(dmxdev, "io suspend ok\n");
	ret_suspend = mtk_dmx_filter_suspend(true, dmxdev);
	if (ret_suspend < 0)
		goto WAKE_IO;
	MTK_DMX_SUSPEND(dmxdev, "filter suspend ok\n");
	ret_suspend = mtk_pvr_suspend(true, dmxdev);
	if (ret_suspend < 0)
		goto WAKE_FLT;
	MTK_DMX_SUSPEND(dmxdev, "pvr suspend ok\n");
	ret_suspend = mtk_dmx_pm_suspend(true, dmxdev);
	if (ret_suspend < 0)
		goto WAKE_PVR;
	MTK_DMX_SUSPEND(dmxdev, "pm suspend ok\n");
	is_suspend = true;
	goto DONE;
RESUME:
	if (is_suspend == false) {
		ret_resume = -EFAULT;
		goto DONE;
	}
	ret_resume = mtk_dmx_pm_init(&dmxdev->pm, false);
	if (ret_resume < 0) {
		MTK_DMX_ERR("pm init fail\n");
		goto DONE;
	}
	MTK_DMX_SUSPEND(dmxdev, "pm init ok\n");
	ret_resume |= mtk_dmx_pm_suspend(false, dmxdev);
	MTK_DMX_SUSPEND(dmxdev, "pm resume: %d\n", ret_resume);
WAKE_PVR:
	ret_resume |= mtk_pvr_suspend(false, dmxdev);
	MTK_DMX_SUSPEND(dmxdev, "pvr resume: %d\n", ret_resume);
WAKE_FLT:
	ret_resume |= mtk_dmx_filter_suspend(false, dmxdev);
	MTK_DMX_SUSPEND(dmxdev, "filter resume: %d\n", ret_resume);
WAKE_IO:
	ret_resume |= mtk_dmx_io_suspend(false, dmxdev);
	MTK_DMX_SUSPEND(dmxdev, "io resume: %d\n", ret_resume);
	if (ret_suspend == 0)
		reg_restore(REG_BASE_TS_SAMPLE_MIN);
IRQ_EN:
	enable_irq((unsigned int)dmxdev->tm.irq);
	is_suspend = false;
DONE:

	alp_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_ALP);
	mtk_dmx_alp_init(DEMUX_ALP_INSTANCE_0);
	if (alp_num > 1)
		mtk_dmx_alp_init(DEMUX_ALP_INSTANCE_1);

	ret_suspend = (ret_suspend || ret_resume) ? -EFAULT : 0;
	if (ret_suspend)
		MTK_DMX_ERR("%s : fail\n", (suspend) ? "suspend" : "resume");
	else
		MTK_DMX_INFO("%s : ok\n", (suspend) ? "suspend" : "resume");
	return ret_suspend;
}

// TODO: Refactor it to another module
int mtk_dvr2_fh_ctx_init(
		struct mtk_dvr2_fh_ctx *ctx,
		struct mtk_dvr2 *dvr,
		enum dvr_data_direction dir,
		u32 size)
{
	int ret;
	struct mtk_dmx_path *path = NULL;
	struct dmx2dev *dmx2dev = NULL;
	struct dvb_fh *fh = NULL;

	if (!ctx) {
		MTK_DMX_ERR("ctx is NULL\n");
		return -EINVAL;
	}
	if (!dvr) {
		MTK_DMX_ERR("dvr is NULL\n");
		return -EINVAL;
	}
	path = dvr2_to_path(dvr);
	dmx2dev = &path->dmx2dev;
	fh = &ctx->fh;

	fh_dvrctx_init(ctx, dvr);
	fh_dvrctx_set_dir(ctx, dir);

	dvb_fh_init(&ctx->fh, DVB_DEVICE_DVR2, &dmx2dev->fh_mgr, &dmx2dev->ctrl_hdl);
	dvb_fh_add(&ctx->fh);

	ret = init_dvr(fh, &size);
	if (ret < 0) {
		MTK_DMX_ERR("init dvr fail\n");
		dvb_fh_del(fh);
		dvb_fh_exit(fh);
		return -EFAULT;
	}

	if (dir == DVR_DATA_FROM_DEVICE) {
		ret = allocate_pvr_buf(fh);
		if (ret < 0) {
			MTK_DMX_ERR("allocate pvr buf fail\n");
			ret = mtk_dvr2_fh_ctx_exit(ctx);
			return -EFAULT;
		}
	} else if (dir == DVR_DATA_TO_DEVICE) {
		struct mtk_bc_ctx *bc_ctx = NULL;
		struct mmap_buf_info mmap_buf_info[] = {
			{
				.mem_info_type = MEM_INFO_TYPE_NORMAL,
				// for usb cam, use tsio filein buf
				.mmap_buf_type = MMAP_BUF_TYPE_TSO_FILEIN,
				.used_to_init_type = USED_TO_INIT_RINGBUFF,
				.buf_size = size,
			},
		};

		if (mtk_bc_get_ctx(fh, &bc_ctx) < 0) {
			MTK_DMX_ERR("get bc ctx fail\n");
			return -EINVAL;
		}

		ret = allocate_buf(
			dvr->rm,
			bc_ctx,
			true,
			1,
			mmap_buf_info);

		if (ret < 0) {
			MTK_DMX_ERR("allocate filin buf fail\n");
			ret = mtk_dvr2_fh_ctx_exit(ctx);
			return -EFAULT;
		}
	}

	return 0;
}

// TODO: Refactor it to another module
int mtk_dvr2_fh_ctx_exit(struct mtk_dvr2_fh_ctx *ctx)
{
	int ret;
	struct dvb_fh *fh = NULL;

	if (!ctx) {
		MTK_DMX_ERR("ctx is NULL\n");
		return -EINVAL;
	}
	fh = fh_dvrctx_get_fh(ctx);

	ret = release_dvr(fh);
	if (ret < 0) {
		MTK_DMX_ERR("release dvr fail\n");
		return -EFAULT;
	}

	dvb_fh_del(fh);
	dvb_fh_exit(fh);

	return 0;
}

bool mtk_dmx_dev_path_id_in_range(
	enum path_id_type path_id_type,
	u16 path_id)
{
	if (DEV_CHECK_RANGE(
		PATH_ID_TYPE_MIN,
		PATH_ID_TYPE_MAX - 1,
		path_id_type)) {
		return false;
	}

	if (path_id == PATH_ID_INVALID)
		return false;

	if (path_id_ranges[path_id_type][PATH_ID_RANGE_START] > path_id)
		return false;
	else if (path_id_ranges[path_id_type][PATH_ID_RANGE_END] < path_id)
		return false;

	return true;
}

int mtk_dmx_dev_set_path_id_range(
	enum path_id_type path_id_type,
	u16 start, u16 end)
{
	int iRet = MTK_DMX_SUCCESS;

	if (DEV_CHECK_RANGE(
		PATH_ID_TYPE_MIN,
		PATH_ID_TYPE_MAX - 1,
		path_id_type)) {
		return MTK_DMX_INVALID_PARAMETER;
	}
	if (start == PATH_ID_INVALID) {
		MTK_DMX_DEBUG("start == PATH_ID_INVALID\n");
		return MTK_DMX_INVALID_PARAMETER;
	}
	if (end == PATH_ID_INVALID) {
		MTK_DMX_DEBUG("end == PATH_ID_INVALID\n");
		return MTK_DMX_INVALID_PARAMETER;
	}
	if (start > end) {
		MTK_DMX_DEBUG("start %u > end %u\n", start, end);
		return MTK_DMX_INVALID_PARAMETER;
	}

	path_id_ranges[path_id_type][PATH_ID_RANGE_START] = start;
	path_id_ranges[path_id_type][PATH_ID_RANGE_END] = end;
	MTK_DMX_INFO("path_id_ranges[%d] = [%u, %u]\n", path_id_type, start, end);

	return iRet;
}

u16 mtk_dmx_dev_get_path_id_range(
	enum path_id_type path_id_type,
	enum path_id_range path_id_range)
{
	if (DEV_CHECK_RANGE(
		PATH_ID_TYPE_MIN,
		PATH_ID_TYPE_MAX - 1,
		path_id_type)) {
		return PATH_ID_INVALID;
	}
	if (DEV_CHECK_RANGE(
		PATH_ID_RANGE_MIN,
		PATH_ID_RANGE_MAX - 1,
		path_id_range)) {
		return PATH_ID_INVALID;
	}

	return path_id_ranges[path_id_type][path_id_range];
}

u16 mtk_dmx_dev_get_predefined_path_id(u16 path_id)
{
	if (path_id == PATH_ID_INVALID)
		return PATH_ID_INVALID;

	if (IS_TSIF_TS(path_id))
		return TSIF_0 +
			path_id -
			mtk_dmx_dev_get_path_id_range(
				PATH_ID_TYPE_M2TS,
				PATH_ID_RANGE_START);
	else if (IS_TSIF_TLV(path_id)) {
		return TLV_TSIF_0 +
			path_id -
			mtk_dmx_dev_get_path_id_range(
				PATH_ID_TYPE_ISDB3,
				PATH_ID_RANGE_START);
	} else if (IS_TSIF_ALP(path_id)) {
		return TS_ALP_0 +
			path_id -
			mtk_dmx_dev_get_path_id_range(
				PATH_ID_TYPE_ATSC3,
				PATH_ID_RANGE_START);
	}

	MTK_DMX_INFO("path_id %u\n", path_id);

	return PATH_ID_INVALID;
}

u16 mtk_dmx_dev_get_path_id(u16 predefined_path_id)
{
	u16 path_id;
	enum path_id_type path_id_type;

	if (predefined_path_id == PATH_ID_INVALID)
		return PATH_ID_INVALID;

	switch (predefined_path_id) {
	case TSIF_0...TSIF_2:
		path_id_type = PATH_ID_TYPE_M2TS;
		path_id = predefined_path_id - TSIF_0;
		break;
	case TLV_TSIF_0...TLV_TSIF_2:
		path_id_type = PATH_ID_TYPE_ISDB3;
		path_id = predefined_path_id - TLV_TSIF_0;
		break;
	case TS_ALP_0...TS_ALP_FI:
		path_id_type = PATH_ID_TYPE_ATSC3;
		path_id = predefined_path_id - TS_ALP_0;
		break;
	default:
		MTK_DMX_INFO(
			"predefined_path_id %u is not supported\n",
			predefined_path_id);
		return PATH_ID_INVALID;
	}

	if (mtk_dmx_dev_get_path_id_range(
		path_id_type,
		PATH_ID_RANGE_START) == PATH_ID_INVALID)
		return PATH_ID_INVALID;
	else if (mtk_dmx_dev_get_path_id_range(
		path_id_type,
		PATH_ID_RANGE_END) == PATH_ID_INVALID)
		return PATH_ID_INVALID;

	path_id += mtk_dmx_dev_get_path_id_range(
		path_id_type,
		PATH_ID_RANGE_START);
	if (path_id > mtk_dmx_dev_get_path_id_range(
		path_id_type,
		PATH_ID_RANGE_END))
		return PATH_ID_INVALID;

	MTK_DMX_DEBUG(
		"predefined_path_id %u to path_id %u\n",
		predefined_path_id,
		path_id);

	return path_id;
}

struct mtk_dmx2dev *get_mtk_dmx2dev(void)
{
	return gDev;
}
