/*
 * drivers/video/sunxi/disp2/disp/de/lowlevel_sun50iw1/de_gsu.c
 *
 * Copyright (c) 2007-2019 Allwinnertech Co., Ltd.
 * Author: zhengxiaobin <zhengxiaobin@allwinnertech.com>
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * 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 "de_feat.h"
#include "de_gsu_type.h"
#include "de_scaler.h"
#include "de_scaler_table.h"

static volatile __gsu_reg_t *gsu_dev[DEVICE_NUM][UI_CHN_NUM];
static de_reg_blocks gsu_glb_block[DEVICE_NUM][UI_CHN_NUM];
static de_reg_blocks gsu_out_block[DEVICE_NUM][UI_CHN_NUM];
static de_reg_blocks gsu_scale_block[DEVICE_NUM][UI_CHN_NUM];
static de_reg_blocks gsu_coeff_block[DEVICE_NUM][UI_CHN_NUM];

int de_gsu_init(unsigned int sel, uintptr_t reg_base)
{
	int j, chno;
	uintptr_t gsu_base;
	void *memory;
	int vi_chno;
	int gsu_offset;

	chno=de_feat_is_support_scale(sel);
	vi_chno = de_feat_get_num_vi_chns(sel);
	chno = chno - vi_chno;

	gsu_offset = VSU_OFST + vi_chno*0x20000;

	for (j=0;j<chno;j++)
	{
		gsu_base = reg_base + (sel+1)*0x00100000 + gsu_offset + j*0x10000;	//FIXME  display path offset should be defined

		memory = (void *)kmalloc(sizeof(__gsu_reg_t), GFP_KERNEL | __GFP_ZERO);
		if (NULL == memory) {
			__wrn("malloc ui scaler[%d][%d] memory fail! size=0x%x\n", sel, j, (unsigned int)sizeof(__gsu_reg_t));
			return -1;
		}

		gsu_glb_block[sel][j].off			= gsu_base;
		gsu_glb_block[sel][j].val			= memory;
		gsu_glb_block[sel][j].size			= 0x14;
		gsu_glb_block[sel][j].dirty 		= 0;

		gsu_out_block[sel][j].off			= gsu_base + 0x40;
		gsu_out_block[sel][j].val			= memory + 0x40;
		gsu_out_block[sel][j].size			= 0x4;
		gsu_out_block[sel][j].dirty 		= 0;

		gsu_scale_block[sel][j].off		= gsu_base + 0x80;
		gsu_scale_block[sel][j].val		= memory + 0x80;
		gsu_scale_block[sel][j].size		= 0x20;
		gsu_scale_block[sel][j].dirty 		= 0;

		gsu_coeff_block[sel][j].off		= gsu_base + 0x200;
		gsu_coeff_block[sel][j].val		= memory + 0x200;
		gsu_coeff_block[sel][j].size		= 0x40;
		gsu_coeff_block[sel][j].dirty 		= 0;

		de_gsu_set_reg_base(sel, j, memory);
	}

	return 0;
}

int de_gsu_update_regs(unsigned int sel)
{
	int i,chno;

	chno=de_feat_is_support_scale(sel);
	chno = chno - de_feat_get_num_vi_chns(sel);

	for (i=0;i<chno;i++)
	{
		if (gsu_glb_block[sel][i].dirty == 0x1){
			memcpy((void *)gsu_glb_block[sel][i].off,gsu_glb_block[sel][i].val,gsu_glb_block[sel][i].size);
			gsu_glb_block[sel][i].dirty = 0x0;}
		if (gsu_out_block[sel][i].dirty == 0x1){
			memcpy((void *)gsu_out_block[sel][i].off,gsu_out_block[sel][i].val,gsu_out_block[sel][i].size);
			gsu_out_block[sel][i].dirty = 0x0;}
		if (gsu_scale_block[sel][i].dirty == 0x1){
			memcpy((void *)gsu_scale_block[sel][i].off,gsu_scale_block[sel][i].val,gsu_scale_block[sel][i].size);
			gsu_scale_block[sel][i].dirty = 0x0;}
		if (gsu_coeff_block[sel][i].dirty == 0x1){
			memcpy((void *)gsu_coeff_block[sel][i].off,gsu_coeff_block[sel][i].val,gsu_coeff_block[sel][i].size);
			gsu_coeff_block[sel][i].dirty = 0x0;}

	}

	return 0;
}

//*********************************************************************************************************************
// function       : de_gsu_set_reg_base(unsigned int sel, unsigned int chno, unsigned int base)
// description    : set gsu reg base
// parameters     :
//                  sel		<rtmx select>
//                  chno	<overlay select>
//                  base	<reg base>
// return         :
//                  success
//*********************************************************************************************************************
int de_gsu_set_reg_base(unsigned int sel, unsigned int chno, void *base)
{
	__inf("sel=%d, chno=%d, base=0x%p\n", sel, chno, base);
	gsu_dev[sel][chno] = (__gsu_reg_t *)base;

	return 0;
}

//*********************************************************************************************************************
// function       : de_gsu_enable(unsigned int sel, unsigned int chno, unsigned int en)
// description    : enable/disable gsu
// parameters     :
//                  sel		<rtmx select>
//                  chno	<overlay select>
//                  en		<enable: 0-diable; 1-enable>
// return         :
//                  success
//*********************************************************************************************************************
int de_gsu_enable(unsigned int sel, unsigned int chno, unsigned int en)
{
	gsu_dev[sel][chno-VI_CHN_NUM]->ctrl.bits.en = en;
	gsu_glb_block[sel][chno-VI_CHN_NUM].dirty   = 1;

	return 0;
}

//*********************************************************************************************************************
// function       : de_gsu_set_fir_coef(unsigned int sel, unsigned int chno, unsigned int *coef)
// description    : set fir coefficients
// parameters     :
//                  sel		<rtmx select>
//                  chno	<overlay select>
//                  *coef	<coefficients pointer>
// return         :
//                  success
//*********************************************************************************************************************
//static int de_gsu_set_fir_coef(unsigned int sel, unsigned int chno, unsigned int *coef)
//{
//	memcpy(&gsu_dev[sel][chno-VI_CHN_NUM]->hcoeff, coef, sizeof(unsigned int)*GSU_PHASE_NUM);
//
//	return 0;
//}

//*********************************************************************************************************************
// function       : de_gsu_calc_fir_coef(unsigned int step)
// description    : set fir coefficients
// parameters     :
//                  step		<horizontal scale ratio of vsu>
// return         :
//                  offset (in word) of coefficient table
//*********************************************************************************************************************
static unsigned int de_gsu_calc_fir_coef(unsigned int step)
{
	unsigned int pt_coef;
	unsigned int scale_ratio, int_part, float_part, fir_coef_ofst;

	scale_ratio = step>>(GSU_PHASE_FRAC_BITWIDTH-3);
	int_part = scale_ratio>>3;
	float_part = scale_ratio & 0x7;
	fir_coef_ofst = (int_part==0)  ? GSU_ZOOM0_SIZE :
					(int_part==1)  ? GSU_ZOOM0_SIZE + float_part :
					(int_part==2)  ? GSU_ZOOM0_SIZE + GSU_ZOOM1_SIZE + (float_part>>1) :
					(int_part==3)  ? GSU_ZOOM0_SIZE + GSU_ZOOM1_SIZE + GSU_ZOOM2_SIZE :
					(int_part==4)  ? GSU_ZOOM0_SIZE + GSU_ZOOM1_SIZE + GSU_ZOOM2_SIZE + GSU_ZOOM3_SIZE :
					GSU_ZOOM0_SIZE + GSU_ZOOM1_SIZE + GSU_ZOOM2_SIZE + GSU_ZOOM3_SIZE + GSU_ZOOM4_SIZE;

	pt_coef = fir_coef_ofst * GSU_PHASE_NUM ;

	return pt_coef;
}


//*********************************************************************************************************************
// function       : de_gsu_set_para(unsigned int sel, unsigned int chno, unsigned int *coef)
// description    : set fir coefficients
// parameters     :
//                  sel		<rtmx select>
//                  chno	<overlay select>
//                  *coef	<coefficients pointer>
// return         :
//                  success
//*********************************************************************************************************************
int de_gsu_set_para(unsigned int sel, unsigned int chno, unsigned int enable,
					unsigned int in_w, unsigned int in_h, unsigned int out_w, unsigned int out_h, scaler_para *para)
{
	unsigned int pt_coef;
	unsigned int chno_t;
	unsigned int vi_chno;

	vi_chno = de_feat_get_num_vi_chns(sel);//FIXME, video channnel must support scale?

	chno_t = chno-vi_chno;

	gsu_dev[sel][chno_t]->ctrl.bits.en = enable;

	if (0==enable)
	{
		gsu_glb_block[sel][chno_t].dirty   = 1;
		return 0;
	}

	//basic parameter
	gsu_dev[sel][chno_t]->outsize.dwval = ((out_h - 1)<<16) | (out_w - 1);
	gsu_dev[sel][chno_t]->insize.dwval = ((in_h - 1)<<16) | (in_w - 1);
	gsu_dev[sel][chno_t]->hstep.dwval = para->hstep<<GSU_PHASE_FRAC_REG_SHIFT;
	gsu_dev[sel][chno_t]->vstep.dwval = para->vstep<<GSU_PHASE_FRAC_REG_SHIFT;
	gsu_dev[sel][chno_t]->hphase.dwval = para->hphase<<GSU_PHASE_FRAC_REG_SHIFT;
	gsu_dev[sel][chno_t]->vphase0.dwval = para->vphase<<GSU_PHASE_FRAC_REG_SHIFT;

	//fir coefficient
	pt_coef = de_gsu_calc_fir_coef(para->vstep);
	memcpy(&gsu_dev[sel][chno_t]->hcoeff, lan2coefftab16 + pt_coef, sizeof(unsigned int)*GSU_PHASE_NUM);
	gsu_dev[sel][chno_t]->ctrl.bits.coef_switch_rdy = 1;
	gsu_glb_block[sel][chno_t].dirty   = 1;
	gsu_out_block[sel][chno_t].dirty   = 1;
	gsu_scale_block[sel][chno_t].dirty = 1;
	gsu_coeff_block[sel][chno_t].dirty = 1;

	return 0;
}

//*********************************************************************************************************************
// function       : de_gsu_calc_scaler_para(de_rect64 crop, de_rect frame, de_rect *crop_fix,scaler_para *para)
// description    : calculate scaler parameters
// parameters     :
//                  crop.w	<frame buffer crop width with 32bit fraction part>
//                  crop.h	<frame buffer crop height with 32bit fraction part>
//                  crop.x	<frame buffer coordinate x with 32bit fraction part>
//					crop.y	<frame buffer coordinate y with 32bit fraction part>
//					frame.w	<frame width in integer>
//					frame.h	<frame height in integer>
//					crop_fix->w<framebuffer crop_fix width after fixed>
//					crop_fix->h<framebuffer crop_fix height after fixed>
//					crop_fix->x<framebuffer crop_fix coordinate x after fixed>
//					crop_fix->y<framebuffer crop_fix coordinate y after fixed>
//					hstep	<horizon scale ratio>
//					vstep	<vertical scale ratio>
//					hphase  <horizon initial phase>
//					vphase  <vertical initial phase>
// return         :
//                  success
//*********************************************************************************************************************
int de_gsu_calc_scaler_para(de_rect64 crop, de_rect frame, de_rect *crop_fix,scaler_para *para)
{
	unsigned long long tmp = 0;

	tmp = (N2_POWER(crop.w,GSU_PHASE_FRAC_BITWIDTH));
	if (frame.w)
		do_div(tmp, frame.w);
	else
		tmp = 0;
	para->hstep= (unsigned int)(tmp>>GSU_FB_FRAC_BITWIDTH);

	tmp = (N2_POWER(crop.h,GSU_PHASE_FRAC_BITWIDTH));
	if (frame.h)
		do_div(tmp, frame.h);
	else
		tmp = 0;
	para->vstep= (unsigned int)(tmp>>GSU_FB_FRAC_BITWIDTH);

	para->hphase = ((crop.x & 0xffffffff)>>(32-GSU_PHASE_FRAC_BITWIDTH));
	para->vphase = ((crop.y & 0xffffffff)>>(32-GSU_PHASE_FRAC_BITWIDTH));;

	crop_fix->w = (unsigned int)(((crop.w & 0xffffffff) + (crop.x & 0xffffffff))>= N2_POWER(1,GSU_FB_FRAC_BITWIDTH)?
								 ((crop.w>>GSU_FB_FRAC_BITWIDTH) + 1) : (crop.w>>GSU_FB_FRAC_BITWIDTH));

	crop_fix->h = (unsigned int)(((crop.h & 0xffffffff) + (crop.y & 0xffffffff))>= N2_POWER(1,GSU_FB_FRAC_BITWIDTH)?
								 ((crop.h>>GSU_FB_FRAC_BITWIDTH) + 1) : (crop.h>>GSU_FB_FRAC_BITWIDTH));

	crop_fix->x = (int)(crop.x>>GSU_FB_FRAC_BITWIDTH);
	crop_fix->y = (int)(crop.y>>GSU_FB_FRAC_BITWIDTH);

	return 0;
}

//*********************************************************************************************************************
// function       : de_calc_ovl_coord(unsigned int frame_coord, unsigned int scale_step, int gsu_sel)
// description    : calculate overlay coordinate
// parameters     :
//                  frame_coord	<frame buffer coordinate>
//                  scale_step  <vsu/gsu horizontal/vertical scale step>
//					gsu_sel		<is gsu or not: 0-is vsu, others-is gsu
// return         : layer_coord <layer coordinate>
//                  success
//*********************************************************************************************************************
int de_calc_ovl_coord(unsigned int frame_coord, unsigned int scale_step, int gsu_sel)
{
	int shift, half,layer_coord;

	shift = (gsu_sel==0)?VSU_PHASE_FRAC_BITWIDTH : GSU_PHASE_FRAC_BITWIDTH;
	half = (1<<(shift-1));
	layer_coord = (frame_coord*scale_step + half)>>shift;
	__inf("half_shift_xy=<%x,%d,%d>\n", half, shift,layer_coord);

	return layer_coord;
}

//*********************************************************************************************************************
// function       : de_gsu_sel_ovl_scaler_para(unsigned char *en, scaler_para *layer_scale_para[], scaler_para *ovl_scale_para)
// description    : calculate ui overlay scaler parameters
// parameters     :
//                  en					<pointer of layer enable>
//                  layer_scale_para  	<pointer array of ui layer scaler parameter>
//					ovl_scale_para		<pointer of ui overlay scaler parameter>
// return         :
//                  success
//*********************************************************************************************************************
int de_gsu_sel_ovl_scaler_para(unsigned char *en, scaler_para *layer_scale_para, scaler_para *ovl_scale_para)
{
	int i, j, layer_en_num;
	int used_layer_idx[LAYER_MAX_NUM_PER_CHN];

	//get vaild layer number and record layer index
	layer_en_num = 0;
	j = 0;
	for (i=0; i<LAYER_MAX_NUM_PER_CHN; i++)
	{
		if (*(en + i) == 1)
		{
			layer_en_num++;
			used_layer_idx[j++] = i;
		}
	}

	if (layer_en_num==1)	//only one layer enabled in one overlay
	{
		//set overlay scale para through this layer
		ovl_scale_para->hphase = layer_scale_para[used_layer_idx[0]].hphase;
		ovl_scale_para->vphase = layer_scale_para[used_layer_idx[0]].vphase;
		ovl_scale_para->hstep = layer_scale_para[used_layer_idx[0]].hstep;
		ovl_scale_para->vstep = layer_scale_para[used_layer_idx[0]].vstep;
	}
	else if (layer_en_num>1)	//two or more layers enabled in one overlay
	{
		//set overlay scale step through first enabled layer
		ovl_scale_para->hstep = layer_scale_para[used_layer_idx[0]].hstep;
		ovl_scale_para->vstep = layer_scale_para[used_layer_idx[0]].vstep;

		//set overlay phase through first enabled non-zero-phase layer
		for (i=0; i<layer_en_num; i++)
		{
			if (layer_scale_para[used_layer_idx[i]].hphase != 0)
			{
				ovl_scale_para->hphase = layer_scale_para[used_layer_idx[i]].hphase;
				break;
			}
		}

		//all layer phase equal to zero
		if (i == layer_en_num)
		{
			ovl_scale_para->hphase = layer_scale_para[used_layer_idx[0]].hphase;
		}

		//set overlay phase through first enabled non-zero-phase layer
		for (i=0; i<layer_en_num; i++)
		{
			if (layer_scale_para[used_layer_idx[i]].vphase != 0)
			{
				ovl_scale_para->vphase = layer_scale_para[used_layer_idx[i]].vphase;
				break;
			}
		}

		//all layer phase equal to zero
		if (i == layer_en_num)
		{
			ovl_scale_para->vphase = layer_scale_para[used_layer_idx[0]].vphase;
		}

	}
	else
	{
		ovl_scale_para->hphase = 0;
		ovl_scale_para->vphase = 0;
		ovl_scale_para->hstep = (unsigned int)N2_POWER(1,GSU_PHASE_FRAC_BITWIDTH);
		ovl_scale_para->vstep = (unsigned int)N2_POWER(1,GSU_PHASE_FRAC_BITWIDTH);
	}

	return 0;
}
