// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * drivers/amlogic/media/enhancement/amvecm/hdr/gamut_convet.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.
 *
 */

#ifndef CONFIG_AMLOGIC_ZAPPER_CUT
#include <linux/types.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>
#include <linux/amlogic/media/amvecm/amvecm.h>
#include <linux/amlogic/media/amvecm/hdr10_tmo_alg.h>
#include "am_hdr10_tm.h"
#include "../set_hdr2_v0.h"
#include "../s5_set_hdr2_v0.h"
#include "am_hdr10_tmo_fw.h"

unsigned int hdr10_tm_dbg;
module_param(hdr10_tm_dbg, uint, 0664);
MODULE_PARM_DESC(hdr10_tm_dbg, "HDR10 tone mapping dbg\n");

#define pr_hdr_tm(fmt, args...)\
	do {\
		if (hdr10_tm_dbg)\
			pr_info(fmt, ## args);\
	} while (0)

unsigned int panell = 400;
module_param(panell, uint, 0664);
MODULE_PARM_DESC(panell, "display panel luminance\n");

static unsigned int hdr10_tm_sel = 2; /*1 old algorithm, 2 hdr_tmo algorithm  default 2*/
module_param(hdr10_tm_sel, uint, 0664);
MODULE_PARM_DESC(hdr10_tm_sel, "hdr10_tm_sel\n");

#define KNEE_POINT 2
static unsigned int kp = KNEE_POINT;
static unsigned int knee_point[KNEE_POINT] = {0, 0};
module_param_array(knee_point, uint, &kp, 0664);
MODULE_PARM_DESC(knee_point, "\n knee point xy\n");

/*u32 ebz_p[MAX_BEIZER_ORDER] = {
 *	0, 1170, 1755, 2121, 2377, 2596, 2779, 2925, 3064, 3185,
 *	3320, 3514, 3799, 3957, 4095
 *};
 */

/*time domain filter, avoid flicker*/
/*scene change th: 2/3 scene diff*/
u32 sc_th = 682;
module_param(sc_th, uint, 0664);
MODULE_PARM_DESC(sc_th, "scene change th\n");

u32 hdr_tm_iir = 1;
module_param(hdr_tm_iir, uint, 0664);
MODULE_PARM_DESC(hdr_tm_iir, "HDR_TM_IIR\n");

int is_hdr_tmo_support(void)
{
	if (get_cpu_type() >= MESON_CPU_MAJOR_ID_SM1)
		return 1;
	return 0;
}

int time_iir(u32 *maxl)
{
	int i;
	static u32 hist_diff[3];
	static u32 max_lum[3];
	u32 diff;
	u32 sum = 0;
	int sumshft;
	int norm14;
	u32 bld_coef;
	u32 dtl_lum = 0;
	/*ret=1: scn change; ret=2: blend*/
	int ret;

	for (i = 0; i < 2; i++) {
		hist_diff[i] = hist_diff[i + 1];
		max_lum[i] = max_lum[i + 1];
	}
	hist_diff[2] = 0;
	max_lum[2] = *maxl;

	for (i = 0; i < 128; i++) {
#ifndef CONFIG_AMLOGIC_ZAPPER_CUT
		if (chip_type_id == chip_t3x) {
			sum += s5_hdr_hist[15][i];
			diff = (s5_hdr_hist[15][i] > s5_hdr_hist[14][i]) ?
				(s5_hdr_hist[15][i] - s5_hdr_hist[14][i]) :
				(s5_hdr_hist[14][i] - s5_hdr_hist[15][i]);
		} else
#endif
		{
			sum += hdr_hist[15][i];
			diff = (hdr_hist[15][i] > hdr_hist[14][i]) ?
				(hdr_hist[15][i] - hdr_hist[14][i]) :
				(hdr_hist[14][i] - hdr_hist[15][i]);
		}
		hist_diff[2] += diff;
	}

	if (sum == 0)
		return 0;

	sumshft =
		(sum >= (1 << 24)) ? 8 :
		(sum >= (1 << 22)) ? 6 :
		(sum >= (1 << 20)) ? 4 :
		(sum >= (1 << 18)) ? 2 :
		(sum >= (1 << 16)) ? 0 :
		(sum >= (1 << 14)) ? -2 :
		(sum >= (1 << 12)) ? -4 :
		(sum >= (1 << 10)) ? -6 :
		(sum >= (1 << 8)) ? -8 :
		(sum >= (1 << 6)) ? -10 :
		(sum >= (1 << 4)) ? -12 : -16;

	if (sumshft >= 0)
		norm14 = (1 << 30) / (sum >> sumshft);
	else if (sumshft > -16)
		norm14 = (1 << (30 + sumshft)) / sum;
	else
		norm14 = 1 << 14;

	if (sumshft >= 0) {
		hist_diff[2] = ((hist_diff[2] >> sumshft) * norm14 +
			(1 << 13)) >> 14;
	} else {
		hist_diff[2] = (((hist_diff[2] << (-sumshft)) * norm14) +
			(1 << 13)) >> 14;
	}

	/*normalize to 10bit*/
	hist_diff[2] >>= 6;
	if (hist_diff[2] > sc_th || (*maxl <= panell)) {
		ret = 1;
	} else {
		bld_coef = hist_diff[2];
		dtl_lum =
			(max_lum[2] > max_lum[1]) ?
			(max_lum[2] - max_lum[1]) :
			(max_lum[1] - max_lum[2]);

		if (dtl_lum < (1 << 3))
			max_lum[2] =
				(max_lum[2] > max_lum[1]) ?
				(max_lum[1] + (dtl_lum >> 1)) :
				(max_lum[1] - (dtl_lum >> 1));
		else if (dtl_lum < (1 << 6))
			max_lum[2] =
				(max_lum[2] > max_lum[1]) ?
				(max_lum[1] + (dtl_lum >> 3)) :
				(max_lum[1] - (dtl_lum >> 3));
		else
			max_lum[2] =
				(max_lum[2] > max_lum[1]) ?
				(max_lum[1] + (dtl_lum >> 6)) :
				(max_lum[1] - (dtl_lum >> 6));

		*maxl = max_lum[2];
		ret = 2;
	}

	pr_hdr_tm("ret = %d, dtl_lum = %d, max_lum[1] = %d, max_lum[2] = %d\n",
		  ret, dtl_lum, max_lum[1], max_lum[2]);

	return ret;
}

/*hdr10 tone mapping alg */
int gen_oe_x_val(u64 *oe_x_val)
{
	unsigned int i = 0, j = 0;
	int bin_num = 0;
	int temp1 = 0, temp2 = 0;

	oe_x_val[0] = 0;

	for (i = 1; i < 8; i++) {
		bin_num++;
		temp1 = 1 << ((i - 1) + 4);
		oe_x_val[i] = (u64)temp1;
	}
	for (j = 11; j < 20; j++) {
		for (i = 0; i <  4; i++) {
			bin_num++;
			temp1 = 1 << (j - 2);
			temp2 = 1 << j;
			oe_x_val[bin_num] = (u64)temp1 * i + (u64)temp2;
		}
	}
	for (j = 20; j < 31; j++) {
		for (i = 0; i < 8; i++) {
			bin_num++;
			temp1 = 1 << (j - 3);
			temp2 = 1 << j;
			oe_x_val[bin_num] = (u64)temp1 * i + (u64)temp2;
		}
	}
	for (i = 0; i < 16; i++) {
		bin_num++;
		oe_x_val[bin_num] = (1 << (31 - 4)) * i + (1 << 31);
	}
	oe_x_val[OE_X - 1] = MAX_32;

	return 0;
}

void decasteliau_alg(u64 *ebzcur, u64 step, u64 range, u64 *p, u32 order)
{
	u64 point[MAX_BEIZER_ORDER + 1];
	int i, j;

	for (i = 0; i < order; i++)
		point[i] = p[i];

	for (i = 1; i < order; i++) {
		for (j = 0; j < order - i; j++) {
			point[j] = (range - step) * point[j] +
				step * point[j + 1] + (range >> 1);
			point[j] = div64_u64(point[j], range);
		}
	}
	ebzcur[1] = point[0];
}

static u64 curvex[OE_X], curvey[OE_X];
static u32 oo_gain[OE_X];

int gen_knee_anchor(u32 maxl, u32 panell, u64 *sx, u64 *sy, u64 *anchor)
{
	int i;
	int knee_bypass = 1229;
	u64 anchor_linear[MAX_BEIZER_ORDER];
	u64 norm, blendcoef;
	//int tgtl = 10000;
	struct basisootf_params basisootf_param;
	u64 sx1, sy1;
	u64 anchor0;

	sx1 = *sx;
	sy1 = *sy;

	basisootf_params_init(&basisootf_param);

	for (i = 0; i < MAX_BEIZER_ORDER - 1; i++)
		anchor_linear[i] = (i + 1) *
			(1 << MAX12_BIT) / MAX_BEIZER_ORDER;

	pr_hdr_tm("knee anchor p1: %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld %lld\n",
		  sx1, sy1,
		  anchor_linear[0], anchor_linear[1],
		  anchor_linear[2], anchor_linear[3],
		  anchor_linear[4], anchor_linear[5],
		  anchor_linear[6], anchor_linear[7],
		  anchor_linear[8]);

	if (maxl <= panell) {
		sx1 = knee_bypass;
		sy1 = knee_bypass;
		for (i = 0; i < MAX_BEIZER_ORDER - 1; i++)
			anchor[i] = anchor_linear[i];
	} else {
		norm = maxl;
		blendcoef = panell;
		sy1 = (blendcoef * knee_bypass +
			(norm - blendcoef) * sy1 + (norm >> 1));
		sy1 = div64_u64(sy1, norm);
		sx1 = sy1 * panell;
		sx1 = div64_u64(sx1, maxl);
		for (i = 0; i < MAX_BEIZER_ORDER - 1; i++) {
			anchor[i] =
			(blendcoef * anchor_linear[i] +
			(norm - blendcoef) * anchor[i] +
			(norm >> 1));

			anchor[i] = div64_u64(anchor[i], norm);
		}

		anchor0 = calcp1((u32)sx1, (u32)sy1,
				 panell, maxl, &basisootf_param, NULL);

		for (i = 1; i < MAX_BEIZER_ORDER - 1; i++)
			anchor[i] = min(anchor[i], (i + 1) * anchor0);

		if (anchor0 < anchor[1])
			anchor[0] = anchor0;
	}
	*sx = sx1;
	*sy = sy1;

	pr_hdr_tm("knee anchor p2: %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld %lld\n",
		  sx1, sy1,
		  anchor[0], anchor[1],
		  anchor[2], anchor[3],
		  anchor[4], anchor[5],
		  anchor[6], anchor[7],
		  anchor[8]);

	return 0;
}

int dynamic_hdr_sdr_ootf(u32 maxl, u32 panell, u64 sx, u64 sy, u64 *anchor)
{
	int i;
	u32 tgtl = 10000;
	static u64 oe_x[OE_X] = {0};
	int order = MAX_BEIZER_ORDER + 1;
	u64 step;
	u64 kx, ky;
	u64 ebzcur[2];
	u64 range_ebz_x, range_ebz_y;
	u64 finalanchor[MAX_BEIZER_ORDER + 1];
	u64 temp;
	u64 sx_primy;
	u64 kx_primy;

	gen_oe_x_val(oe_x);

	gen_knee_anchor(maxl, panell, &sx, &sy, anchor);

	pr_hdr_tm("d1: %d, %d, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld, %lld %lld\n",
		  maxl, panell,
		  sx, sy,
		  anchor[0], anchor[1],
		  anchor[2], anchor[3],
		  anchor[4], anchor[5],
		  anchor[6], anchor[7],
		  anchor[8]);

	for (i = 0; i < order - 2; i++)
		finalanchor[i + 1] = anchor[i] << (MAX32_BIT - MAX12_BIT);

	finalanchor[0] = 0;
	finalanchor[10] = MAX_32;

	if (tgtl <= maxl)
		maxl = tgtl - 1;

	kx = sx << (MAX32_BIT - MAX12_BIT);
	ky = sy << (MAX32_BIT - MAX12_BIT);

	kx = kx * maxl;
	kx = div64_u64(kx, tgtl);

	if (maxl <= panell) {
		sx_primy = (u64)(1 << MAX12_BIT) * panell +  (tgtl >> 1);
		sx_primy = div64_u64(sx_primy, tgtl);
	} else {
		sx_primy = (u64)(1 << MAX12_BIT) * maxl +  (tgtl >> 1);
		sx_primy = div64_u64(sx_primy, tgtl);
	}
	kx_primy = sx_primy << (MAX32_BIT - MAX12_BIT);

	range_ebz_x = kx_primy - kx;
	range_ebz_y = MAX_32 - ky;

	if (maxl <= panell) {
		for (i = 0; i < OE_X; i++) {
			if (oe_x[i] <= kx_primy) {
				temp = tgtl << TM_GAIN_BIT;
				oo_gain[i] = div64_u64(temp, panell);
			} else {
				curvey[i] = oe_x[148] << TM_GAIN_BIT;
				curvex[i] = oe_x[i];
				oo_gain[i] = div64_u64(curvey[i], curvex[i]);
			}
			if (oo_gain[i] < (1 << TM_GAIN_BIT))
				oo_gain[i] = 1 << TM_GAIN_BIT;
			if (oo_gain[i] > (1 << MAX12_BIT) - 1)
				oo_gain[i] = (1 << MAX12_BIT) - 1;
		}
	} else {
		for (i = 0; i < OE_X; i++) {
			if (oe_x[i] <= kx) {
				temp = tgtl << TM_GAIN_BIT;
				oo_gain[i] = div64_u64(temp, panell);
			} else if (oe_x[i] <=  kx_primy) {
				step = oe_x[i] - kx;
				decasteliau_alg(&ebzcur[0], step,
						range_ebz_x,
						finalanchor, order);
				curvey[i] = ky +
					((range_ebz_y * ebzcur[1] + MAX_32 / 2)
					>> MAX32_BIT);
				temp = curvey[i] << TM_GAIN_BIT;
				curvex[i] = kx + step;
				oo_gain[i] = div64_u64(temp, curvex[i]);
			}  else {
				curvey[i] = oe_x[148] << TM_GAIN_BIT;
				curvex[i] = oe_x[i];
				oo_gain[i] = div64_u64(curvey[i], curvex[i]);
			}
			if (oo_gain[i] < (1 << TM_GAIN_BIT))
				oo_gain[i] = 1 << TM_GAIN_BIT;
			if (oo_gain[i] > (1 << MAX12_BIT) - 1)
				oo_gain[i] = (1 << MAX12_BIT) - 1;
		}
	}

	for (i = 0; i < OE_X; i++) {
		if ((i + 1) % 10 == 0)
			pr_hdr_tm("%d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n",
				  oo_gain[i - 9], oo_gain[i - 8],
				  oo_gain[i - 7], oo_gain[i - 6],
				  oo_gain[i - 5], oo_gain[i - 4],
				  oo_gain[i - 3], oo_gain[i - 2],
				  oo_gain[i - 1], oo_gain[i]);
		if (i == 148)
			pr_hdr_tm("%d, %d, %d, %d, %d, %d, %d, %d, %d\n",
				  oo_gain[i - 8],
				  oo_gain[i - 7], oo_gain[i - 6],
				  oo_gain[i - 5], oo_gain[i - 4],
				  oo_gain[i - 3], oo_gain[i - 2],
				  oo_gain[i - 1], oo_gain[i]);
	}
	pr_hdr_tm("\n");

	return 0;
}

int hdr10_tm_dynamic_proc(struct vframe_master_display_colour_s *p)
{
	int i;
	u32 tmp;
	u32 maxl;
	u32 primary_maxl;
	u32 panel_luma;
	u64 sx, sy;
	u64 anchor[MAX_BEIZER_ORDER - 1];
	u32 P_init[MAX_BEIZER_ORDER - 1] = {
		181, 406, 607, 796, 834, 863, 890, 917, 938
	};
	int scn_chang_flag = 1;
	struct aml_tmo_reg_sw *pre_tmo_reg;

	primary_maxl = p->luminance[0];

	if (primary_maxl > 10000)
		primary_maxl /= 10000;
	/*no luminance*/
	if (primary_maxl == 0)
		primary_maxl = 1200;

	/*use 95% maxl because of high percert flicker*/
#ifndef CONFIG_AMLOGIC_ZAPPER_CUT
	if (chip_type_id == chip_t3x)
		tmp = s5_percentile[8];
	else
#endif
		tmp = percentile[8];

	maxl = (tmp > primary_maxl) ? primary_maxl : tmp;
	pr_hdr_tm("maxl = %d, percentile[8] = %d, primary_maxl =%d\n",
		maxl, tmp, primary_maxl);

	if (hdr_tm_iir)
		scn_chang_flag = time_iir(&maxl);

	if (scn_chang_flag == 0)
		pr_hdr_tm("invalid hist\n");
	else if (scn_chang_flag == 1)
		pr_hdr_tm("scn change\n");
	else if (scn_chang_flag == 2)
		pr_hdr_tm("maxl blend: maxl = %d\n", maxl);

	panel_luma = panell;

	sx = knee_point[0];
	sy = knee_point[1];

	/*invalid case*/
	if (maxl == 0) {
		maxl = 1200;
		pr_hdr_tm("invalid maxl\n");
	}

	for (i = 0; i < MAX_BEIZER_ORDER - 1; i++)
		anchor[i] = P_init[i] << 2;

	pre_tmo_reg = tmo_fw_param_get();

	if (!is_hdr_tmo_support() || !pre_tmo_reg->pre_hdr10_tmo_alg) {
		/*used old hdr alg*/
		pr_hdr_tm("used old hdr alg - ");
		if (!pre_tmo_reg->pre_hdr10_tmo_alg)
			pr_hdr_tm("hdr alg ko insmod failed.\n");
		else
			pr_hdr_tm("the chip is not support.\n");
		hdr10_tm_sel = 1;
	}

	if (hdr10_tm_sel == 1) {
		dynamic_hdr_sdr_ootf(maxl, panel_luma, sx, sy, anchor);
		memcpy(oo_y_lut_hdr_sdr, oo_gain, sizeof(u32) * OE_X);
	} else if (hdr10_tm_sel == 2) {
		hdr10_tmo_gen(oo_gain);
		memcpy(oo_y_lut_hdr_sdr, oo_gain, sizeof(u32) * OE_X);
	} else {
		memcpy(oo_y_lut_hdr_sdr, oo_y_lut_hdr_sdr_def,
			sizeof(u32) * OE_X);
	}

	if (hdr10_tm_dbg > 0)
		hdr10_tm_dbg--;

	return 0;
}
#endif
