// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * drivers/amlogic/media/deinterlace/detect3d.c
 *
 * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 */

#include <linux/kernel.h>
#include <linux/module.h>
/* Amlogic Headers */

#include <linux/amlogic/media/frame_provider/tvin/tvin.h>
#include <linux/amlogic/iomap.h>
/* media module used media/registers/cpu_version.h since kernel 5.4 */
#include <linux/amlogic/media/registers/cpu_version.h>
#include "register.h"
#include "register_nr4.h"
#include "detect3d.h"

#include "di_pqa.h"
/*******************Local defines**********************/
#define DET3D_REG_NUM				9
/* the number of total register */
#define FRAME_MAX				(1<<15)
/* Frame cnt max value */

#define LR_FMT_SCORE_MAX			(2047)
/* range of score in LR detection */
#define LR_FMT_SCORE_MIN			(-2047)
#define TB_FMT_SCORE_MAX			(2047)
/* range of score in TB detection */
#define TB_FMT_SCORE_MIN			(-2047)
#define INTERLACE_FMT_SCORE_MAX			(63)
/* range of score in interlace detection */
#define INTERLACE_FMT_SCORE_MIN			(-63)
#define CHESSBOARD_FMT_SCORE_MAX			(63)
/* range of score in chessboard detection */
#define CHESSBOARD_FMT_SCORE_MIN			(-63)

#define LR_SCORE_LOWER_LIMIT			128
/* if score > this limit, input is sure to be LR format */
#define TB_SCORE_LOWER_LIMIT			128
/* if score > this limit, input is sure to be TB format */
#define INTERLACE_SCORE_LOWER_LIMIT		30
/* if score > this limit, input is sure to be interlace format */
#define CHESSBOARD_SCORE_LOWER_LIMIT		30
/* if score > this limit, input is sure to be chessboard format */

#define NOT_LR_SCORE_UPPER_LIMIT		(-24)
/* if score < this limit, input is sure to be not LR format */
#define NOT_TB_SCORE_UPPER_LIMIT		(-24)
/* if score < this limit, input is sure to be not TB format */
#define NOT_INTERLACE_SCORE_UPPER_LIMIT		(-12)
/* if score < this limit, input is sure to be not interlace format */
#define NOT_CHESSBOARD_SCORE_UPPER_LIMIT		(-12)
/* if score < this limit, input is sure to be not chessboard format */

#define LR_SYMMETRY_LOWER_LIMIT		44
/* if > this limit,input is translational-symmetry in left half and right half */
#define TB_SYMMETRY_LOWER_LIMIT		44
/* if > this limit,input is translational-symmetry in top half and bottom half */

static int chessbd_vrate = 29;
static bool det3d_debug;

/***************************Local variables **********************************/
const unsigned int det3d_table[DET3D_REG_NUM] = {
	0x00000002, 0x00000027, 0x00000065, 0x00444400,
	0xc8404733, 0x00060606, 0x00060606, 0x0000002a,
	0x0c0c0c0a
};

static struct det3d_info_s det3d_info = {
	-1,				/* nfrm */
	DET3D_FMT_NULL,			/* tfw_det3d_fmt */
	0,				/* score_3d_lr */
	0,				/* score_3d_tb */
	{0, 0, 0, 0, 0, 0, 0, 0},	/* tscore_3d_lr[8] */
	{0, 0, 0, 0, 0, 0, 0, 0},	/* tscore_3d_tb[8] */
	0,				/* tscore_3d_lr_accum */
	0,				/* tscore_3d_tb_accum */
	0,				/* score_3d_chs */
	0,				/* score_3d_int */
	{0, 0, 0, 0, 0, 0, 0, 0},	/* chs_valid_his[8] */
	{0, 0, 0, 0, 0, 0, 0, 0},	/* int_valid_his[8] */
};

/*
 * Enable and Disable det3d
 * flag == true, enable det3d; flag == false, disable det3d;
 */
