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

#include <linux/time.h>
#include <linux/rtc.h>
#include <linux/delay.h>

#include "../mtk_tv_drm_kms.h"
#include <drm/mtk_tv_drm.h>
#include "mtk_tv_drm_video_frc.h"

//-------------------------------------------------------------------------------------------------
//  Local Defines
//-------------------------------------------------------------------------------------------------

#define free kfree
#define malloc(size) kmalloc((size), GFP_KERNEL)
#define _DATA_LEN 4
#define MOTION_STATUS_CNT      (10)

#define DATA_D0                 (0)
#define DATA_D1                 (1)
#define DATA_D2                 (2)
#define DATA_D3                 (3)

#define NUM_2 (2)
#define NUM_3 (3)
#define NUM_4 (4)
#define NUM_5 (5)
#define NUM_15 (15)

#define FRC_DEBUG_MS_PER_NSEC  (1000000)

#define FRC_IP_VERSION_1       (0x01) // M6
#define FRC_IP_VERSION_2       (0x02) // M6L
#define FRC_IP_VERSION_1_E3    (0x03)
#define FRC_IP_VERSION_4       (0x04) // mt5876
#define FRC_IP_VERSION_5       (0x05)
#define FRC_IP_VERSION_6       (0x06)
#define FRC_IP_VERSION_7       (0x07)

//CMD DEFAULT VALUE
#define DEFAULT_VALUE           (0x00)
#define DEFAULT_MEMC_MODE       (0x01)
#define DEFAULT_FRC_MODE        (0x00)
#define DEFAULT_DEMO_MODE       (0x00)
#define DEFAULT_PARTITION_MODE  (0x00)
#define DEFAULT_CADENCE_CTRL    (0xFF)
#define DEFAULT_FPLL_LOCK       (0xFF)
#define DEFAULT_PRE_VALUE       (0xFF)

#define _PAT_TRIG_CNT 10
#define ENABLE_MEMC_PATH (0x0C)
#define MFC_MEMC_MODE(X) ((X) & (0x000F))

#define _8K_HSIZE 7680
#define _8K_VSIZE 4320
#define _4K_HSIZE 3840
#define _4K_VSIZE 2160

#define IS_INPUT_DTV(x)	((x >= META_PQ_INPUTSRC_DTV_DIS) && (x < META_PQ_INPUTSRC_DTV_DIS_MAX))
#define IS_INPUT_MM(x)	((x >= META_PQ_INPUTSRC_STORAGE_DIS) && \
				(x < META_PQ_INPUTSRC_STORAGE_DIS_MAX))
#define IS_INPUT_B2R(x)	(IS_INPUT_DTV(x) || IS_INPUT_MM(x))

#define _1_SECOND 1000000ul

#define _FREQ_70HZ 70
#define IS_OUTPUT_120HZ(x) ((x) > _FREQ_70HZ)

#define LOCK                    (1)
#define VFRQRATIO_10            (10)
#define VFRQRATIO_1000          (1000)
#define SIZERATIO_16            (16)
#define LATENCYRATIO_10         (10)
#define LATENCYRATIO_1000       (1000)

#define SHIFT_8(X) ((X) >> (8))
#define ML_SHIFT_8 (8)
#define ML_SHIFT_16 (16)
#define ML_SHIFT_23 (23)
#define ML_SHIFT_48 (48)
#define ML_MASK_0x7F (0x7F)
#define ML_MASK_0xFFFF (0xFFFF)
#define ML_MASK_0x1FFFF (0x1FFFF)

//-------------------------------------------------------------------------------------------------
//  Local Structures
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Global Variables
//-------------------------------------------------------------------------------------------------

static uint16_t MOTION_STATUS[MOTION_STATUS_CNT] = {
0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000};
static struct mtk_frc_debug_info_history gFrcDbgHistroy = {0};

//-------------------------------------------------------------------------------------------------
//  Local Functions
//-------------------------------------------------------------------------------------------------

static const char *FrcInitStatus2Str(enum mtk_video_plane_memc_initstate eInitState)
{
	switch (eInitState) {
	case MEMC_INIT_CHECKRV55:
		return "MEMC_INIT_CHECK_COP";
	case MEMC_INIT_SENDDONE:
		return "MEMC_INIT_SENDDONE";
	case MEMC_INIT_FINISH:
		return "MEMC_INIT_FINISH";
	default:
		return "Invalid FRC Init State";
	}
}

const char *FilmModeToString(uint8_t CadenceNum)
{
	switch (CadenceNum) {
	case E_FRC_DEBUG_VIDEO_G0:
		return "1:1";
	case E_FRC_DEBUG_22_G0:
		return "2:2";
	case E_FRC_DEBUG_32_G0:
		return "3:2";
	case E_FRC_DEBUG_4222_G0:
		return "4:2:2:2";
	case E_FRC_DEBUG_32322_G0:
		return "3:2:3:2:2";
	case E_FRC_DEBUG_21111_G0:
		return "2:1:1:1:1";
	case E_FRC_DEBUG_2332_G0:
		return "2:3:3:2";
	case E_FRC_DEBUG_2111_G0:
		return "2:1:1:1";
	case E_FRC_DEBUG_33_G0:
		return "3:3";
	case E_FRC_DEBUG_44_G0:
		return "4:4";
	case E_FRC_DEBUG_55_G0:
		return "5:5";
	case E_FRC_DEBUG_42222_G0:
		return "4:2:2:2:2";
	default:
		return "No Define";
	}
}

static const char *FrcCmd2Str(enum Frc_command_list eFrcCmd)
{
	switch (eFrcCmd) {
	case FRC_MB_CMD_GET_SW_VERSION:
		return "GET_SW_VERSION";
	case FRC_MB_CMD_GET_RWDIFF:
		return "GET_RWDIFF";
	case FRC_MB_CMD_INIT:
		return "INIT";
	case FRC_MB_CMD_SET_TIMING:
		return "SET_TIMING";
	case FRC_MB_CMD_SET_INPUT_FRAME_SIZE:
		return "SET_INPUT_FRAME_SIZE";
	case FRC_MB_CMD_SET_OUTPUT_FRAME_SIZE:
		return "SET_OUTPUT_FRAME_SIZE";
	case FRC_MB_CMD_SET_FPLL_LOCK_DONE:
		return "SET_FPLL_LOCK_DONE";
	case FRC_MB_CMD_SWITCH_TO_SHM:
		return "SWITCH_TO_SHM";
	case FRC_MB_CMD_SET_MEMC_BYPASS:
		return "SET_MEMC_BYPASS";
	case FRC_MB_CMD_SET_FRC_MODE:
		return "SET_FRC_MODE";
	case FRC_MB_CMD_SET_MFC_MODE:
		return "SET_MFC_MODE";
	case FRC_MB_CMD_SET_FRC_GAME_MODE:
		return "SET_FRC_GAME_MODE";
	case FRC_MB_CMD_SET_MFC_DEMO_MODE:
		return "SET_MFC_DEMO_MODE";
	case FRC_MB_CMD_60_REALCINEMA_MODE:
		return "60_REALCINEMA_MODE";
	case FRC_MB_CMD_SET_INPUT_TYPE:
		return "SET_INPUT_TYPE";
	case FRC_MB_CMD_SET_MFC_DECODE_IDX_DIFF:
		return "SET_MFC_DECODE_IDX_DIFF";
	case FRC_MB_CMD_SET_MFC_FRC_DS_SETTINGS:
		return "SET_MFC_FRC_DS_SETTINGS";
	case FRC_MB_CMD_SET_MFC_CROPWIN_CHANGE:
		return "SET_MFC_CROPWIN_CHANGE";
	case FRC_MB_CMD_MEMC_MASK_WINDOWS_H:
		return "MEMC_MASK_WINDOWS_H";
	case FRC_MB_CMD_MEMC_MASK_WINDOWS_V:
		return "MEMC_MASK_WINDOWS_V";
	case FRC_MB_CMD_MEMC_ACTIVE_WINDOWS_H:
		return "MEMC_ACTIVE_WINDOWS_H";
	case FRC_MB_CMD_MEMC_ACTIVE_WINDOWS_V:
		return "MEMC_ACTIVE_WINDOWS_V";
	case FRC_MB_CMD_FILM_CADENCE_CTRL:
		return "FILM_CADENCE_CTRL";
	case FRC_MB_CMD_INIT_DONE:
		return "INIT_DONE";
	case FRC_MB_CMD_SET_SHARE_MEMORY_ADDR:
		return "SET_SHARE_MEMORY_ADDR";
	case FRC_MB_CMD_SET_QoS:
		return "SET_QoS";
	case FRC_MB_CMD_GET_QoS:
		return "GET_QoS";
	case FRC_MB_CMD_SUSPEND:
		return "SUSPEND";
	case FRC_MB_CMD_RESUME:
		return "RESUME";
	case FRC_MB_CMD_SET_MEMC_DEBUG_CADENCE:
		return "SET_MEMC_DEBUG_CADENCE";
	case FRC_MB_CMD_SET_MEMC_DEBUG_OUTSR:
		return "SET_MEMC_DEBUG_OUTSR";
	case FRC_MB_CMD_SET_MEMC_DEBUG_FALLBACKW3:
		return "SET_MEMC_DEBUG_FALLBACKW3";
	case FRC_MB_CMD_SET_MEMC_DEBUG_FALLBACKW0:
		return "SET_MEMC_DEBUG_FALLBACKW0";
	case FRC_MB_CMD_SET_MEMC_DEBUG_MOTION:
		return "SET_MEMC_DEBUG_MOTION";
	case FRC_MB_CMD_SET_MEMC_DEBUG_SHOW_FILM_ERROR:
		return "SET_MEMC_DEBUG_SHOW_FILM_ERROR";
	case FRC_MB_CMD_SET_MEMC_DEBUG_AUTOMODE:
		return "SET_MEMC_DEBUG_AUTOMODE";
	case FRC_MB_CMD_SET_MEMC_DEBUG_AUTOMODE_FINAL:
		return "SET_MEMC_DEBUG_AUTOMODE_FINAL";
	case FRC_MB_CMD_SET_MEMC_DEBUG_OSDMASK:
		return "SET_MEMC_DEBUG_OSDMASK";
	case FRC_MB_CMD_SET_MEMC_DEBUG_HALO_REDUCTION:
		return "SET_MEMC_DEBUG_HALO_REDUCTION";
	case FRC_MB_CMD_SET_MEMC_DEBUG_SEARCH_RANGE:
		return "SET_MEMC_DEBUG_SEARCH_RANGE";
	case FRC_MB_CMD_SET_MEMC_DEBUG_PERIODICAL:
		return "SET_MEMC_DEBUG_PERIODICAL";
	case FRC_MB_CMD_SET_MEMC_DEBUG_BADEDIT:
		return "SET_MEMC_DEBUG_BADEDIT";
	case FRC_MB_CMD_SET_MEMC_DEBUG_MVSTICKING:
		return "SET_MEMC_DEBUG_MVSTICKING";
	case FRC_MB_CMD_SET_MEMC_DEBUG_SMALL_OBJECT:
		return "SET_MEMC_DEBUG_SMALL_OBJECT";
	case FRC_MB_CMD_SET_MEMC_DEBUG_SPOTLIGHT:
		return "SET_MEMC_DEBUG_SPOTLIGHT";
	case FRC_MB_CMD_SET_MEMC_DEBUG_BOUNDARY_HALO:
		return "SET_MEMC_DEBUG_BOUNDARY_HALO";
	case FRC_MB_CMD_SET_MEMC_DEBUG_POSITION_RATIO_TABLE:
		return "SET_MEMC_DEBUG_POSITION_RATIO_TABLE";
	case FRC_MB_CMD_SET_MEMC_DEBUG_IMPREMENTATION_FRAME:
		return "SET_MEMC_DEBUG_IMPREMENTATION_FRAME";
	case FRC_MB_CMD_SET_MEMC_RELPOS_RATIO_INDEX:
		return "SET_MEMC_RELPOS_RATIO_INDEX";
	case FRC_MB_CMD_SET_MECM_LEVEL:
		return "SET_MECM_LEVEL";
	case FRC_MB_CMD_GET_MECM_ALG_STATUS:
		return "GET_MECM_ALG_STATUS";
	default:
		return "Invalid FRC command";
	}
}

