/* SPDX-License-Identifier: LGPL-2.1-only OR BSD-3-Clause */
/******************************************************************************
 *
 * This file is provided under a dual license.  When you use or
 * distribute this software, you may choose to be licensed under
 * version 2.1 of the GNU Lesser General Public License
 * ("LGPLv2.1 License") or BSD License.
 *
 * LGPLv2.1 License
 *
 * Copyright(C) 2019 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2.1 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * 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 https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html for more
 * details.
 *
 * BSD LICENSE
 *
 * Copyright(C) 2019 MediaTek Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/

#include "drv_scaler_drvif.h"
#include "vdp_display.h"
#include "scpos_debug.h"
#include "drv_scaler_psc.h"
#include "drv_mjc_sys.h"
#include "hw_psc.h"



#if defined(SCPIP_SUPPORT_POST_SCALER)


#define PSC_FRAME_TRACK_TARGET  250
#define OSD_HSYNC_DLY     0
#define OSD_VSYNC_DLY     0xFFFD

typedef struct
{
    UINT8 bPscModeChanged;
    VRM_INFO_T pscIn;
    VRM_INFO_T pscOut;
	UINT32 u4HDeltaPhaseIntg;	            //H Scaling factor (integer part)
	UINT32 u4HDeltaPhaseFrac;	            //H Scaling factor (fraction part)
    UINT32 u4HInitPhaseSign;                //H Scaling phase  (sign)
    UINT32 u4HInitPhaseIntg;                //H Scaling phase  (integer)
    UINT32 u4HInitPhaseFrac;                //H Scaling phase  (fraction)	
	UINT32 u4InputWidth;			        //input width(pixel based)
	UINT32 u4InputHeight;                   //input height(pixel based)
	UINT32 u4OutputX;			            //output X(pixel based)
	UINT32 u4OutputY;                       //output Y(pixel based)
	UINT32 u4OutputWidth;                   //output Width(pixel based)
	UINT32 u4OutputHeight;	                //output Height(pixel based)    
	BOOL bNeedSetData;                    //update XY later (Before Set data trigger)
}PSC_INFO_T;

PSC_INFO_T pscInfo;

UINT8 pscInitFlag = SV_FALSE;

#ifndef CC_MTK_LOADER
//static functions
static void _vPscUpdateDispPrm(void);
static void _vPscUpdateInOutRegion(void);
static void _vPscUpdateOutputResolution(UINT32 outX, UINT32 outY, UINT32 outW, UINT32 outH);
static void _vPscUpdateReg(void);
static void _vPscSetImportOnOff(UINT8 bOnOff);                
#endif
static void vPscSetOsdHVSyncDly( UINT16 u2Osd_HsyncDly, UINT16 u2Osd_VsyncDly );

static void _vPscInitSyncAdj(void);
EXTERN BOOL fgFrameTrackIsFlgSet(UINT32 u4Flag);
    
#ifndef CC_MTK_LOADER

void vPscFireModeChanged(void)
{
    pscInfo.bPscModeChanged = SV_TRUE;
}

void vPscClearModeChanged(void)
{
    pscInfo.bPscModeChanged = SV_FALSE;
}

UINT8 fgPscIsModeChanged(void)
{
    RVChannel* mainChannel;
    RVChannel* subChannel;
    UINT8 ret = SV_FALSE;

    mainChannel = getChannel(SV_VP_MAIN);
    subChannel = getChannel(SV_VP_PIP);
    
    if ( (mainChannel->bIsChannelOn ==SV_TRUE && mainChannel->bIsFreeze == SV_FALSE) || 
         (subChannel->bIsChannelOn ==SV_TRUE && subChannel->bIsFreeze == SV_FALSE) ||
         (pscInitFlag == SV_TRUE))   //channel is on & not freezed
    {
        if(pscInfo.bPscModeChanged == SV_TRUE)
        {
            ret = SV_TRUE;
        }
    }
    return ret;
}
/******************************************
*  @Cobra porting done
******************************************/
void vPscSetImportOff(UINT8 bOnOff)
{
    if(bOnOff == SV_ON)
    {
		vPscSetResToggle(SV_ON);
    }
    else
    {	
		vPscSetResToggle(SV_OFF);
    }
}