void det3d_config(bool flag)
{
	int i;

	if (flag == 1) {
		/* disable 3D detection */
		DI_Wr_reg_bits(NR2_SW_EN, 0, DET3D_EN_BIT, DET3D_EN_WID);

		/* initialize the registers */
		for (i = 0; i < DET3D_REG_NUM; i++)
			DI_Wr((DET3D_BASE_ADD + i), det3d_table[i]);

		/* Det 3D interrupt enble */
		DI_Wr_reg_bits(DET3D_MOTN_CFG, 1,
DET3D_INTR_EN_BIT, DET3D_INTR_EN_WID);
		/* enable 3D detection */
		if (cpu_after_eq(MESON_CPU_MAJOR_ID_TXLX))
			DI_Wr_reg_bits(NR4_TOP_CTRL, 1, 14, 1);
		else
			DI_Wr_reg_bits(NR2_SW_EN, 1,
				DET3D_EN_BIT, DET3D_EN_WID);
	} else {
		/* Det 3D interrupt disable */
		DI_Wr_reg_bits(DET3D_MOTN_CFG, 0,
DET3D_INTR_EN_BIT, DET3D_INTR_EN_WID);
		/* disable 3D detection */
		if (cpu_after_eq(MESON_CPU_MAJOR_ID_TXLX))
			DI_Wr_reg_bits(NR4_TOP_CTRL, 0, 14, 1);
		else
			DI_Wr_reg_bits(NR2_SW_EN, 0,
				DET3D_EN_BIT, DET3D_EN_WID);
		memset(&det3d_info, 0, sizeof(det3d_info));
	}
}

/*
 * Read cbus reg signed bits
 * length must be <= 31
 */

int read_cbus_reg_signed_bits(unsigned int reg, unsigned int startbit,
							unsigned int length)
{
	unsigned int val, tmp = 1;

	if (length > 31)
		length = 31;
	val = Rd_reg_bits(reg, startbit, length);
	tmp = tmp << (length - 1);

	/* pr_dbg("len = %d, unsigned value = %d,
	 * signed value = %d",length,val,((val >= tmp )?
	 * (val - (tmp << 1)):val));
	 */
	return (int)((val >= tmp)?(val - (tmp << 1)):val);
}

/*
 * accumulate the frame to frame scores
 */