static int _mtk_video_display_frc_getSize(uint8_t frc_ip_version,
	uint16_t *Hsize, uint16_t *Vsize)
{
	if ((Hsize == NULL) || (Vsize == NULL)) {
		DRM_ERROR("[%s, %d]frc getSize failed!\n",
			__func__, __LINE__);
		return -ENODEV;
	}

	switch (frc_ip_version) {
	case FRC_IP_VERSION_2:
	case FRC_IP_VERSION_4:
	case FRC_IP_VERSION_5:
	case FRC_IP_VERSION_6:
	case FRC_IP_VERSION_7:
		*Hsize = _4K_HSIZE;
		*Vsize = _4K_VSIZE;
		break;
	default:
		*Hsize = _8K_HSIZE;
		*Vsize = _8K_VSIZE;
		break;
	}

	return 0;
}

static int _mtk_video_display_frc_get_ipm_hv_size(uint16_t *HSize, uint16_t *VSize)
{
	struct hwregFrcDebugInfo stFrcDebugInfo;

	if (HSize == NULL) {
		DRM_ERROR("%s[%d]HSize is nullptr!\n", __func__, __LINE__);
		return -EINVAL;
	}
	if (VSize == NULL) {
		DRM_ERROR("%s[%d]VSize is nullptr!\n", __func__, __LINE__);
		return -EINVAL;
	}

	memset(&stFrcDebugInfo, 0x0, sizeof(struct hwregFrcDebugInfo));
	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);

	*HSize = stFrcDebugInfo.IpmSrcHSize;
	*VSize = stFrcDebugInfo.IpmSrcVSize;

	return 0;
}

static void _mtk_video_display_frc_set_ipm_pattern(
		struct mtk_tv_kms_context *pctx,
		struct mtk_frc_pattern_info *pstPatternInfo)
{
	struct mtk_video_context *pctx_video = &pctx->video_priv;

	uint16_t reg_num = pctx_video->reg_num;

	struct hwregFrcPatIn paramFrcPatIn;
	struct hwregOut paramOut;
	int ret = 0;

	memset(&paramFrcPatIn, 0, sizeof(struct hwregFrcPatIn));
	memset(&paramOut, 0, sizeof(struct hwregOut));

	paramOut.reg = malloc(sizeof(struct reg_info) * reg_num);
	if (paramOut.reg)
		memset(paramOut.reg, 0, sizeof(struct reg_info)*reg_num);
	else {
		DRM_ERROR("\n[%s,%d]malloc fail", __func__, __LINE__);
		return;
	}

	paramFrcPatIn.RIU = 1;
	paramFrcPatIn.bEn = pstPatternInfo->bEnble;
	paramFrcPatIn.bForce = pstPatternInfo->bForce;
	paramFrcPatIn.bSwap = pstPatternInfo->bSwap;
	paramFrcPatIn.MoveType = pstPatternInfo->MoveType;
	paramFrcPatIn.speed = pstPatternInfo->speed;

	ret = _mtk_video_display_frc_get_ipm_hv_size(
		&paramFrcPatIn.Hsize, &paramFrcPatIn.Vsize);
	if (ret != 0)
		DRM_ERROR("[%s, %d]frc get IPM H/V size failed!\n", __func__, __LINE__);

	drv_hwreg_render_frc_set_ipmpattern(
	paramFrcPatIn, &paramOut);

	if (paramOut.reg)
		free(paramOut.reg);
}

static void _mtk_video_display_frc_set_opm_pattern(
		struct mtk_tv_kms_context *pctx,
		struct mtk_frc_pattern_info *pstPatternInfo)
{
	struct mtk_frc_context *pctx_frc = &pctx->frc_priv;

	struct hwregFrcPatIn paramFrcPatIn;
	struct hwregOut paramOut;
	int ret = 0;

	memset(&paramFrcPatIn, 0, sizeof(struct hwregFrcPatIn));
	memset(&paramOut, 0, sizeof(struct hwregOut));

	paramOut.reg = malloc(sizeof(struct reg_info)*REG_NUM_MAX);
	if (paramOut.reg)
		memset(paramOut.reg, 0, sizeof(struct reg_info)*REG_NUM_MAX);
	else {
		DRM_ERROR("\n[%s,%d]malloc fail", __func__, __LINE__);
		return;
	}

	paramFrcPatIn.RIU = 1;
	paramFrcPatIn.sel = pstPatternInfo->sel;
	paramFrcPatIn.bEn = pstPatternInfo->bEnble;
	paramFrcPatIn.bForce = pstPatternInfo->bForce;
	paramFrcPatIn.bSwap = pstPatternInfo->bSwap;
	paramFrcPatIn.MoveType = pstPatternInfo->MoveType;
	paramFrcPatIn.speed = pstPatternInfo->speed;
	paramFrcPatIn.bOpmF2 = pstPatternInfo->bOpmF2;
	paramFrcPatIn.bOpmF3 = pstPatternInfo->bOpmF3;
	paramFrcPatIn.F2_fcnt = pstPatternInfo->F2_fcnt;
	paramFrcPatIn.F3_fcnt = pstPatternInfo->F3_fcnt;

	ret = _mtk_video_display_frc_getSize(pctx_frc->frc_ip_version,
		&paramFrcPatIn.Hsize, &paramFrcPatIn.Vsize);
	if (ret < 0)
		DRM_ERROR("[%s, %d]frc getSize failed!\n",
			__func__, __LINE__);

	drv_hwreg_render_frc_set_opmpattern(
	paramFrcPatIn, &paramOut);

	if (paramOut.reg)
		free(paramOut.reg);
}

static int _mtk_video_display_frc_check_motion_status(
	uint16_t *u16Data,
	uint16_t u16Length,
	struct mtk_frc_context *pctx_frc)
{
	#define MV_X_MASK(X)       ((X) & (0x00FF))
	#define MV_Y_MASK(X)       (((X) & (0xFF00))>>8)
	#define MV_DIFF_MASK(X)    ((X) & (0x000F))
	#define CHECK_CNT_MASK(X)  ((X) & (0x000F))
	#define DEBUG_EN_MASK(X)   ((X) & (0x0001))

	uint8_t u8MVX = DEFAULT_VALUE, u8MVY = DEFAULT_VALUE;
	uint8_t u8Diff = DEFAULT_VALUE, u8DebugEn = DEFAULT_VALUE;
	uint8_t u8CheckCnt = DEFAULT_VALUE, u8CheckCntEnd = DEFAULT_VALUE;
	uint8_t u8MVX_ResultCnt = DEFAULT_VALUE, u8MVY_ResultCnt = DEFAULT_VALUE;

