/* 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 "general.h"
#include "hw_vdoin.h"
#include "util.h"
#include "pe_if.h"
#include "drvcust_if.h"

#define LUMA_BACKLIGHT_CTRL		SV_FALSE	// backlight control initialize: 0: OFF, 1:ON
#define LUMA_MODIFY_COUNT		0x0060	    // 0x1000 = 4096
#define LUMA_LONG_TIME			0x0020	    // 0x0040 = 64,  statistic 64 frame APL

// Global Parameter For Adaptive Backlight Control
UINT8 bBackLightCtrl;
UINT16 wAutoContrastModifyCount;
UINT16 wAutoContrastLongTime;

UINT32 dwSumAPL;
UINT16 wFrameCount;
UINT16 wDoModifyCount;
UINT16 wModifyInterval;

UINT16 wPreValue;
UINT16 wCurValue;
UINT8 bModifyPos;

enum
{
	DYNAMIC_CONTRAST_RATIO_100PERCENT = 0,
	DYNAMIC_CONTRAST_RATIO_150PERCENT,
	DYNAMIC_CONTRAST_RATIO_200PERCENT,
	DYNAMIC_CONTRAST_END
};


typedef struct structBacklightCurve
{
	UINT8 x1;
	UINT8 x2;
	UINT8 x3;
	UINT8 x4;
	UINT8 y0;
	UINT8 y1;
	UINT8 y2;
	UINT8 y3;
	UINT8 y4;
	INT16 m0;
	INT16 m1;
	INT16 m2;
	INT16 m3;
	INT16 m4;
} structBacklightCurve;

structBacklightCurve BacklightCurve;
/*************************************************************************************/
//                  Customized Internal Adaptive Backlight Driver
/*************************************************************************************/
void _AdaptiveBacklightSetCurve(UINT8 y0, UINT8 x1, UINT8 y1, UINT8 x2, UINT8 y2,
                         UINT8 x3, UINT8 y3, UINT8 x4, UINT8 y4, UINT8 y5)
{
    // initialize
    wFrameCount = wAutoContrastLongTime;
    dwSumAPL = 0;
    wDoModifyCount = wAutoContrastLongTime;

    // boundery protection
    if (x1 > x2)
    {
        x1 = x2;
    }
    if (x2 > x3)
    {
        x2 = x3;
    }
    if (x3 > x4)
    {
        x3 = x4;
    }

    // The segment from ( x0, y0 ) --> ( x1, y1 )
    if (x1 > 0)
    {
        BacklightCurve.m0 =
            (INT16) ((((INT32) y1 - (INT32) y0) << 7) / (INT32) x1);
    }
    else
    {
        BacklightCurve.m0 = 0;
    }

    // The segment from ( x1, y1 ) --> ( x2, y2 )
    if (x2 > x1)
    {
        BacklightCurve.m1 =
            (INT16) ((((INT32) y2 - (INT32) y1) << 7) / ((INT32) x2 -
                                                         (INT32) x1));
    }
    else
    {
        BacklightCurve.m1 = 0;
    }

    // The segment from ( x2, y2 ) --> ( x3, y3 )
    if (x3 > x2)
    {
        BacklightCurve.m2 =
            (INT16) ((((INT32) y3 - (INT32) y2) << 7) / ((INT32) x3 -
                                                         (INT32) x2));
    }
    else
    {
        BacklightCurve.m2 = 0;
    }

    // The segment from ( x3, y3 ) --> ( x4, y4 )
    if (x4 > x3)
    {
        BacklightCurve.m3 =
            (INT16) ((((INT32) y4 - (INT32) y3) << 7) / ((INT32) x4 -
                                                         (INT32) x3));
    }
    else
    {
        BacklightCurve.m3 = 0;
    }

    // The segment from ( x4, y4 ) --> ( x5, y5 )
    if (x4 < 255)
    {
        BacklightCurve.m4 =
            (INT16) ((((INT32) y5 - (INT32) y4) << 7) / ((INT32) 255 -
                                                         (INT32) x4));
    }
    else
    {
        BacklightCurve.m4 = 0;
    }

    BacklightCurve.x1 = x1;
    BacklightCurve.x2 = x2;
    BacklightCurve.x3 = x3;
    BacklightCurve.x4 = x4;
    BacklightCurve.y0 = y0;
    BacklightCurve.y1 = y1;
    BacklightCurve.y2 = y2;
    BacklightCurve.y3 = y3;
    BacklightCurve.y4 = y4;
}
/*************************************************************************************/
//            Customized Adaptive Backlight Driver (Internal Interfaces)
/*************************************************************************************/
extern DviStatus_t DviSta[2];
void _DRVCUST_DBLProc(void)
{
    INT32 i4BackLightModify = 0;
    INT32 i4TargetValue = 0;
    UINT8 bLongTimeAPL = 0;

    // Pause running adaptive backlight if video is not ready.
    if ((bBackLightCtrl != SV_FALSE)
        && (bDrvVideoSignalStatus(SV_VP_MAIN) == SV_VDO_STABLE) &&  (SRM_IsEpgMode() == FALSE))
    {
        if ((bGetVideoDecType(SV_VP_MAIN) == SV_VD_DVI)
            && (DviSta[0]._bDviTiming == MODE_WAIT))
        {
            return;
        }

        if (wFrameCount != 0)
        {
            dwSumAPL += (UINT32) bDrvGetAPL();

            // do modify back light job
            if ((wFrameCount == wDoModifyCount) && (bModifyPos < 2))
            {
                if ((bModifyPos == 1) && (wPreValue < 255))
                {
                    wCurValue = wPreValue + 1;
                    i4BackLightModify -= 1;
                }

                if ((bModifyPos == 0) && (wPreValue > 0))
                {
                    wCurValue = wPreValue - 1;
                    i4BackLightModify += 1;
                }

                //LOG(0, "Dimmer:%d\n", wCurValue);

                // set back light duty
                // vApiSetPanelBright(wCurValue);
                DRVCUST_SetBacklightDuty(VDP_ARG_BACKLT_DBL, wCurValue);
                // recursive
                wPreValue = wCurValue;

                // proceesing finish !
                if (i4BackLightModify == 0)
                {
                    bModifyPos = 2;
                }

                // Protect
                if (wDoModifyCount < wModifyInterval)
                {
                    bModifyPos = 2;
                }
                else
                {
                    wDoModifyCount -= wModifyInterval;
                }
            }

            wFrameCount--;
        }
        else
        {
            // Calculate bLongTimeAPL
            bLongTimeAPL =
                (UINT8) (dwSumAPL / (UINT32) wAutoContrastLongTime);

            // Reset to calculate next dwSumAPL
            wFrameCount = wAutoContrastLongTime;
            wDoModifyCount = wAutoContrastLongTime;
            dwSumAPL = 0;

            // Calculate i4TargetValue from bLongTimeAPL
            if (bLongTimeAPL < BacklightCurve.x1)
            {
                i4TargetValue =
                    ((((long) bLongTimeAPL -
                       0) * (long) BacklightCurve.m0) >> 7) +
                    (long) BacklightCurve.y0;
            }
            else if (bLongTimeAPL < BacklightCurve.x2)
            {
                i4TargetValue =
                    ((((long) bLongTimeAPL -
                       (long) BacklightCurve.x1) *
                      (long) BacklightCurve.m1) >> 7) +
                    (long) BacklightCurve.y1;
            }
            else if (bLongTimeAPL < BacklightCurve.x3)
            {
                i4TargetValue =
                    ((((long) bLongTimeAPL -
                       (long) BacklightCurve.x2) *
                      (long) BacklightCurve.m2) >> 7) +
                    (long) BacklightCurve.y2;
            }
            else if (bLongTimeAPL < BacklightCurve.x4)
            {
                i4TargetValue =
                    ((((long) bLongTimeAPL -
                       (long) BacklightCurve.x3) *
                      (long) BacklightCurve.m3) >> 7) +
                    (long) BacklightCurve.y3;
            }
            else
            {
                i4TargetValue =
                    ((((long) bLongTimeAPL -
                       (long) BacklightCurve.x4) *
                      (long) BacklightCurve.m4) >> 7) +
                    (long) BacklightCurve.y4;
            }

            // Calculate i4BackLightModify
            i4BackLightModify += (i4TargetValue - (long) wPreValue);

            // Calculate wModifyCount
            if (i4BackLightModify > 0)
            {
                bModifyPos = 1;
                wModifyInterval =
                    (UINT16) ((INT32) wAutoContrastModifyCount /
                              i4BackLightModify);
            }
            else if (i4BackLightModify < 0)
            {
                bModifyPos = 0;
                wModifyInterval =
                    (UINT16) ((INT32) wAutoContrastModifyCount /
                              (-i4BackLightModify));
            }
            else
            {
                bModifyPos = 2;
                wModifyInterval = 1;
            }

            // avoid wModifyInterval = 0
            if (wModifyInterval < 1)
            {
                wModifyInterval = 1;
            }
        }
    }
}