static void det3d_accumulate_score(int lr_score, int tb_score,
				int int_score, int chessbd_score)
{
	int tmp1 = 0;
	int tmp2 = 0;
	int m;

	/* accumulate the frame to frame scores */
	if (det3d_info.nfrm < 1) {
		det3d_info.score_3d_lr = lr_score;
		det3d_info.score_3d_tb = tb_score;

		/* initialize the temporary score buffer */
		det3d_info.tscore_3d_lr[0] = lr_score;
		det3d_info.tscore_3d_tb[0] = tb_score;
		det3d_info.chs_valid_his[0] = chessbd_score;
		det3d_info.int_valid_his[0] = int_score;
		det3d_info.tscore_3d_lr_accum = (lr_score <= 0) ? 1 : 0;
		det3d_info.tscore_3d_tb_accum = (tb_score <= 0) ? 1 : 0;
	} else {
		det3d_info.score_3d_lr = det3d_info.score_3d_lr + lr_score;
		det3d_info.score_3d_tb = det3d_info.score_3d_tb + tb_score;

		for (m = 7; m > 0; m--) {
			det3d_info.tscore_3d_lr[m]  =
det3d_info.tscore_3d_lr[m - 1]; /* optimized needed??? */
			det3d_info.tscore_3d_tb[m]  =
det3d_info.tscore_3d_tb[m - 1];
			det3d_info.chs_valid_his[m] =
(chessbd_score == det3d_info.chs_valid_his[0]) ?
det3d_info.chs_valid_his[m - 1]:0;
			det3d_info.int_valid_his[m] =
(int_score == det3d_info.int_valid_his[0]) ? det3d_info.int_valid_his[m - 1]:0;
		}
		/* -------------------------------------------- */
		/* detection result agreement in frame to frame */
		/* -------------------------------------------- */
		det3d_info.tscore_3d_lr[0] = lr_score;
		det3d_info.tscore_3d_tb[0] = tb_score;
		det3d_info.tscore_3d_lr_accum =
det3d_info.tscore_3d_lr_accum + (lr_score <= 0) -
(det3d_info.tscore_3d_lr[5] <= 0);
		det3d_info.tscore_3d_tb_accum =
det3d_info.tscore_3d_tb_accum + (tb_score <= 0) -
(det3d_info.tscore_3d_tb[5] <= 0);
		/* Clip to s12 */
		det3d_info.score_3d_lr =
(det3d_info.score_3d_lr > LR_FMT_SCORE_MAX) ? 1800 : det3d_info.score_3d_lr;
		det3d_info.score_3d_lr =
(det3d_info.score_3d_lr < LR_FMT_SCORE_MIN) ? -1800 : det3d_info.score_3d_lr;
		det3d_info.score_3d_tb =
(det3d_info.score_3d_tb > TB_FMT_SCORE_MAX) ? 1800 : det3d_info.score_3d_tb;
		det3d_info.score_3d_tb =
(det3d_info.score_3d_tb < TB_FMT_SCORE_MIN) ? -1800 : det3d_info.score_3d_tb;

		/* ------------------------------------- */
		/* Chessboard frame to frame agreement */
		/* ------------------------------------- */
		det3d_info.score_3d_chs =
((chessbd_score == det3d_info.chs_valid_his[0])) ? det3d_info.score_3d_chs : 0;
		/* det3d_info.score_3d_int    =
		 * ((int_score == det3d_info.int_valid_his[0]))?
		 * det3d_info.score_3d_int: 0;
		 */
		det3d_info.chs_valid_his[0] = chessbd_score;
		det3d_info.int_valid_his[0] = int_score;

		for (m = 0; m < 8; m++) {
			tmp1 = tmp1 + (det3d_info.chs_valid_his[m]);
			tmp2 = tmp2 + (det3d_info.int_valid_his[m]);
		}
		det3d_info.score_3d_chs = det3d_info.score_3d_chs + tmp1;
		det3d_info.score_3d_int = det3d_info.score_3d_int + tmp2;
		if (det3d_debug)
			pr_info("%s input(%d,%d),output (%d,%d).\n",
__func__, chessbd_score, int_score,
det3d_info.score_3d_chs, det3d_info.score_3d_int);
		/* clipping to s7 */
		det3d_info.score_3d_chs =
(det3d_info.score_3d_chs > CHESSBOARD_FMT_SCORE_MAX) ?
CHESSBOARD_FMT_SCORE_MAX : det3d_info.score_3d_chs;
		det3d_info.score_3d_chs =
(det3d_info.score_3d_chs < CHESSBOARD_FMT_SCORE_MIN) ?
CHESSBOARD_FMT_SCORE_MIN : det3d_info.score_3d_chs;
		det3d_info.score_3d_int =
(det3d_info.score_3d_int > INTERLACE_FMT_SCORE_MAX) ?
INTERLACE_FMT_SCORE_MAX : det3d_info.score_3d_int;
		det3d_info.score_3d_int =
(det3d_info.score_3d_int < INTERLACE_FMT_SCORE_MIN) ?
INTERLACE_FMT_SCORE_MIN : det3d_info.score_3d_int;
	}

}

/*
 * detect 3D format
 * execute one or more frame after init;
 */
enum tvin_trans_fmt det3d_fmt_detect(void)
{
	/* FW registers */
	int chessbd_hor_rate = 31;/* 8bits: norm to 16 */
	/* int chessbd_ver_rate = 31;// 8bits: norm to 16 */
	int chessbd_hor_thrd = 4; /* 8bits: */
	int chessbd_ver_thrd = 4; /* 8bits: */