	uint16_t u16MVX_Min = DEFAULT_VALUE, u16MVX_Max = DEFAULT_VALUE;
	uint16_t u16MVY_Min = DEFAULT_VALUE, u16MVY_Max = DEFAULT_VALUE;
	uint16_t CURRENT_MOTION_STATUS[MOTION_STATUS_CNT] = {0};

	if (u16Data == NULL || pctx_frc == NULL) {
		DRM_ERROR("[%s, %d] u16Data or pctx_frc is null.\n", __func__, __LINE__);
		return -EINVAL;
	}
	if ((u16Length == 0) || (u16Length > _DATA_LEN)) {
		DRM_ERROR("[%s, %d]u16Length(%d) is invalid!\n", __func__, __LINE__, u16Length);
		return -EINVAL;
	}

	memcpy(CURRENT_MOTION_STATUS, MOTION_STATUS, sizeof(CURRENT_MOTION_STATUS));
	u8MVX         = MV_X_MASK(u16Data[DATA_D0]);
	u8MVY         = MV_Y_MASK(u16Data[DATA_D0]);
	u8Diff		  = MV_DIFF_MASK(u16Data[DATA_D1]);
	u8CheckCntEnd = CHECK_CNT_MASK(u16Data[DATA_D2]);
	u8DebugEn     = DEBUG_EN_MASK(u16Data[DATA_D3]);

	if (u8MVX > u8Diff) {
		u16MVX_Min = u8MVX - u8Diff;
		u16MVX_Max = u8MVX + u8Diff;
	}

	if (u8MVY > u8Diff) {
		u16MVY_Min = u8MVY - u8Diff;
		u16MVY_Max = u8MVY + u8Diff;
	}

	for (u8CheckCnt = 0; u8CheckCnt < u8CheckCntEnd; u8CheckCnt++) {
		if ((MV_X_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]) >= u16MVX_Min)
			&& (MV_X_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]) <= u16MVX_Max))
			u8MVX_ResultCnt++;

		if ((MV_Y_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]) >= u16MVY_Min)
			&& (MV_Y_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]) <= u16MVY_Max))
			u8MVY_ResultCnt++;
	}

	if (((u8MVX_ResultCnt == u8CheckCntEnd)
		&& (u8MVY_ResultCnt == u8CheckCntEnd))
		&& (pctx_frc->frcopmMaskEnable == false))
		pr_crit("[FRC][%s:%d] Check Motion status: PASS, OPM_MASK = %d\n"
			, __func__, __LINE__, pctx_frc->frcopmMaskEnable);
	else
		pr_crit("[FRC][%s:%d] Check Motion status: FAIL, OPM_MASK = %d\n"
			, __func__, __LINE__, pctx_frc->frcopmMaskEnable);

	if (u8DebugEn == TRUE) {
		pr_crit("[FRC][%s:%d] MVX_Min = 0x%x, MVX_Max = 0x%x\n",
			__func__, __LINE__, u16MVX_Min, u16MVX_Max);
		pr_crit("[FRC][%s:%d] MVY_Min = 0x%x, MVY_Max = 0x%x\n",
			__func__, __LINE__, u16MVY_Min, u16MVY_Max);
		pr_crit("[FRC][%s:%d] DIFF = 0x%x, MAX DIFF = 0x0F",
					__func__, __LINE__, u8Diff);
		pr_crit("[FRC][%s:%d] Check_Cnt = 0x%x, MAX_CheckCnt = 0x0A",
					__func__, __LINE__, u8CheckCntEnd);

		for (u8CheckCnt = 0; u8CheckCnt < u8CheckCntEnd; u8CheckCnt++) {
			pr_crit("[FRC][%s:%d] INDEX = %d, MVX = 0x%x, MVY = 0x%x\n",
				__func__, __LINE__, u8CheckCnt,
				MV_X_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]),
				MV_Y_MASK(CURRENT_MOTION_STATUS[u8CheckCnt]));
		}
	}

	#undef MV_X_MASK
	#undef MV_Y_MASK
	#undef MV_DIFF_MASK
	#undef CHECK_CNT_MASK
	#undef DEBUG_EN_MASK

	return 0;
}

static inline struct mtk_drm_plane *_mtk_video_display_frc_get_mtk_drm_plane(
	struct mtk_tv_kms_context *pctx)
{
	struct ext_crtc_prop_info *crtc_props = NULL;
	uint64_t plane_id = 0;
	struct drm_mode_object *obj = NULL;
	struct drm_plane *plane = NULL;

	if (pctx == NULL)
		return NULL;

	crtc_props = pctx->ext_crtc_properties;
	if (crtc_props == NULL)
		return NULL;

	plane_id = crtc_props->prop_val[E_EXT_CRTC_PROP_FRAMESYNC_PLANE_ID];
	obj = drm_mode_object_find(pctx->drm_dev, NULL, plane_id, DRM_MODE_OBJECT_ANY);
	if (obj == NULL)
		return NULL;

	/* obj_to_plane(obj) is never null */
	plane = obj_to_plane(obj);

	return to_mtk_plane(plane);
}

static inline struct mtk_video_context *_mtk_video_display_frc_get_video_context(
	struct mtk_tv_kms_context *pctx)
{
	struct mtk_drm_plane *mplane = NULL;
	struct mtk_tv_kms_context *pctx_plane = NULL;

	if (pctx == NULL)
		return NULL;

	mplane = _mtk_video_display_frc_get_mtk_drm_plane(pctx);
	if (mplane == NULL)
		return NULL;

	pctx_plane = (struct mtk_tv_kms_context *)mplane->crtc_private;
	if (pctx_plane == NULL)
		return NULL;

	return &pctx_plane->video_priv;

}

static void _mtk_video_display_frc_set_dynamic_path_log_en(
		struct mtk_tv_kms_context *pctx,
		bool bLogEn)
{
	struct mtk_video_context *pctx_video = &pctx->video_priv;

	uint16_t reg_num = pctx_video->reg_num;

	struct hwregFrcDynPathLogEn paramFrcDynPathLogEn;
	struct hwregOut paramOut;
	int ret = 0;

	memset(&paramFrcDynPathLogEn, 0, sizeof(struct hwregFrcDynPathLogEn));
	memset(&paramOut, 0, sizeof(struct hwregOut));

	paramOut.reg = malloc(sizeof(struct reg_info) * reg_num);
	if (paramOut.reg)
		memset(paramOut.reg, 0, sizeof(struct reg_info)*reg_num);
	else {
		DRM_ERROR("\n[%s,%d]malloc fail", __func__, __LINE__);
		return;
	}

	paramFrcDynPathLogEn.RIU = 1;
	paramFrcDynPathLogEn.bLogEn = bLogEn;

	ret = drv_hwreg_render_frc_set_dynamic_path_log_en(
	paramFrcDynPathLogEn, &paramOut);
	if (ret != 0)
		DRM_ERROR("[%s, %d]frc set func failed!\n", __func__, __LINE__);

	if (paramOut.reg)
		free(paramOut.reg);
}

static void _mtk_video_display_frc_force_dynamic_path_bypass(
		struct mtk_tv_kms_context *pctx,
		bool bBypass)
{
	struct mtk_video_context *pctx_video = &pctx->video_priv;

	uint16_t reg_num = pctx_video->reg_num;

	struct hwregFrcDynPathBypass paramFrcDynPathBypass;
	struct hwregOut paramOut;
	int ret = 0;

	memset(&paramFrcDynPathBypass, 0, sizeof(struct hwregFrcDynPathBypass));
	memset(&paramOut, 0, sizeof(struct hwregOut));

	paramOut.reg = malloc(sizeof(struct reg_info) * reg_num);
	if (paramOut.reg)
		memset(paramOut.reg, 0, sizeof(struct reg_info)*reg_num);
	else {
		DRM_ERROR("\n[%s,%d]malloc fail", __func__, __LINE__);
		return;
	}

	paramFrcDynPathBypass.RIU = 1;
	paramFrcDynPathBypass.bBypass = bBypass;

	ret = drv_hwreg_render_frc_force_dynamic_path_bypass(
	paramFrcDynPathBypass, &paramOut);
	if (ret != 0)
		DRM_ERROR("[%s, %d]frc set func failed!\n", __func__, __LINE__);

	if (paramOut.reg)
		free(paramOut.reg);
}

static void _mtk_video_display_frc_force_dynamic_path_select(
		struct mtk_tv_kms_context *pctx,
		bool bEn, bool bSel)
{
	struct mtk_video_context *pctx_video = &pctx->video_priv;

	uint16_t reg_num = pctx_video->reg_num;

	struct hwregFrcDynPathSelect paramFrcDynPathSelect;
	struct hwregOut paramOut;
	int ret = 0;

	memset(&paramFrcDynPathSelect, 0, sizeof(struct hwregFrcDynPathSelect));
	memset(&paramOut, 0, sizeof(struct hwregOut));

	paramOut.reg = malloc(sizeof(struct reg_info) * reg_num);
	if (paramOut.reg)
		memset(paramOut.reg, 0, sizeof(struct reg_info)*reg_num);
	else {
		DRM_ERROR("\n[%s,%d]malloc fail", __func__, __LINE__);
		return;
	}

	paramFrcDynPathSelect.RIU = 1;
	paramFrcDynPathSelect.bEn = bEn;
	paramFrcDynPathSelect.bSel = bSel;

	ret = drv_hwreg_render_frc_force_dynamic_path_select(
	paramFrcDynPathSelect, &paramOut);
	if (ret != 0)
		DRM_ERROR("[%s, %d]frc set func failed!\n", __func__, __LINE__);

	if (paramOut.reg)
		free(paramOut.reg);
}