void _DRVCUST_LightSensorProc(void)
{
// to be defined by customer
}

/*************************************************************************************/
//            Customized Adaptive Backlight Driver (External Interfaces)
/*************************************************************************************/

void DRVCUST_AdaptiveBacklightInit(void)
{
    // ------------------------------------------------------------------------------------------------------------
    // this function defines the initialization of backlight algorithm
    // for example : initialize IIR strength, select default mapping table, defines backlight lower/upper bound, etc
    // you could also set initialzed backlight value in this function, in case no value in initialization
    // ------------------------------------------------------------------------------------------------------------    
    bBackLightCtrl = LUMA_BACKLIGHT_CTRL;
    wAutoContrastModifyCount = LUMA_MODIFY_COUNT;
    wAutoContrastLongTime = LUMA_LONG_TIME;

  	//DRVCUST_SetBacklightDuty(VDP_ARG_BACKLT_DBL, 0xFF);
}

void DRVCUST_AdaptiveBacklightProc(void)
{
    // ------------------------------------------------------------------------------------------------------------
    // this function defines customized algorithm for backlight behavior, which may contain
    // dynamic backlight, light sensor control, etc
    //
    // following listed function are frequently used in these algorithms, you could also find them
    // in drv_meter.c :
    // 
    //      UINT8 bDrvGetAPL(void) : returns average picture level
    //      UINT8 u1DrvGetLumaHist(UINT16 waHistCur[LUMA_HIST_NUM], UINT16 u2NormBase) : get (normalized) luma histogram
    //      UINT8 bDrvGetOutputLumaMax(UINT8 bPath) : returns picture luma maximum value in output stage
    //
    // you should set your algorithm result through DRVCUST_SetBacklightDuty(UINT8 u1RefIndex, UINT16 u2Duty)
    // that you can manipulate final backlight result which may controlled by several backlight algorithms
    //
    // sample code example : 
    //
    // UINT8 u1CurrentAPL = bDrvGetAPL();
    // DRVCUST_SetBacklightDuty(VDP_ARG_BACKLT_DBL, u1CurrentAPL);  // which directly maps backlight from APL        
    // ------------------------------------------------------------------------------------------------------------
    
    _DRVCUST_DBLProc();
    _DRVCUST_LightSensorProc();
}