extern UINT32 GCD(UINT32 a, UINT32 b);

/******************************************
*  @ cobra need review
******************************************/
void vPscConfigFrameTrack(UINT32 u4Step)
{
    UINT8 u1InFR;
    UINT8 u1OutFR;
    UINT8 u1TrackVNum;
	UINT8 bLockWin;
    //UINT32 u4PreVofst;
    //UINT32 u4PanelVTotal;
    //UINT32 u4UpperBound;
    //UINT32 u4LowerBound;
    //UINT32 u4LockRange;
    UINT8 u1MaskInput=0;
    bLockWin = 2;

    u1InFR = bDrvVideoGetRefreshRate(SV_VP_MAIN);
    u1OutFR = vDrvGetLCDFreq();
    
    if ((bDrvVideoSignalStatus(SV_VP_MAIN) != SV_VDO_STABLE) || (u1InFR==0) || (u1OutFR==0))
    	return;
        
    u1TrackVNum = u1InFR / GCD(u1InFR, u1OutFR);
    bLockWin *= u1TrackVNum;
	if (u1TrackVNum==1)  //input == output
    {
      u1MaskInput=0;
    }
	else
	{
       u1MaskInput=u1TrackVNum;       
 	}	
	//}
    //u4PanelVTotal = u4PscGetPanelVTotal();
    
    //1. set frame track interval
    vPscMaskInputFrame(u1MaskInput);  

    //2. set frame track target. current's target is PRE_VOFST_BYPASS
    //using 96 default value?? need to review!!
	vPscSetFrameTrackTargetDistance(PSC_FRAME_TRACK_TARGET);
	
    //3. reference front scaler output since Post scaler is in MJC dispmode.
    //cobra post scaler no need to select which input vsync to track.
    
    //4. set to mask mode
    //PSC_WRITE_FLD(PSPIP_OA_0D, 1, PSPIP_OA_0D_FRAME_TRACK_VSYNC_DIST_MODE);  
    
    //6. set track range, need to do
    
    vPscSetFrameTrackLockWin(bLockWin);
    vPscSetFrameTrackLockTolerateLineNum(bLockWin);
    //vPscSetFrameTrackTurboRegion(UINT32 u4Region)
	//vPscSetFrameTrackStep(UINT32 u4Step)

	//7. other setting
	//vPscSetFrameTrackOnceOnly(UINT8 bOnOff)
}

/******************************************
*  @ Cobra porting done
******************************************/
void vPscSetFrameTrackOnOff(UINT32 u4OnOff)
{
    #if defined(__MODEL_slt__) || defined(DRV_SUPPORT_EXTMJC) // Frame tracking always off for SLT and external MJC
    vPscSetFrameTrackStep(0);
    vPscSetDddsFrameTrackEnable(SV_OFF);
    #else
    if (u4OnOff)  // notice the order
    {  
    	 if(fgFrameTrackIsFlgSet(FRAME_TRACK_MODE_DDDS_BY_PSTSCALER)) // DDDS frame track mode
        {
            //set to DDDS Adjust mode        
            vPscSetFrameTrackStep(0);
            if(u1DrvIsBypassPathToMJCPost() ==  SV_TRUE)
        	{
        	    vPscSetDddsFrameTrackEnable(SV_OFF);
        	}
			else
			{
			    vPscSetDddsFrameTrackEnable(SV_ON);
			}
            
        }
        else 
        {
            //set to V Total Adjust mode         
            vPscSetDddsFrameTrackEnable(SV_OFF);
			if(u1DrvIsBypassPathToMJCPost() ==  SV_TRUE)
        	{
        	    vPscSetFrameTrackStep(0);
        	}
			else
			{
			    vPscSetFrameTrackStep(1);
			}
        }    
    }
    else
    {
         vPscSetFrameTrackStep(0);
         vPscSetDddsFrameTrackEnable(SV_OFF);
    }
    #endif    
}