static void _mtk_video_display_frc_get_system_time(struct mtk_frc_debug_info *pDbgInfo)
{
	struct timespec64 ts;
	struct rtc_time tm;
	long milliseconds;

	// Get the current time in seconds and nanoseconds since 1970-01-01 00:00:00 UTC
	ktime_get_real_ts64(&ts);

	// Convert the time to the rtc_time structure
	rtc_time64_to_tm(ts.tv_sec, &tm);

	// Calculate milliseconds from nanoseconds
	milliseconds = ts.tv_nsec / FRC_DEBUG_MS_PER_NSEC;

	if ((size_t)snprintf(pDbgInfo->time_buffer, FRC_TIME_BUFFER_SIZE,
		"%02d-%02d:%02d:%02d:%02d.%03ld",
		tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, milliseconds) >
		FRC_TIME_BUFFER_SIZE) {
		DRM_WARN("%s[%d] snprintf failed.", __func__, __LINE__);
	}
}

static void _mtk_video_display_frc_mload_parsing(
	uint64_t cmd, uint32_t *addr, uint16_t *data, uint16_t *mask)
{
	uint8_t  addr_temp = 0;
	uint16_t bank_temp = 0;

	*mask = (uint16_t)(cmd >> ML_SHIFT_48);
	*data = (uint16_t)(ML_MASK_0xFFFF & (uint16_t)cmd);
	addr_temp = (uint8_t)((cmd>>ML_SHIFT_16 & ML_MASK_0x7F));
	bank_temp = (uint16_t)((cmd >> ML_SHIFT_23) & ML_MASK_0x1FFFF);
	*addr = (uint32_t)(addr_temp|(bank_temp << ML_SHIFT_8));
}

static void _mtk_video_display_frc_mload_dump(
	struct mtk_frc_context *pctx_frc, struct hwregFrcVgsyncDebug *dDsInfo)
{
	uint64_t *pu64Va = NULL;
	uint64_t u64Pa = 0, u64Size = 0, cmd = 0;
	uint32_t u32Addr = 0;
	uint16_t u16Data = 0, u16Mask = 0, u16DispH = 0, u16DispW = 0;
	uint8_t Idx = 0;
	bool remap = 0, bAvailable = 0, bVgsyncEn = 0, bAfdEn = 0;

	Idx = SHIFT_8(dDsInfo->DsOpbIdx);
	u64Pa = pctx_frc->frc_ml_info[Idx].u64Pa;
	u64Size = pctx_frc->frc_ml_info[Idx].u64Size;
	u16DispH = pctx_frc->frc_ml_info[Idx].u16DispH;
	u16DispW = pctx_frc->frc_ml_info[Idx].u16DispW;
	bAvailable = pctx_frc->frc_ml_info[Idx].bAvailable;
	bVgsyncEn = pctx_frc->frc_vgsync_en[Idx];
	bAfdEn =  pctx_frc->frc_afd_en[Idx];
	pu64Va = (uint64_t *)mtk_render_common_pa2va(u64Pa, u64Size, &remap);

	if (pu64Va != NULL) {
		cmd = (*pu64Va);
		_mtk_video_display_frc_mload_parsing(cmd, &u32Addr, &u16Data, &u16Mask);
	}

	pctx_frc->frc_ml_dump_info.u64Pa = u64Pa;
	pctx_frc->frc_ml_dump_info.u32Addr = u32Addr;
	pctx_frc->frc_ml_dump_info.u16Data = u16Data;
	pctx_frc->frc_ml_dump_info.u16Mask = u16Mask;
	pctx_frc->frc_ml_dump_info.u16DispH = u16DispH;
	pctx_frc->frc_ml_dump_info.u16DispW = u16DispW;
	pctx_frc->frc_ml_dump_info.bAvailable = bAvailable;
	pctx_frc->frc_ml_dump_info.bVgsyncEn = bVgsyncEn;
	pctx_frc->frc_ml_dump_info.bAfdEn = bAfdEn;

	if (pu64Va && remap) {
		mtk_render_common_iounmap(pu64Va);
		pu64Va = NULL;
	}
}

//-------------------------------------------------------------------------------------------------
//  Global Functions
//-------------------------------------------------------------------------------------------------

const char *SenceToString(enum mtk_video_frc_pq_scene scene)
{
	switch (scene) {
	case MEMC_PQ_SCENE_NORMAL:
		return "Normal";
	case MEMC_PQ_SCENE_GAME:
		return "Game";
	case MEMC_PQ_SCENE_GAME_CROP:
		return "GameCrop";
	case MEMC_PQ_SCENE_PC:
		return "PC";
	case MEMC_PQ_SCENE_PHOTO:
		return "Photo";
	case MEMC_PQ_SCENE_FORCE_P:
		return "ForceP";
	case MEMC_PQ_SCENE_HFR:
		return "HFR";
	case MEMC_PQ_SCENE_GAME_WITH_MEMC:
		return "GameMEMC";
	case MEMC_PQ_SCENE_3D:
		return "3D";
	case MEMC_PQ_SCENE_SLICED_GAME:
		return "SlicedGame";
	case MEMC_PQ_SCENE_GAME_WITH_PC:
		return "GamePC";
	case MEMC_PQ_SCENE_GAME_CROP_WITH_MEMC:
		return "GameCropMEMC";
	case MEMC_PQ_SCENE_MAX:
		return "MAX";
	default:
		return "Not Defined";
	}
}

int mtk_video_display_frc_check_ipm_hv_size(
	struct mtk_tv_kms_context *pctx)
{
	struct mtk_frc_context *pctx_frc = &pctx->frc_priv;
	uint16_t IpmSrcHSize = 0;
	uint16_t IpmSrcVSize = 0;
	int ret = 0;

	if ((pctx_frc->stPatInfo.sel == MEMC_PAT_IPM_FORCE_TIMING) &&
		(pctx_frc->stPatInfo.bEnble == TRUE)) {
		ret = _mtk_video_display_frc_get_ipm_hv_size(&IpmSrcHSize, &IpmSrcVSize);
		if (ret != 0) {
			DRM_ERROR("[%s, %d]frc get IPM H/V size failed!\n", __func__, __LINE__);
		} else {
			if ((IpmSrcHSize != pctx_frc->frc_ipm_hsize) ||
				(IpmSrcVSize != pctx_frc->frc_ipm_vsize)) {
				_mtk_video_display_frc_set_ipm_pattern(pctx,
					&pctx_frc->stPatInfo);
				pctx_frc->frc_ipm_hsize = IpmSrcHSize;
				pctx_frc->frc_ipm_vsize = IpmSrcVSize;
			}
		}
	}

	return 0;
}

int mtk_video_display_frc_get_motion_status(
	struct mtk_tv_kms_context *pctx)
{
	struct mtk_frc_context *pctx_frc = &pctx->frc_priv;
	struct hwregFrcMotionstatus stFrcMotionStats = {0};
	static uint8_t motion_status_cnt     = DEFAULT_VALUE;

	memset(&stFrcMotionStats, 0, sizeof(struct hwregFrcMotionstatus));

	if (pctx_frc->frcopmMaskEnable == false) {
		//Read Register value
		drv_hwreg_render_frc_get_motion_status(&stFrcMotionStats);

		MOTION_STATUS[motion_status_cnt] = stFrcMotionStats.Global_MVXY;
		motion_status_cnt++;

		if (motion_status_cnt == MOTION_STATUS_CNT)
			motion_status_cnt = DEFAULT_VALUE;
	}

	return 0;
}

void mtk_video_display_frc_record_change(
	struct mtk_frc_debug_info dbgInfo)
{
	struct hwregFrcDebugInfo stFrcDebugInfo = {0};

	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);
	dbgInfo.bIsMemcPath = (MFC_MEMC_MODE(stFrcDebugInfo.BlendingTopMux) == ENABLE_MEMC_PATH);
	dbgInfo.bRecorded = true;

	if (gFrcDbgHistroy.currentIndex >= 0 &&
		gFrcDbgHistroy.currentIndex < MAX_CMD_HISTORY_SIZE) {
		// Store the new info in the history array at the current index
		gFrcDbgHistroy.dbgHistroy[gFrcDbgHistroy.currentIndex] = dbgInfo;
		_mtk_video_display_frc_get_system_time(
			&gFrcDbgHistroy.dbgHistroy[gFrcDbgHistroy.currentIndex]);

		// Increment the current index and wrap it around if necessary
		gFrcDbgHistroy.currentIndex =
			(gFrcDbgHistroy.currentIndex + 1) % MAX_CMD_HISTORY_SIZE;
	}
}

void mtk_video_display_frc_mload_parsing(
	uint64_t *pu64Va, uint32_t *addr, uint16_t *data, uint16_t *mask)
{
	uint64_t cmd = 0;

	if (pu64Va != NULL) {
		cmd = (*pu64Va);
		_mtk_video_display_frc_mload_parsing(cmd, addr, data, mask);
	}
}