void DRVCUST_AdaptiveBacklightParam(UINT8 bMode)
{
    // ------------------------------------------------------------------------------------------------------------
    // this function defines the behavior when UI parameter changed
    // for example : UI have 3 level (low (0) / mid (1) / high (2))
    // when level changed, backlight mapping changed according to pre-defined behavior
    // ------------------------------------------------------------------------------------------------------------
    bBackLightCtrl = bMode;

    switch (bMode)
    {
    case DYNAMIC_CONTRAST_RATIO_150PERCENT:
        _AdaptiveBacklightSetCurve(bHalGetAdpBacklight(ADP_BACKLIGHT_MIDDLE_IDX),      // y0
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_MIDDLE_IDX),      // y1
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_MIDDLE_IDX),      // y2
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX) // y5
            );
        break;
    case DYNAMIC_CONTRAST_RATIO_200PERCENT:
        _AdaptiveBacklightSetCurve(bHalGetAdpBacklight(ADP_BACKLIGHT_LOW_IDX), // y0
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_LOW_IDX), // y1
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_LOW_IDX), // y2
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX) // y5
            );
        break;
    default:
    case DYNAMIC_CONTRAST_RATIO_100PERCENT: // Turn Off Dynamic Backlight
        _AdaptiveBacklightSetCurve(bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y0
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y1
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y2
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            (UINT8)DRVCUST_OptGet(eAdpBacklightAplBright),     // x4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX) // y5
            );
        //DRVCUST_SetBacklightDuty(VDP_ARG_BACKLT_DBL, 0xFF);        
        break;
    }
}

void DRVCUST_SetBacklightDuty(UINT8 u1RefIndex, UINT16 u2Duty)
{
    // ------------------------------------------------------------------------------------------------------------    
    // Customized backlight interface
    // Could be defined as multiplication of several result :
    // such as (1) adaptive backlight * (2) UI value * (3) light sensor value
    // ------------------------------------------------------------------------------------------------------------    
    static UINT16 u2BacklightSrc[VDP_ARG_BACKLT_MAX] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
    UINT16 u2BacklightDuty;

    u2BacklightSrc[u1RefIndex] = u2Duty;

    switch(u1RefIndex)
    {
        case VDP_ARG_BACKLT_UI:
        case VDP_ARG_BACKLT_DBL:
            u2BacklightDuty = (u2BacklightSrc[VDP_ARG_BACKLT_UI] * u2BacklightSrc[VDP_ARG_BACKLT_DBL]) >> 8;
            if(bIsPanelOn() == SV_ON)
            {                     
            vApiSetPanelBright((UINT8)u2BacklightDuty);
            }
            break;
        case VDP_ARG_BACKLT_LIGHT_SNSR:
            // behavior to be defined by customer
            break;
        case VDP_ARG_BACKLT_OPC:
            // behavior to be defined by customer
            break;            
        case VDP_ARG_BACKLT_SYSTEM_SET:
            u2BacklightDuty = u2BacklightSrc[VDP_ARG_BACKLT_SYSTEM_SET];
            if(bIsPanelOn() == SV_ON)
            {         
            vApiSetPanelBright((UINT8)u2BacklightDuty);
            }
            break;            
            
    }        
}

/**************************************************************************/
//         D_INTERFACE Adaptive Backlight Param Update
/**************************************************************************/
void DRVCUST_AutoBacklightUpdate(AUTO_BACKLIGHT_SETS_INFO_T *aBLight)
{
    UNUSED(aBLight);
    return;
}

/**************************************************************************/
//         Customized Backlight Driver For Loader Boot Sequence
/**************************************************************************/
void DRVCUST_UpdateBacklightState(UINT8 u1State, UINT16 u2Duty)
{
    UNUSED(u1State);
    UNUSED(u2Duty);
}