void vPscSwInit(void)
{    
    pscInfo.bPscModeChanged = SV_FALSE;
    pscInfo.bNeedSetData = SV_FALSE;
	pscInfo.u4OutputX = 0XFFFF;
	pscInfo.u4OutputY = 0XFFFF;
}

static void _vPscUpdateInOutRegion(void)
{
    VRMGetModuleVRMInfo(SV_VP_MAIN, VRM_MODULE_PSC, &(pscInfo.pscIn), &(pscInfo.pscOut));
}

static void _vPscUpdateScaliingFactorPrm(UINT32 inW, UINT32 outW) 
{
	UINT32 u4HPhaseInc;   
	
    //for H up case
    if ( inW == 0 || outW == 0 || outW < inW)
    {    
		return;
    }
    else  
    {    
        //scaling delta phase
		u4HPhaseInc = (inW * DFT_UNIT_PHASE(SV_VP_MAIN)) / outW;
		pscInfo.u4HDeltaPhaseIntg = u4HPhaseInc >> DFT_FRAC_PHASE_BITS(SV_VP_MAIN);	
		pscInfo.u4HDeltaPhaseFrac = u4HPhaseInc & DFT_FRAC_PHASE_MASK(SV_VP_MAIN);

        //scaling init phase
		pscInfo.u4HInitPhaseIntg = 0;
	    if(pscInfo.u4HDeltaPhaseFrac == 0)
    	{
			pscInfo.u4HInitPhaseFrac = 0;
			pscInfo.u4HInitPhaseSign = 0;    	
    	}
	    else
    	{
			pscInfo.u4HInitPhaseFrac = DFT_UNIT_PHASE(SV_VP_MAIN) + 
				( ((inW-1)*DFT_UNIT_PHASE(SV_VP_MAIN)) - ((outW-1)*pscInfo.u4HDeltaPhaseFrac))/2;
			pscInfo.u4HInitPhaseSign = 1;            
    	}
    }
}

static void _vPscUpdateInputResolution(UINT32 inW,UINT32 inH)
{
    pscInfo.u4InputWidth = inW;
    pscInfo.u4InputHeight = inH;
}

static void _vPscUpdateOutputResolution(UINT32 outX, UINT32 outY, UINT32 outW, UINT32 outH)
{
    CRIT_STATE_T csState;    

    if(pscInfo.u4OutputX != outX || pscInfo.u4OutputY != outY)
    {
        vPscSetNeedSetDataFlag(SV_TRUE);
        LOG(3,"PSC SET DATA FLG = SV_TRUE\n");            
    }

    csState = x_crit_start();
    pscInfo.u4OutputX = outX;
    pscInfo.u4OutputY = outY;
    pscInfo.u4OutputWidth = outW;
    pscInfo.u4OutputHeight = outH;
    x_crit_end(csState);
}

static void _vPscUpdateDispPrm()
{    
    //1. update input & output size
    _vPscUpdateInOutRegion();
    //2. update H up scaling factor
    _vPscUpdateScaliingFactorPrm(pscInfo.pscIn.u2Width, pscInfo.pscOut.u2Width);
    //3. update input resolution
    _vPscUpdateInputResolution(pscInfo.pscIn.u2Width,pscInfo.pscIn.u2Height);
    //4. update output resolution
    _vPscUpdateOutputResolution(pscInfo.pscOut.u2X,pscInfo.pscOut.u2Y,
                                        pscInfo.pscOut.u2Width,pscInfo.pscOut.u2Height);
}
/******************************************
*  @Cobra porting done
******************************************/
static void _vPscSetImportOnOff(UINT8 bOnOff)
{
    LOG(3,"Set PSC import(%d)\n",bOnOff);
	vPscSetResImportEnable(bOnOff);
}