void mtk_video_display_frc_print_info(struct mtk_tv_kms_context *pctx)
{
#define MFC_BLENDING_MASK(X) ((X) & (0x000F))

	struct mtk_frc_context *pctx_frc = &pctx->frc_priv;
	struct hwregFrcDebugInfo *dInfo = &(pctx_frc->dInfo);
	struct hwregFrcDebugInfo *pre_dInfo = &(pctx_frc->pre_dInfo);
	struct hwregFrcVgsyncDebug *dDsInfo = &(pctx_frc->dDsInfo);
	struct hwregFrcMlDumpInfo *dMlDInfo = &(pctx_frc->frc_ml_dump_info);

	if (!pctx_frc->bDebugCmdEn)
		return;

	drv_hwreg_render_frc_get_debug_info(dInfo);
	drv_hwreg_render_frc_get_vgsync_debug_info(dDsInfo);

	switch (pctx_frc->debugCmdType) {
	case E_FRC_DEBUG_CMD_SWITCH:
		pr_crit("V %x, %x, %x, %x, %x, %x, %x, %x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->SwRst,
			dInfo->DummyFreezeEn, dInfo->FreezeEn, dInfo->HSEFreezeEn,
			dInfo->OP2DsSel, dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx);
		break;
	case E_FRC_DEBUG_CMD_DS_SWITCH:
		pr_crit("V %x, %x, %x, %x, %x, %x, %x, %x, %3x, %3x, %3x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->SwRst,
			dInfo->DummyFreezeEn, dInfo->FreezeEn, dInfo->HSEFreezeEn,
			dInfo->OP2DsSel, dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx);
		break;
	case E_FRC_DEBUG_CMD_SWITCH_CHG:
		if ((pre_dInfo->BlendingTopMux != dInfo->BlendingTopMux) ||
			(pre_dInfo->SwRst != dInfo->SwRst) ||
			(pre_dInfo->HSEFreezeEn != dInfo->HSEFreezeEn) ||
			(pre_dInfo->OP2DsSel != dInfo->OP2DsSel)) {
			pr_crit("V %x, %x, %x, %x, %x, %x, %x, %x\n",
				MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->SwRst,
				dInfo->DummyFreezeEn, dInfo->FreezeEn, dInfo->HSEFreezeEn,
				dInfo->OP2DsSel, dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx);
		}
		memcpy(pre_dInfo, dInfo, sizeof(struct hwregFrcDebugInfo));
		break;
	case E_FRC_DEBUG_CMD_MUTE:
		pr_crit("V %x, %x, %x, %x, %x, %x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->InsMute,
			dInfo->ColovSetBlkMain, dInfo->GopSetBlkMain, dInfo->IpmWriteIdx,
			dInfo->OpmF2ReadIdx);
		break;
	case E_FRC_DEBUG_CMD_DS_MUTE:
		pr_crit("V %x, %x, %x, %x, %x, %x, %3x, %3x, %3x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->InsMute,
			dInfo->ColovSetBlkMain, dInfo->GopSetBlkMain, dInfo->IpmWriteIdx,
			dInfo->OpmF2ReadIdx,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx);
		break;
	case E_FRC_DEBUG_CMD_DS_MUTE_INST:
		pr_crit("V %x, %x, %x, %x, %x, %x, %3x, %3x, %3x, %3x, %3x, %3x, %3x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->InsMute,
			dInfo->ColovSetBlkMain, dInfo->GopSetBlkMain, dInfo->IpmWriteIdx,
			dInfo->OpmF2ReadIdx,
			dInfo->IpmInstH, dInfo->IpmInstV,
			dInfo->CropHSize, dInfo->CropVSize,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx);
		break;
	case E_FRC_DEBUG_CMD_DS_FREEZE_INST:
		pr_crit("V %x, %x, %x, %x, %x, %x, %x, %3x, %3x, %3x, %3x, %3x, %3x, %3x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->SwRst,
			dInfo->DummyFreezeEn, dInfo->FreezeEn, dInfo->HSEFreezeEn,
			dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dInfo->IpmInstH, dInfo->IpmInstV,
			dInfo->CropHSize, dInfo->CropVSize,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx);
		break;
	case E_FRC_DEBUG_CMD_LINE_CNT:
		pr_crit("V %x, %x, %x, %x, %x, %x, %x, %x, %4x, %4x\n",
			MFC_BLENDING_MASK(dInfo->BlendingTopMux), dInfo->SwRst,
			dInfo->DummyFreezeEn, dInfo->FreezeEn, dInfo->HSEFreezeEn,
			dInfo->OP2DsSel, dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dInfo->IpmLineCntH, dInfo->IpmLineCntV);
		break;
	case E_FRC_DEBUG_CMD_VGSYNC:
		pr_crit("V %x, %x, %x, %x, %3x, %3x, %3x, %3x, %3x, %3x, %3x\n",
			dInfo->MfcLevel, dInfo->HvspBypassEn,
			dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dInfo->IpmInstH, dInfo->IpmInstV,
			dInfo->CropHSize, dInfo->CropVSize,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx);
		break;
	case E_FRC_DEBUG_CMD_VGSYNC_ADVANCE:
		_mtk_video_display_frc_mload_dump(pctx_frc, dDsInfo);
		pr_crit("V %x, %x, %x, %x, %x, %3x, %3x, %3x, %3x, %3x, %3x, %3x, %3x, "
			"%3x, %3x, %3x, %3x, %3x, %llx, %4x, %4x, %x, %x, %x\n",
			dInfo->MfcLevel, dInfo->InsMute, dInfo->HvspBypassEn,
			dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dInfo->IpmInstH, dInfo->IpmInstV,
			dInfo->HvspSrcHSize, dInfo->HvspSrcVSize,
			dInfo->HvspDstHSize, dInfo->HvspDstVSize,
			dInfo->CropHSize, dInfo->CropVSize,
			dDsInfo->DsIpIdx, dDsInfo->DsOpfIdx, dDsInfo->DsOpbIdx,
			dMlDInfo->u16DispH, dMlDInfo->u16DispW, dMlDInfo->u64Pa,
			dMlDInfo->u32Addr, dMlDInfo->u16Data,
			dMlDInfo->bAvailable, dMlDInfo->bVgsyncEn, dMlDInfo->bAfdEn);
		break;
	case E_FRC_DEBUG_CMD_VGSYNC_VAILD:
		_mtk_video_display_frc_mload_dump(pctx_frc, dDsInfo);
		pr_crit("V %x, %x, %x, %x, %x, %3x, %3x, %3x, %3x, "
			"%4x, %x, %x, %x, %x\n",
			dInfo->MfcLevel, dInfo->InsMute, dInfo->HvspBypassEn,
			dInfo->IpmWriteIdx, dInfo->OpmF2ReadIdx,
			dInfo->CropHSize, dInfo->CropVSize,
			dDsInfo->DsIpIdx, dDsInfo->DsOpbIdx,
			dMlDInfo->u32Addr, dMlDInfo->u16Data,
			dMlDInfo->bAvailable, dMlDInfo->bVgsyncEn, dMlDInfo->bAfdEn);
		break;
	default:
		break;
	}
}

//-------------------------------------------------------------------------------------------------
//  Global IOCTRL Functions
//-------------------------------------------------------------------------------------------------

int mtk_video_display_frc_get_algo_version(uint32_t *pVer)
{
	int ret = 0;
	struct hwregFrcDebugInfo stFrcDebugInfo = {0};

	if (pVer == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	memset(&stFrcDebugInfo, 0, sizeof(struct hwregFrcDebugInfo));

	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);

	*pVer = stFrcDebugInfo.Rv55Ver;

	return ret;
}

int mtk_video_display_frc_set_extra_latency(
	struct mtk_tv_kms_context *pctx,
	struct drm_mtk_tv_video_extra_latency *latency)
{
	struct mtk_panel_context *pctx_pnl = NULL;
	struct mtk_drm_plane *mplane = NULL;
	struct mtk_video_context *pctx_video = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	struct video_plane_prop *plane_props = NULL;
	unsigned int plane_inx = 0;
	struct mtk_video_plane_ctx *plane_ctx = NULL;
	uint8_t roundUpFrame = 0;
	uint8_t roundDownFrame = 0;
	uint32_t deltaTimeDiffUp = 0;
	uint32_t deltaTimeDiffDown = 0;
	uint32_t durationTime_msx1000 = 0;
	uint32_t input_freq = 0;
	uint64_t input_vfreq = 0;
	uint32_t output_freq = 0;
	uint32_t latency_msx1000 = 0;
	uint8_t extra_latency_diff = 0;
	uint8_t FramedelayCntx10 = 0;
	uint8_t FramedelayMaxCntX10 = 0;
	uint8_t real_extra_latency_cnt = 0;
	uint32_t real_latencyx1000 = 0;
	int ret = 0;

	mplane = _mtk_video_display_frc_get_mtk_drm_plane(pctx);
	pctx_video = _mtk_video_display_frc_get_video_context(pctx);

	if ((pctx == NULL) || (latency == NULL) || (mplane == NULL)
		|| (pctx_video == NULL)) {
		DRM_ERROR("[%s, %d]Pointer is NULL!\n", __func__, __LINE__);
		return -ENODEV;
	}