	/* local FW variables */
	int m;
	int tmp_sp_lr, tmp_sp_tb;
	int tmp_lr, tmp_tb, tmp1, tmp2;
	int tmp_chs, tmp_int;
	int tmp_symtc_lr, tmp_symtc_tb, tmp_frmstill;
	int chessbd_hor_valid, chessbd_ver_valid;
	/* int dump_valid = 0; */
	/* static int call_first = 1; */

	if (det3d_info.nfrm < (FRAME_MAX))
		det3d_info.nfrm++;
	else
		det3d_info.nfrm = 256; /* reset to smaller number */

	/* Split line contribution */
	tmp_sp_lr =
Rd_reg_bits(DET3D_RO_SPLT_HT,
	DET3D_SPLIT_HT_VALID_BIT, DET3D_SPLIT_HT_VALID_WID)
+ Rd_reg_bits(DET3D_RO_SPLT_HB,
	DET3D_SPLIT_HB_VALID_BIT, DET3D_SPLIT_HB_VALID_WID);
	tmp_sp_tb =
Rd_reg_bits(DET3D_RO_SPLT_VL,
	DET3D_SPLIT_VL_VALID_BIT, DET3D_SPLIT_VL_VALID_WID)
+ Rd_reg_bits(DET3D_RO_SPLT_VR,
	DET3D_SPLIT_VR_VALID_BIT, DET3D_SPLIT_VR_VALID_WID);

	/* protect static graphics pattern */
	if ((tmp_sp_lr == 2) && (tmp_sp_tb == 2)) {
		tmp_sp_lr = -1;
		tmp_sp_tb = -1;
		/* -1: bias towards 2D; 0: keep previous status */
	}

	/* 8x8 statistics scores for being LR and TB */
	tmp_lr = tmp_sp_lr
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_LUMA_LR,
	DET3D_LUMA_LR_SUM_BIT, DET3D_LUMA_LR_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_CHRV_LR,
	DET3D_CHRV_LR_SUM_BIT, DET3D_CHRV_LR_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_CHRU_LR,
	DET3D_CHRU_LR_SUM_BIT, DET3D_CHRU_LR_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_HEDG_LR,
	DET3D_HEDG_LR_SUM_BIT, DET3D_HEDG_LR_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_VEDG_LR,
	DET3D_VEDG_LR_SUM_BIT, DET3D_VEDG_LR_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_MOTN_LR,
	DET3D_MOTN_LR_SUM_BIT, DET3D_MOTN_LR_SUM_WID);

	tmp_tb = tmp_sp_tb
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_LUMA_TB,
	DET3D_LUMA_TB_SUM_BIT, DET3D_LUMA_TB_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_CHRV_TB,
	DET3D_CHRV_TB_SUM_BIT, DET3D_CHRV_TB_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_CHRU_TB,
	DET3D_CHRU_TB_SUM_BIT, DET3D_CHRU_TB_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_HEDG_TB,
	DET3D_HEDG_TB_SUM_BIT, DET3D_HEDG_TB_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_VEDG_TB,
	DET3D_VEDG_TB_SUM_BIT, DET3D_VEDG_TB_SUM_WID)