static void _vPscUpdateReg(void)
{
    /**** start import protect ****/
    _vPscSetImportOnOff(SV_OFF);                
    //1. set input resolution
    vPscSetInputWidthHeight(pscInfo.u4InputWidth,pscInfo.u4InputHeight);
    //2. set output resolution
    //if(bPscIsNeedSetData()== SV_TRUE)
    {
        vPscSetHmskStartOfst(pscInfo.u4OutputX);
        vPscSetVmskStartOfst(pscInfo.u4OutputY);    
    }

    vPscSetOutputWidthHeight(pscInfo.u4OutputWidth, pscInfo.u4OutputHeight);    	
    
    //MEMC Output height
    vPscSetMEMCOutputWidth(pscInfo.u4InputWidth);
    
    //3. set H Delta phase
    vPscSetHDeltaPhase(pscInfo.u4HDeltaPhaseIntg, pscInfo.u4HDeltaPhaseFrac);
    //4. set H Initial phase
    vPscSetHInitPhase(pscInfo.u4HInitPhaseSign, pscInfo.u4HInitPhaseIntg, pscInfo.u4HInitPhaseFrac);
    //5. set mjc dispmode register (need review)
    if(u1DrvIsBypassPathToMJCPost() ==  SV_TRUE)
    {
        PSC_WRITE_FLD(PSCSYS_11, SV_ON, PSC_INV_INPUT_MVDE);
    }
    else
    {   
        PSC_WRITE_FLD(PSCSYS_11, SV_OFF, PSC_INV_INPUT_MVDE);
    }
    //_vPscSetImportOnOff(SV_ON);
    /**** end of import protect ****/
}

void vPscUpdateReg_SETDATA(void)
{
	LOG(3,"vPscUpdateReg_SETDATA \n");
    //2. set output resolution
    vPscSetHmskStartOfst(pscInfo.u4OutputX);
	vPscSetVmskStartOfst(pscInfo.u4OutputY);
}

BOOL bPscIsNeedSetData(void)
{
    return pscInfo.bNeedSetData;
}

void vPscSetNeedSetDataFlag(UINT8 bFlag)
{
    pscInfo.bNeedSetData = bFlag;
}

void vPSCDumpResInfo(void)
{
}

void vPscPrintCurrentScale(void)
{
    LOG(0,"PSC H(0x%x-0x%x)\n",pscInfo.u4HDeltaPhaseIntg, pscInfo.u4HDeltaPhaseFrac);
}

void vPscNormProc(void)
{
    if(fgPscIsModeChanged()==SV_TRUE)
    {    
        //1. update display parameter
        _vPscUpdateDispPrm();
        //2. update related register
        _vPscUpdateReg();
        //3. clear mode change
        vPscClearModeChanged();
    }
}
#endif

void vPscHwInit(void)
{
    //1. init MJC dispmode
    //_vPscInitMjcDispmode();
    //2. init SyncAdj/Active Adj/Read Delay
    _vPscInitSyncAdj();
    //3. set initial watel level
    //vPscSetDispFifoWaterLevel(PSC_DEFAULT_WTLV);
    //4. set psc h init phase
    //vPscSetHInitPhase(0, 0, 0);
    //5. select OSD v signal to VDE
    vPscSelectOsdVsync(1);
    //inverse osd h/v sync 
    vPscSetInvOsdHVSync(SV_TRUE, SV_TRUE);

    vPscSetOsdHVSyncDly( OSD_HSYNC_DLY, OSD_VSYNC_DLY );
    
    vPscSetCoreSel(1,1);
    
    v1PscSetRiscChannelSel(1,0);
    
    vPscTGSlaveSyncMode(0); //set to recomend vale '0'.

    pscInitFlag = SV_TRUE;
}

static void vPscSetOsdHVSyncDly( UINT16 u2Osd_HsyncDly, UINT16 u2Osd_VsyncDly )
{
   vPscSetOsdHsyncDly( u2Osd_HsyncDly );
   vPscSetOsdVsyncDly( u2Osd_VsyncDly );
}

static void _vPscInitSyncAdj(void)
{
  	//not use
}

void vPscPreInit(void)
{
    static UINT32 _pscPreset = 0;

    if (_pscPreset == 0)
    {
        _pscPreset = 1;

        //todo: do pre init here
        vPscHwInit();
    }   
}

#endif