	pctx_pnl = &pctx->panel_priv;
	plane_props = pctx_video->video_plane_properties;
	plane_inx = mplane->video_index;
	plane_ctx = pctx_video->plane_ctx + plane_inx;
	pctx_frc = &pctx->frc_priv;

	if ((pctx_pnl == NULL) || (pctx_frc == NULL) || (plane_ctx == NULL)
		|| (plane_props == NULL)) {
		DRM_ERROR("[%s, %d]Pointer is NULL!\n", __func__, __LINE__);
		return -ENODEV;
	}

	if (pctx->stub)
		plane_ctx->framesyncInfo.input_source = META_PQ_INPUTSRC_HDMI_DIS;

	//get memc support info
	ret = drv_hwreg_render_frc_get_memc_support_info(&pctx_frc->eMEMC_support_info);
	if (ret < 0) {
		DRM_ERROR("[%s, %d] check memc support failed\n",
			__func__, __LINE__);
		return -ENODEV;
	}

	// only non vdec source, memc path, efuse support memc can support video extra latency
	if ((IS_INPUT_B2R(plane_ctx->framesyncInfo.input_source)) ||
		(latency->planetype != MTK_VPLANE_TYPE_MEMC) ||
		(pctx_frc->frc_a2dp_enable == FALSE) ||
		(pctx_frc->eMEMC_support_info == E_DRV_FRC_SUPPORT_NON_MEMC)) {
		latency->real_video_latency_ms = 0;
		DRM_INFO("[%s][%d]Support memc Type:%d, Support A2DP:%d\n",
		__func__, __LINE__, pctx_frc->eMEMC_support_info, pctx_frc->frc_a2dp_enable);
		return 0;
	}

	//transfer to frame number
	input_vfreq = plane_props->prop_val[EXT_VPLANE_PROP_INPUT_VFREQ];
	input_freq  = (uint32_t) DIV64_U64_ROUND_CLOSEST(input_vfreq, VFRQRATIO_1000);
	output_freq = DIV_ROUND_CLOSEST(pctx_pnl->outputTiming_info.u32OutputVfreq, VFRQRATIO_1000);

	if (input_freq == 0) {
		latency->real_video_latency_ms = 0;
		return 0;
	}

	durationTime_msx1000 = DIV_ROUND_CLOSEST(_1_SECOND, input_freq);
	latency_msx1000 = latency->video_latency_ms*LATENCYRATIO_1000;

	roundUpFrame = DIV_ROUND_UP(latency_msx1000, durationTime_msx1000);
	roundDownFrame = (latency_msx1000/durationTime_msx1000);

	deltaTimeDiffUp = abs(latency_msx1000 - roundUpFrame*durationTime_msx1000);
	deltaTimeDiffDown = abs(latency_msx1000 - roundDownFrame*durationTime_msx1000);

	if (IS_OUTPUT_120HZ(output_freq))
		extra_latency_diff = pctx_frc->frc_a2dp_max_frame_cnt -
					pctx_frc->frc_frame_cnt120Hz;
	else
		extra_latency_diff = pctx_frc->frc_a2dp_max_frame_cnt -
					pctx_frc->frc_frame_cnt60Hz;

	// get HW max support frame
	FramedelayCntx10 = mtk_video_display_frc_get_FramedelayCnt(input_freq, output_freq, 0,
							pctx_frc->frc_ip_version);
	FramedelayMaxCntX10 = mtk_video_display_frc_get_FramedelayCnt(input_freq,
					output_freq, extra_latency_diff, pctx_frc->frc_ip_version);

	if (deltaTimeDiffUp < deltaTimeDiffDown) {
		if (((roundUpFrame*LATENCYRATIO_10)+FramedelayCntx10) > FramedelayMaxCntX10) {
			real_latencyx1000 = extra_latency_diff*durationTime_msx1000;
			real_extra_latency_cnt = extra_latency_diff;
		} else {
			real_latencyx1000 = roundUpFrame*durationTime_msx1000;
			real_extra_latency_cnt = roundUpFrame;
		}
	} else {
		if (((roundDownFrame*LATENCYRATIO_10)+FramedelayCntx10) > FramedelayMaxCntX10) {
			real_latencyx1000 = extra_latency_diff*durationTime_msx1000;
			real_extra_latency_cnt = extra_latency_diff;
		} else {
			real_latencyx1000 = roundDownFrame*durationTime_msx1000;
			real_extra_latency_cnt = roundDownFrame;
		}
	}

	latency->real_video_latency_ms = DIV_ROUND_CLOSEST(real_latencyx1000, LATENCYRATIO_1000);

	//write extra delay number to share memory
	mtk_video_display_frc_set_rv55_extra_delay_num(pctx, real_extra_latency_cnt);

	DRM_INFO("[%s][%d]extra latency:%d, real extra latency:%d, input_freq:%d, output_freq:%d\n",
		__func__, __LINE__, latency->video_latency_ms, latency->real_video_latency_ms,
		input_freq, output_freq);

	return 0;
}

int mtk_video_display_set_hse_ioctl(
	struct drm_device *drm_dev,
	void *data,
	struct drm_file *file_priv)
{
	struct drm_crtc *crtc = NULL;
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_tv_drm_crtc *mtk_tv_crtc = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	struct drm_mtk_tv_set_hse_enable *hse_enable = (struct drm_mtk_tv_set_hse_enable *)data;

	if (!drm_dev || !data || !file_priv)
		return -EINVAL;

	drm_for_each_crtc(crtc, drm_dev) {
		mtk_tv_crtc = to_mtk_tv_crtc(crtc);
		break;
	}
	pctx = (struct mtk_tv_kms_context *)mtk_tv_crtc->crtc_private;
	pctx_frc = &pctx->frc_priv;

	if (pctx_frc->frc_as_hse) {
		struct mtk_drm_plane *plane = 0;
		struct mtk_video_context *pctx_video = NULL;
		struct video_plane_prop *plane_props = NULL;
		unsigned int plane_idx = 0;
		size_t i = 0;

		for (i = 0; i < ARRAY_SIZE(mtk_tv_crtc->plane); ++i) {
			plane = mtk_tv_crtc->plane[i];
			if (plane->mtk_plane_type == MTK_DRM_PLANE_TYPE_VIDEO) {
				plane_idx = (unsigned int) plane->video_index;
				break;
			}
		}
		pctx_video = &pctx->video_priv;
		plane_props = (pctx_video->video_plane_properties + plane_idx);
		if (plane_props->prop_val[EXT_VPLANE_PROP_VIDEO_PLANE_TYPE]
			!= MTK_VPLANE_TYPE_MEMC) {
			DRM_ERROR("[%s, %d]: plane type is not MEMC.\n", __func__, __LINE__);
			return -EINVAL;
		}
		mtk_video_display_set_frc_freeze(pctx, plane_idx, hse_enable->enable);
	}

	return 0;
}

int mtk_tv_drm_video_set_vrr_mode_type_ioctl(
	struct drm_device *drm_dev,
	void *data,
	struct drm_file *file_priv)
{
	int ret = 0;
	enum drm_render_vrr_mode_type eVrr;

	if (!drm_dev || !data || !file_priv) {
		ret = -ENODEV;
		return ret;
	}

	eVrr = *((uint32_t *)data);
	mtk_tv_drm_video_set_vrr_mode_type(eVrr);


	return ret;
}

//-------------------------------------------------------------------------------------------------
//  Global SYSFS Functions
//-------------------------------------------------------------------------------------------------

int mtk_video_display_frc_set_debug_cmd_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int value = 0, type = 0, level = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &value);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	find = mtk_frc_get_value_from_string((char *)buf, "type", count, &type);
	if (!find) {
		DRM_ERROR("Cmdline format error, type should be set, please echo help!\n");
		return -EINVAL;
	}

	find = mtk_frc_get_value_from_string((char *)buf, "level", count, &level);
	if (!find) {
		DRM_ERROR("Cmdline format error, type should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->bDebugCmdEn = (bool)value;
	pctx_frc->debugCmdType = (uint16_t)type;
	pctx_frc->debugCmdLevel = (uint8_t)level;

	return count;
}

int mtk_video_display_frc_set_debug_cmd_show(
	struct device *dev,
	char *buf)
{
	int ret = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	ret = snprintf(buf, PAGE_SIZE, "		****  frc_debug_cmd ****\n"
				"		- Enable = %d\n"
				"		- Type   = 0x%x\n"
				"		- Level  = %d\n",
				pctx_frc->bDebugCmdEn,
				pctx_frc->debugCmdType,
				pctx_frc->debugCmdLevel);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_set_dynamic_path_log_en_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int value = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &value);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->bForceDynamicPathLogEn = (bool)value;
	_mtk_video_display_frc_set_dynamic_path_log_en(pctx, (bool)value);

	return count;
}

int mtk_video_display_frc_set_dynamic_path_log_en_show(
	struct device *dev,
	char *buf)
{
	int ret = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	ret = snprintf(buf, PAGE_SIZE, "		****  set_dynamic_path_log_en ****\n"
				"		- Log Enable = %d\n",
				pctx_frc->bForceDynamicPathLogEn);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}


int mtk_video_display_frc_force_dynamic_path_bypass_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int value = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &value);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->bForceDynamicPathBypass = (bool)value;
	_mtk_video_display_frc_force_dynamic_path_bypass(pctx, (bool)value);

	return count;
}