+ read_cbus_reg_signed_bits(DET3D_RO_MAT_MOTN_TB,
	DET3D_MOTN_TB_SUM_BIT, DET3D_MOTN_TB_SUM_WID);

	/* 8x8 statistics for being purely symmetrical */
	tmp_symtc_lr = 0;
	tmp_symtc_tb = 0;

	for (m = 0; m < 8; m++) {
		tmp_symtc_lr = tmp_symtc_lr
+ Rd_reg_bits(DET3D_RO_MAT_LUMA_LR, (DET3D_LUMA_LR_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_CHRU_LR, (DET3D_CHRU_LR_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_CHRV_LR, (DET3D_CHRV_LR_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_HEDG_LR, (DET3D_HEDG_LR_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_VEDG_LR, (DET3D_VEDG_LR_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_MOTN_LR, (DET3D_MOTN_LR_SYMTC_BIT + m), 1);
		tmp_symtc_tb = tmp_symtc_tb
+ Rd_reg_bits(DET3D_RO_MAT_LUMA_TB, (DET3D_LUMA_TB_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_CHRU_TB, (DET3D_CHRU_TB_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_CHRV_TB, (DET3D_CHRV_TB_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_HEDG_TB, (DET3D_HEDG_TB_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_VEDG_TB, (DET3D_VEDG_TB_SYMTC_BIT + m), 1)
+ Rd_reg_bits(DET3D_RO_MAT_MOTN_TB, (DET3D_MOTN_TB_SYMTC_BIT + m), 1);
	}

	tmp_symtc_lr = tmp_symtc_lr > LR_SYMMETRY_LOWER_LIMIT;
	tmp_symtc_tb = tmp_symtc_tb > TB_SYMMETRY_LOWER_LIMIT;
	tmp_frmstill = Rd_reg_bits(DET3D_RO_FRM_MOTN,
DET3D_FRAME_MOTION_BIT, DET3D_FRAME_MOTION_WID) < 100;

	/* if FrmStill && score>=0, force score decrease 1 */
	if (tmp_frmstill && (tmp_lr >= -1))
		tmp_lr = tmp_lr - 1;
	if (tmp_frmstill && (tmp_tb >= -1))
		tmp_tb = tmp_tb - 1;

	/* if FrmStill && symtc && score>=0, force score to 0/-1 */
	/* if (tmp_frmstill&&tmp_symtc_lr&&(tmp_lr>=0)) tmp_lr = 0; */
	/* if (tmp_frmstill&&tmp_symtc_tb&&(tmp_tb>=0)) tmp_tb = 0; */

	/* ChessBoard/interlace mode score */
	tmp1 = Rd_reg_bits(DET3D_RO_DET_CB_HOR,
DET3D_CHESSBOARD_HOR_VALUE_BIT, DET3D_CHESSBOARD_HOR_VALUE_WID);
	tmp2 = Rd_reg_bits(DET3D_RO_DET_CB_HOR,
DET3D_CHESSBOARD_NHOR_VALUE_BIT, DET3D_CHESSBOARD_NHOR_VALUE_WID);
	chessbd_hor_valid = tmp1 >
(((tmp2 * chessbd_hor_rate) >> 4) + chessbd_hor_thrd);

	tmp1 = Rd_reg_bits(DET3D_RO_DET_CB_VER,
DET3D_CHESSBOARD_VER_VALUE_BIT, DET3D_CHESSBOARD_VER_VALUE_WID);
	tmp2 = Rd_reg_bits(DET3D_RO_DET_CB_VER,
DET3D_CHESSBOARD_NVER_VALUE_BIT, DET3D_CHESSBOARD_NVER_VALUE_WID);
	chessbd_ver_valid = tmp1 > (((tmp2 * chessbd_vrate) >> 4) +
chessbd_ver_thrd);

	tmp_chs = chessbd_hor_valid & chessbd_ver_valid;
	tmp_chs = 2 * tmp_chs - 1;
	tmp_int = (chessbd_hor_valid == 0) & chessbd_ver_valid;
	tmp_int = 2 * tmp_int - 1;

	det3d_accumulate_score(tmp_lr, tmp_tb, tmp_int, tmp_chs);

	/* quick reset to get faster converse */
	if (((tmp_lr > 8) && (tmp_tb < -8) &&
(det3d_info.score_3d_lr < -127)) || ((det3d_info.tscore_3d_lr_accum >= 4) &&
(det3d_info.score_3d_lr > 63)))
		det3d_info.score_3d_lr = 0;

	if (((tmp_tb > 8) && (tmp_lr < -8) &&
(det3d_info.score_3d_tb < -127)) || ((det3d_info.tscore_3d_tb_accum >= 4) &&
(det3d_info.score_3d_tb > 63)))
		det3d_info.score_3d_tb = 0;
#if 0 /*mark chessboard 3d detection*/
	if (det3d_info.score_3d_chs > CHESSBOARD_SCORE_LOWER_LIMIT &&
det3d_info.score_3d_int < NOT_INTERLACE_SCORE_UPPER_LIMIT) {
		det3d_info.tfw_det3d_fmt = TVIN_TFMT_3D_DET_CHESSBOARD;
		det3d_info.score_3d_lr = 0;
		det3d_info.score_3d_tb = 0;
	} else
#endif
	if ((det3d_info.score_3d_int > INTERLACE_SCORE_LOWER_LIMIT) &&
(det3d_info.score_3d_chs < NOT_CHESSBOARD_SCORE_UPPER_LIMIT)) {
		det3d_info.tfw_det3d_fmt = TVIN_TFMT_3D_DET_INTERLACE;
		det3d_info.score_3d_lr = 0;
		det3d_info.score_3d_tb = 0;
	} else if ((det3d_info.score_3d_lr > LR_SCORE_LOWER_LIMIT) &&
(det3d_info.score_3d_lr > det3d_info.score_3d_tb)) {
		det3d_info.tfw_det3d_fmt = TVIN_TFMT_3D_LRH_OLOR;
	} else if ((det3d_info.score_3d_tb > TB_SCORE_LOWER_LIMIT) &&
(det3d_info.score_3d_lr < det3d_info.score_3d_tb)) {
		det3d_info.tfw_det3d_fmt = TVIN_TFMT_3D_TB;
	} else if ((det3d_info.score_3d_tb < NOT_LR_SCORE_UPPER_LIMIT) &&
(det3d_info.score_3d_lr < NOT_TB_SCORE_UPPER_LIMIT)) {
		det3d_info.tfw_det3d_fmt = TVIN_TFMT_2D;
	} else {
		/* keep previous status */

		if ((det3d_info.score_3d_lr > LR_SCORE_LOWER_LIMIT) &&
(det3d_info.score_3d_tb > TB_SCORE_LOWER_LIMIT)) {
			/* if both LR and TB detected, reset the score */
			det3d_info.score_3d_lr = 0;
			det3d_info.score_3d_tb = 0;
		}
	}

	if (det3d_debug) {
		pr_info("det3d:frame = %d, 3D_fmt = %d, score_3d_lr = %d,",
det3d_info.nfrm, det3d_info.tfw_det3d_fmt, det3d_info.score_3d_lr);
		pr_info("score_3d_tb = %d, score_3d_int = %d, score_3d_chs = %d",
det3d_info.score_3d_tb, det3d_info.score_3d_int, det3d_info.score_3d_chs);
	}
	return det3d_info.tfw_det3d_fmt;
}

module_param(chessbd_vrate, int, 0644);
MODULE_PARM_DESC(chessbd_vrate, "\n the chessboard 3d fmt vertical rate\n");
module_param(det3d_debug, bool, 0644);
MODULE_PARM_DESC(det3d_debug, "\n print the information of 3d detection\n");

static const struct detect3d_op_s di_ops_3d = {
	.det3d_config		= det3d_config,
	.det3d_fmt_detect	= det3d_fmt_detect,
	/*.module_para		= dim_seq_file_module_para_3d,*/
};

bool di_attach_ops_3d(const struct detect3d_op_s **ops)
{
	#if 0
	if (!ops)
		return false;

	memcpy(ops, &di_pd_ops, sizeof(struct pulldown_op_s));
	#else
	*ops = &di_ops_3d;
	#endif

	return true;
}
EXPORT_SYMBOL(di_attach_ops_3d);