int mtk_video_display_frc_force_dynamic_path_bypass_show(
	struct device *dev,
	char *buf)
{
	int ret = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	ret = snprintf(buf, PAGE_SIZE, "		****  force_dynamic_path_bypass ****\n"
				"		- Force Bypass = %d\n",
				pctx_frc->bForceDynamicPathBypass);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_force_dynamic_path_select_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int value = 0, select = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &value);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	find = mtk_frc_get_value_from_string((char *)buf, "select", count, &select);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->bForceDynamicPathEn = (bool)value;
	pctx_frc->bForceDynamicPathSel = (bool)select;
	_mtk_video_display_frc_force_dynamic_path_select(pctx, (bool)value, (bool)select);

	return count;
}

int mtk_video_display_frc_force_dynamic_path_select_show(
	struct device *dev,
	char *buf)
{
	int ret = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	ret = snprintf(buf, PAGE_SIZE, "		****  force_dynamic_path_select ****\n"
				"		- Force Enable = %d\n"
				"		- Force Select = %d\n",
				pctx_frc->bForceDynamicPathEn,
				pctx_frc->bForceDynamicPathSel);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_rwdiff_frc_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int enable = 0, value = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &enable);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	find = mtk_frc_get_value_from_string((char *)buf, "value", count, &value);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->frc_user_rwdiff_en = enable;
	pctx_frc->frc_user_rwdiff = value;

	return count;
}

int mtk_video_display_frc_rwdiff_frc_show(
	struct device *dev,
	char *buf)
{
	int len = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	struct mtk_frc_dynamic_latency *platency = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;
	platency = &pctx_frc->dynamic_latency;

	len += snprintf(buf + len, PAGE_SIZE - len,
		"===============[RWdiff Info]==============\n");
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc input_freq] =            \t%d\n", platency->input_freq);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc output_freq] =           \t%d\n", platency->output_freq);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc LowLatency] =            \t%d\n", platency->bLowLatency);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc TSMode] =                \t%d\n", pctx_frc->bTsMode);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc eMEMC_support_info] =    \t%d\n", pctx_frc->eMEMC_support_info);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc photo_rw_diff] =         \t%d\n", pctx_frc->frc_photo_rw_diff);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc user_rwdiff_en] =        \t%d\n", pctx_frc->frc_user_rwdiff_en);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc user_rwdiff] =           \t%d\n", pctx_frc->frc_user_rwdiff);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc latency] =               \t%d\n", pctx_frc->frc_latency);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc rwdiff] =                \t%d\n", pctx_frc->frc_rwdiff);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"===============[RWdiff Info]==============\n");
	len += snprintf(buf + len, PAGE_SIZE - len,
		"========[Dynamic FRC Latency Info]========\n");
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc ContentW] =              \t%d\n", platency->u32ContentW);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc ContentH)] =             \t%d\n", platency->u32ContentH);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc SrcW] =                  \t%d\n", platency->u32SrcW);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc SrcH] =                  \t%d\n", platency->u32SrcH);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc SrcFps] =                \t%d\n", platency->u32SrcFps);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc MaxFps] =                \t%d\n", platency->u32MaxFps);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc Hbbtv] =                 \t%d\n", platency->bHbbtv);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc Adaptive] =              \t%d\n", platency->bAdaptive);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc B2r] =                   \t%d\n", platency->bB2r);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc Mse] =                   \t%d\n", platency->bMse);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc TempW] =                 \t%d\n", platency->u32TempW);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc TempH] =                 \t%d\n", platency->u32TempH);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc RelFps] =                \t%d\n", platency->u32RelFps);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc BypassMemc] =            \t%d\n", platency->bBypassMemc);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"========[Dynamic FRC Latency Info]========\n");

	return ((len < 0) || (len > PAGE_SIZE)) ? -EINVAL : len;
}

int mtk_video_display_frc_set_extra_latency_info(
	struct device *dev,
	const char *buf)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	uint64_t enable = 0;
	uint64_t value = 0;
	int ret = 0;

	if ((dev == NULL) || (buf == NULL)) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	pctx = dev_get_drvdata(dev);
	if (pctx == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	pctx_frc = &pctx->frc_priv;
	if (pctx_frc == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	ret = mtk_render_common_parser_cmd_int(buf, "enable", &enable);
	if (ret) {
		DRM_ERROR("[%s, %d]: fail parser enable, ret=%d\n", __func__, __LINE__, ret);
		return -EINVAL;
	}
	pctx_frc->frc_user_extra_delay_enable = (bool)enable;

	ret = mtk_render_common_parser_cmd_int(buf, "val", &value);
	if (ret) {
		DRM_ERROR("[%s, %d]: fail parser enable, ret=%d\n", __func__, __LINE__, ret);
		return -EINVAL;
	}
	pctx_frc->frc_user_extra_delay_num = (uint8_t)value;


	// check not support A2DP
	if (pctx_frc->frc_a2dp_enable == FALSE)
		return 0;

	if (pctx_frc->frc_user_extra_delay_enable == false) {
		mtk_video_display_frc_set_rv55_extra_delay_num(pctx, 0);
		pctx_frc->frc_user_extra_delay_num = 0;
	} else {
		mtk_video_display_frc_set_rv55_extra_delay_num(pctx,
			pctx_frc->frc_user_extra_delay_num);
	}

	return 0;
}

int mtk_video_display_frc_get_extra_latency_info(
	struct device *dev,
	char *buf)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	struct mtk_frc_shm_info *frc_shm_info = NULL;
	int ret = 0;

	if ((buf == NULL) || (dev == NULL)) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	pctx = dev_get_drvdata(dev);
	if (pctx == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	pctx_frc = &pctx->frc_priv;
	if (pctx_frc == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	frc_shm_info = (struct mtk_frc_shm_info *) pctx_frc->frc_shm_virAdr;
	if (frc_shm_info == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	ret = snprintf(buf, PAGE_SIZE,
		"[frc extra latency enable] = %d\n"
		"[frc extra latency delay] = %d\n"
		"[frc real latency delay] = %d\n",
		pctx_frc->frc_user_extra_delay_enable,
		pctx_frc->frc_user_extra_delay_num,
		frc_shm_info->extra_delay_num);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_perfect_vgsync_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	int enable = 0;
	bool find = 0;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	find = mtk_frc_get_value_from_string((char *)buf, "enable", count, &enable);
	if (!find) {
		DRM_ERROR("Cmdline format error, enable should be set, please echo help!\n");
		return -EINVAL;
	}

	pctx_frc->frc_perfect_vgsync = enable;

	return count;
}

int mtk_video_display_frc_perfect_vgsync_show(
	struct device *dev,
	char *buf)
{
	int len = 0;
	uint8_t i = 0;

	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	len += snprintf(buf + len, PAGE_SIZE - len,
		"========[FRC Perfect Vsync Info]========\n");
	len += snprintf(buf + len, PAGE_SIZE - len,
		"[frc PerfectVgsyncEn] =            \t%d\n", pctx_frc->frc_perfect_vgsync);
	for (i = 0; i < PQU_RENDER_DS_NUM; i++)
		len += snprintf(buf + len, PAGE_SIZE - len,
			"[frc MLAdr][%d] =            \t%llx\n", i, pctx_frc->frc_ml_info[i].u64Pa);
	len += snprintf(buf + len, PAGE_SIZE - len,
		"========[Dynamic FRC Latency Info]========\n");

	return ((len < 0) || (len > PAGE_SIZE)) ? -EINVAL : len;
}

int mtk_video_display_frc_check_motion_status_store(
	struct device *dev,
	const char *buf,
	unsigned int count)
{
	static const char _sep[] = " ";
	uint16_t TokenCnt;
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	static uint16_t param[_DATA_LEN] = {DEFAULT_VALUE};

	if (dev == NULL || buf == NULL) {
		DRM_ERROR("[%s, %d] dev or buf is null.\n", __func__, __LINE__);
		return -EINVAL;
	}

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	TokenCnt = 0;
	mtk_frc_get_all_value_from_string((char *)buf, (char *)_sep, count, param, &TokenCnt);
	_mtk_video_display_frc_check_motion_status(param, TokenCnt, pctx_frc);

	return count;
}

int mtk_video_display_frc_get_algo_info(struct device *dev, char *buf)
{
	#define MFC_LEVL_MASK(X) ((X) & (0x000F))
	#define MFC_DEBLUR_MASK(X) (((X) & (0x0F00))>>8)
	#define MFC_DEJUDDER_MASK(X) (((X) & (0xF000))>>12)
	#define MFC_FRC_RATE(X) ((X) & (0x000F))
	#define MFC_55_64_MODE(X) (((X) & (0x8000))>>15)
	#define MFC_GAME_MASK(X) (((X) & (0x0800))>>11)
	#define MFC_GAME_MEMC_MASK(X) (((X) & (0x4000))>>14)

	int ret = 0;
	struct hwregFrcDebugInfo stFrcDebugInfo = {0};
	bool bMemc_Mode = 0;
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	if (buf == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	memset(&stFrcDebugInfo, 0, sizeof(struct hwregFrcDebugInfo));

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);

	if (MFC_MEMC_MODE(stFrcDebugInfo.BlendingTopMux) == ENABLE_MEMC_PATH)
		bMemc_Mode = TRUE;
	else
		bMemc_Mode = FALSE;

	ret = snprintf(buf, PAGE_SIZE, "		FRC algo status:\n\n"
				"		****  frc_rv55/r2_status ****\n"
				"		- RV55/R2	Ver = %X\n"
				"		- Init		State = %s\n"
				"		- ConnCop	State = %d\n"
				"		- MFC		VGS = %d\n"
				"		- MFC		Level = %d\n"
				"		- MFC		Deblur = %d\n"
				"		- MFC		Dejudder = %d\n"
				"		- MFC		SW Scene = %s (%d)\n"
				"		- MFC		Game = %d\n"
				"		- MFC		GameMemc = %d\n"
				"		- MFC		In/Out Timing = %X\n"
				"		- MFC		In/Out Panel Timing = %X\n"
				"		- MFC		MEMC Mode = %s\n"
				"		- MFC		FRC_RATE = %X\n"
				"		- MFC		55/64_Mode = %s\n"
				"		- MFC		Delay = %d\n"
				"		- MFC		Support Type : %d\n"
				"		- MFC		InTimingDetect : %d\n",
				stFrcDebugInfo.Rv55Ver,
				FrcInitStatus2Str(pctx_frc->memc_InitState),
				pctx_frc->bConnCop,
				mtk_video_display_frc_get_vgs_onoff(),
				MFC_LEVL_MASK(stFrcDebugInfo.MfcLevel),
				MFC_DEBLUR_MASK(stFrcDebugInfo.MfcUserLevel),
				MFC_DEJUDDER_MASK(stFrcDebugInfo.MfcUserLevel),
				SenceToString(pctx_frc->frc_scene), pctx_frc->frc_scene,
				MFC_GAME_MASK(stFrcDebugInfo.MfcBWAndGameMode),
				MFC_GAME_MEMC_MASK(stFrcDebugInfo.MfcBWAndGameMode),
				stFrcDebugInfo.MfcIOTiming,
				stFrcDebugInfo.MfcIOPanelTiming,
				bMemc_Mode ? "Enable" : "DISABLE",
				MFC_FRC_RATE(stFrcDebugInfo.MfcBWAndGameMode),
				MFC_55_64_MODE(stFrcDebugInfo.Mfc55_64Mode) ? "55" : "64",
				pctx_frc->frc_latency,
				pctx_frc->eMEMC_support_info,
				stFrcDebugInfo.InputTimingDetect
	);

	#undef MFC_LEVL_MASK
	#undef MFC_DEJUDDER_MASK
	#undef MFC_DEBLUR_MASK
	#undef MFC_FRC_RATE
	#undef MFC_55_64_MODE
	#undef MFC_GAME_MASK
	#undef MFC_GAME_MEMC_MASK

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_get_game_memc_cadenceInfo(struct device *dev, char *buf)
{
#define MFC_CADENCENUM_MASK(X) ((X) & (0x000F))
#define MFC_CADENCESUPPORT_MASK(X) (((X) & (0x0010))>>4)

	int ret = 0;
	struct hwregFrcDebugInfo stFrcDebugInfo = {0};
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;
	uint8_t CadenceNum = 0;

	if (buf == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	memset(&stFrcDebugInfo, 0, sizeof(struct hwregFrcDebugInfo));

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);

	CadenceNum = MFC_CADENCENUM_MASK(stFrcDebugInfo.FilmStatusData);

	ret = snprintf(buf, PAGE_SIZE, "		FRC Game Memc cadence Info:\n"
				"		- MFC		Cadence Support in Game MEMC: %s\n"
				"		- MFC		Cadence : %s\n",
				MFC_CADENCESUPPORT_MASK(stFrcDebugInfo.FilmStatusData) ? "Y" : "N",
				FilmModeToString(CadenceNum)
	);

#undef MFC_CADENCESUPPORT_MASK
#undef MFC_CADENCENUM_MASK

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

int mtk_video_display_frc_get_frc_algo_version(struct device *dev, char *buf)
{
	int ret = 0;
	struct hwregFrcDebugInfo stFrcDebugInfo = {0};
	struct mtk_tv_kms_context *pctx = NULL;
	struct mtk_frc_context *pctx_frc = NULL;

	if (buf == NULL) {
		DRM_ERROR("[%s, %d]: Pointer is NULL!\n", __func__, __LINE__);
		return -EINVAL;
	}

	memset(&stFrcDebugInfo, 0, sizeof(struct hwregFrcDebugInfo));

	pctx = dev_get_drvdata(dev);
	pctx_frc = &pctx->frc_priv;

	drv_hwreg_render_frc_get_debug_info(&stFrcDebugInfo);

	ret = snprintf(buf, PAGE_SIZE, "		FRC algo status:\n\n"
				"		****  frc_rv55/r2_status ****\n"
				"		- RV55/R2	Ver = %X\n",
				stFrcDebugInfo.Rv55Ver
	);

	return ((ret < 0) || (ret > PAGE_SIZE)) ? -EINVAL : ret;
}

void mtk_video_display_frc_set_repeat_mode(bool enable)
{
	struct hwregFrcRepeatModeEn paramFrcRepeatModeEn;
	struct hwregOut paramOut;
	struct reg_info *regTbl;

	regTbl = malloc(sizeof(struct reg_info)*REG_NUM_MAX);
	if (regTbl == NULL) {
		DRM_ERROR("[%s,%5d] NULL POINTER\n", __func__, __LINE__);
		return;
	}

	memset(&paramFrcRepeatModeEn, 0, sizeof(struct hwregFrcRepeatModeEn));
	memset(&paramOut, 0, sizeof(struct hwregOut));
	memset(regTbl, 0, sizeof(struct reg_info)*REG_NUM_MAX);

	paramOut.reg = regTbl;
	paramFrcRepeatModeEn.RIU = 1;
	paramFrcRepeatModeEn.bEnable = enable;

	drv_hwreg_render_frc_set_repeat_mode(
		paramFrcRepeatModeEn, &paramOut);

	if (paramOut.reg)
		free(paramOut.reg);
}

void mtk_video_display_frc_set_pattern(
		struct mtk_tv_kms_context *pctx,
		struct mtk_frc_pattern_info *pstPatternInfo)
{
	struct mtk_frc_context *pctx_frc = &pctx->frc_priv;

	memcpy(&pctx_frc->stPatInfo, pstPatternInfo, sizeof(struct mtk_frc_pattern_info));

	if ((pctx_frc->stPatInfo.sel >= MEMC_PAT_IPM_DYNAMIC) &&
		(pctx_frc->stPatInfo.sel <= MEMC_PAT_OPM_FORCE_TIMING)) {
		pstPatternInfo->bForce = TRUE;
		pctx_frc->stPatInfo.bForce = TRUE;
	} else {
		pstPatternInfo->bForce = FALSE;
		pctx_frc->stPatInfo.bForce = FALSE;
	}

	switch (pctx_frc->stPatInfo.sel) {
	case MEMC_PAT_OPM:
		_mtk_video_display_frc_set_opm_pattern(pctx, pstPatternInfo);
		break;
	case MEMC_PAT_IPM:
		_mtk_video_display_frc_set_ipm_pattern(pctx, pstPatternInfo);
		break;
	case MEMC_PAT_IPM_DYNAMIC: // Force Timing Onle for Debug
		_mtk_video_display_frc_set_ipm_pattern(pctx, pstPatternInfo);
		break;
	case MEMC_PAT_IPM_STATIC: // Force Timing Onle for Debug
		_mtk_video_display_frc_set_ipm_pattern(pctx, pstPatternInfo);
		pctx_frc->stPatInfo.frc_pattern_trig_cnt = _PAT_TRIG_CNT;
		break;
	case MEMC_PAT_IPM_FORCE_TIMING:
		_mtk_video_display_frc_set_ipm_pattern(pctx, pstPatternInfo);
		break;
	case MEMC_PAT_OPM_FORCE_TIMING:
		_mtk_video_display_frc_set_opm_pattern(pctx, pstPatternInfo);
		break;
	default:
		break;
	}
}

// Please add a new function for another debug requirement.
int mtk_video_display_frc_get_cmd_history(char *buf)
{
	int len = 0;
	int histroyIdx = 0;

	for (histroyIdx = 0; histroyIdx < MAX_CMD_HISTORY_SIZE; ++histroyIdx) {
		if (gFrcDbgHistroy.dbgHistroy[histroyIdx].bRecorded) {
			pr_crit("[FRC][%02d][%s]:MemcPath(%d),"
				"cmd[0x%02x 0x%04x 0x%04x 0x%04x 0x%04x][%d][%s]\n",
				histroyIdx,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].time_buffer,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].bIsMemcPath,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.CmdID,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.D1,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.D2,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.D3,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.D4,
				gFrcDbgHistroy.dbgHistroy[histroyIdx].sendCmdRet,
				FrcCmd2Str(gFrcDbgHistroy.dbgHistroy[histroyIdx].cmd.CmdID));
		}
	}

	len += snprintf(buf + len, PAGE_SIZE, "\nFormat:[Idx][timeStamp]MemcPath(0/1),"
		"cmd[ID + 4 DATA][sendCmdRet][cmdName].\n");
	len += snprintf(buf + len, PAGE_SIZE, "Dump FRC command history done\n");

	return ((len < 0) || (len > PAGE_SIZE)) ? -EINVAL : len;
}

int mtk_video_display_frc_reset_cmd_history(uint16_t reset)
{
	if (reset)
		memset(&gFrcDbgHistroy, 0x0, sizeof(struct mtk_frc_debug_info_history));

	return 0;
}

