/* 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"
#include "drv_pwm.h"
#include "hw_tcon.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;
AUTO_BACKLIGHT_SETS_INFO_T stBLight;
UINT32 dwOpticalSensorAvgValue;

/*-------------LED Motion-----------------------*/
extern BOOL u1LEDMotionFlag;


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;
            }
        }
    }
}

UINT32 u4LightSensorQueAverage(UINT32 u4Value)
{
	UINT32 bi;
	UINT32 u4LightSensorAverage=0;
	UINT32 u4LightSensorQueSize = 255;
	static UINT32 u4LightSensorQueIndex=0;
	static UINT32 u4LightSensorDataQue[256]=
        {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
         0,0,0,0,0,0};

	u4LightSensorQueIndex = (u4LightSensorQueIndex<u4LightSensorQueSize)?(u4LightSensorQueIndex+1):u4LightSensorQueSize;
	
	for (bi = u4LightSensorQueIndex-1; bi > 0 ; bi--)
	{
		u4LightSensorDataQue[bi] = u4LightSensorDataQue[bi - 1];
		u4LightSensorAverage += u4LightSensorDataQue[bi];
	}

	u4LightSensorDataQue[0] = u4Value;
	u4LightSensorAverage += u4Value;
	u4LightSensorAverage = u4LightSensorAverage/u4LightSensorQueIndex;

	return u4LightSensorAverage;
}

void _DRVCUST_LightSensorProc(void)
{
		UINT32 dwServoADC;

		dwServoADC = (PDWNC_ReadServoADCChannelValue(LIGHT_SENSOR_ADC_PORT) >> 3);
	
		dwServoADC = MIN(255, dwServoADC);
		dwOpticalSensorAvgValue = (UINT8)u4LightSensorQueAverage(dwServoADC);
		

}

/*************************************************************************************/
//            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)
{
#if 0
    // ------------------------------------------------------------------------------------------------------------
    // 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
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_MIDDLE_IDX),      // y1
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_MIDDLE_IDX),      // y2
                            DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            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
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_LOW_IDX), // y1
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_LOW_IDX), // y2
                            DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            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
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x1
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y1
                            DRVCUST_OptGet(eAdpBacklightAplDark),       // x2
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y2
                            DRVCUST_OptGet(eAdpBacklightAplBright),     // x3
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y3
                            DRVCUST_OptGet(eAdpBacklightAplBright),     // x4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX),        // y4
                            bHalGetAdpBacklight(ADP_BACKLIGHT_HIGH_IDX) // y5
            );
        DRVCUST_SetBacklightDuty(VDP_ARG_BACKLT_DBL, 0xFF);        
        break;
    }
#endif
}

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];
    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(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(u2BacklightDuty);
            }
            break;            
            
    }        
}

/**************************************************************************/
//         D_INTERFACE Adaptive Backlight Param Update
/**************************************************************************/
void DRVCUST_AutoBacklightUpdate(AUTO_BACKLIGHT_SETS_INFO_T *aBLight)
{
    if (stBLight.ui1BacklightLevel != aBLight->ui1BacklightLevel)
    {        
        stBLight.ui1BacklightLevel = aBLight->ui1BacklightLevel;
        DRVCUST_SendEvent(E_CUST_PIC_SETTING_CHANGE, 0);
    }

    if ((stBLight.ui1PowerSavingMode != aBLight->ui1PowerSavingMode) ||
        (stBLight.ui1LightSensorOnOff != aBLight->ui1LightSensorOnOff))
    {
        stBLight.ui1PowerSavingMode = aBLight->ui1PowerSavingMode;
		stBLight.ui1LightSensorOnOff = aBLight->ui1LightSensorOnOff;
        DRVCUST_SendEvent(E_CUST_ECO_SETTING_CHANGE, 0);
    }
    return;
}

/**************************************************************************/
//         Customized Backlight Driver For Loader Boot Sequence
/**************************************************************************/
void DRVCUST_UpdateBacklightState(UINT8 u1State, UINT16 u2Duty)
{
    UNUSED(u1State);
    UNUSED(u2Duty);
}
/**************************************************************************/
//        LED Motion - ScanPWM
/**************************************************************************/
BOOL DRVCUST_GetLEDMotionFlag(void)
{
	return u1LEDMotionFlag;
}

/**************************************************************************/
//         Customized PWM Driver
/**************************************************************************/
// u1Channel : 0 is I-Duty. 1 is for I-Peak.
#define PWM_I_DUTY 0
#define PWM_I_PEAK 1
#define PWM_RETURN_ERROR 0xFFFF

#define eBLK_Pulse_Port0  2    //Scanning pwm
#define eBLK_Pulse_Port1  3
#define eBLK_Pulse_Port2  4
#define eBLK_Pulse_Port3  5

typedef enum
{
    eNormal          = 0,
    eHigh_Fix        = 1,
    eLow_Fix         = 2
}Out_Mode;

typedef struct
{
	Out_Mode Mode;
    UINT16 u2duty;
}PWM_Status;

PWM_Status arPWM_Status[2] = {{eNormal, 1024},{eNormal, 1024}};    //PWM Status
UINT8 arScanPWM_Mode[4] = {eNormal,eNormal,eNormal,eNormal};       //Scan PWM Mode

/**************************************************************************/
// Private function : To get PWM Addr.
static UINT32 Backlight_PwmGetAddr(UINT8 u1Channel)
{
	UINT32 u4Reg = 0;
	switch(u1Channel)
	{
		case PWM_I_DUTY:
			u4Reg = REG_RW_PWM0;
			break;

		case PWM_I_PEAK:
			u4Reg = REG_RW_PWM1;
			break;

		default:
			LOG(0, "PWM NOT support (%d) Channel", u1Channel);
	}	

	return u4Reg;
}

// Frequency = 192MHz / (timeBase + 1) / (1024 + 1) // DutyAll is fix to 1024.
// timeBase is (4 ~ 4400)
// Func: set TimeBase, it is another way of saying PreScaler, it is PWM P
void DRVCUST_SetBLPwmFrequency(UINT8 u1Channel, UINT32 u4TimeBase)
{
    UINT32 u4Reg;

    //Check timeBase
    if (u4TimeBase >= 4 && u4TimeBase <= 4400)
    {
        u4Reg = Backlight_PwmGetAddr(u1Channel);
        if (u4Reg != 0)
        {
            IO_WRITE32MSK(HPDS_BASE, u4Reg, 0x40000000, 0xFFF00000);// PWMRSN fix to 1024
            IO_WRITE32MSK(HPDS_BASE, u4Reg + 4, u4TimeBase, 0xFFFFF);// Set TimeBase
            IO_WRITE32_SET(HPDS_BASE, u4Reg, 0);// Enable PWM
        }
    }
    else
    {
        LOG(0, "PWM timeBase(%d) is too large", u4TimeBase);
    }
}

// Return : timeBase [4, 4400]
// Return OK:[4, 4400] ; Fail : 0xFFFF
UINT32 DRVCUST_GetBLPwmFrequency(UINT8 u1Channel)
{
	UINT32 u4Reg;
	UINT32 u4RVal = 0;
	u4Reg = Backlight_PwmGetAddr(u1Channel);
	if(u4Reg != 0)
	{
		u4RVal = IO_READ32(HPDS_BASE, u4Reg+4);
		u4RVal &= 0xFFFFF;
		
		return u4RVal;
	}
	else// means error
	{
		return PWM_RETURN_ERROR;
	}
}

// Duty All is RSN, so Duty cycle is Duty On, is PWM H.
// Paramters :
// u2Ratio : Duty cycle (0 ~ 1024 : 0-100%)
void DRVCUST_SetBLPwmDuty(UINT8 u1Channel, UINT16 u2Ratio)
{
	UINT32 u4Value;
	UINT32 u4Reg;

    if((u1Channel>=2)||(u2Ratio>1024))
    {
        LOG(0, "DRVCUST_SetBLPwmDuty,Parameter Incorrect! [Channel(%d),Ratio(%d)]\n",
            u1Channel,u2Ratio);
        return;
    }

    arPWM_Status[u1Channel].u2duty = u2Ratio;
    if(arPWM_Status[u1Channel].Mode == eHigh_Fix)
	{
        u2Ratio = 1024;
        LOG(0, "DRVCUST_SetBLPwmDuty high\n");
    }
    else if(arPWM_Status[u1Channel].Mode == eLow_Fix)
    {
        u2Ratio = 0;
        LOG(0, "DRVCUST_SetBLPwmDuty low\n");
    }
    
	u4Reg = Backlight_PwmGetAddr(u1Channel);
	if(u4Reg != 0)
	{
		if(u2Ratio <= 1024)    // 0~1024
		{
            if (u2Ratio == 1024)// fix when duty is 1024. duty cycle = 99.9%. duty cycle = duty/(1024 + 1)
            {
                u2Ratio = 1025;
            }
             // PWMRSN fix to 1024 and enable
            u4Value = (((UINT32)0x400 << 20) |
                       ((UINT32)(u2Ratio & 0xFFF) << 4) | 1);
            IO_WRITE32(HPDS_BASE, u4Reg, u4Value);
		}
	}
}

// Return : Duty cycle (0 ~ 1024 : 1-100%)
// Return OK:[0, 1024] ; Fail : 0xFFFF
UINT16 DRVCUST_GetBLPwmDuty(UINT8 u1Channel)
{
	UINT32 u4Reg;
	UINT32 u4RVal = 0;
	u4Reg = Backlight_PwmGetAddr(u1Channel);
	if(u4Reg != 0)
	{
		u4RVal = IO_READ32(HPDS_BASE, u4Reg);
		u4RVal &= 0xFFF0;
		u4RVal = u4RVal >> 4;

        if (u4RVal == 1025)
        {
            u4RVal = 1024;
        }

		return u4RVal;
	}
	else// means error
	{
		return PWM_RETURN_ERROR;
	}
}

/**************************************************************************/
//         Customized scan PWM Driver
/**************************************************************************/
extern UINT8 fgBLCustomCtrl;  // 1: Backlight can be controled by the customer.



typedef struct
{
	INT32 u4Start;
	UINT32 u4High; 
	UINT32 u4Low;
	INT32 u4Phase;
}M_SCANPWM_T;

static M_SCANPWM_T arScanPWM[SCANPWM_MAX_CH]={{0, 500, 500, 200},
											  {0, 500, 500, 200},
											  {0, 500, 500, 200},
											  {0, 500, 500, 200}};

UINT16 arScanPWMPulseNum[SCANPWM_MAX_CH]={1, 1, 1, 1};
UINT16 arScanPWMRatio[SCANPWM_MAX_CH]={512, 512, 512, 512};
UINT32 u4ScanPWMcommonPhase=225;  // unit:line.
INT32 arScanPWMPhase[SCANPWM_MAX_CH]={-562, -562, -562, -562};  // unit:line

void DRVCUST_SetBLScanPwmNumberOfPulses(UINT16* pulseNumber)
{
    UINT16 u2Devider = 1;
    UINT8  u1Src=0;
    INT32 u4Start=0;
    UINT32 u4High=0;
    UINT32 u4Low=0;
    UINT32 u4Phase = 0;

    for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
    {
        u2Devider = *(pulseNumber + u1Src);
        u4Start = arScanPWM[u1Src].u4Start;
        u4High = arScanPWM[u1Src].u4High;
        u4Low = arScanPWM[u1Src].u4Low;
        u4Phase = arScanPWM[u1Src].u4Phase;

        if (u2Devider == 0)
        {
            LOG(0, "Error, DRVCUST_SetBLScanPwmNumberOfPulses, data is 0, src = %d\n", u1Src);
        }
        arScanPWMPulseNum[u1Src] = u2Devider;
        if(IO32ReadFldAlign(PWM_SCAN_12,REG_PWM_SCAN_DBG_MODE) != 0)
            LOG(2, "[scan][Pulse]phase=%d, start=%d, High=%d, low=%d, PulseNum=%d\n", u4Phase, u4Start, u4High, u4Low, u2Devider);

        if(!fgIsScanPWMSetDataLatch())
        {
            vDrvSetScanPWMLatchMode(SCAN_PWM_LATCH_MODE_SETDATA_OFF,SCAN_PWM_LATCH_MODE_OFF,SCAN_PWM_ALIGN_MODE_VSYNC);  // Aligned Vsync
        }
			
        // Invert PWM dimming signal
        if(DRVCUST_PanelGet(eFlagBacklightDimmingInvert))
        {
            vDrvSetScanPWMPolarity((u1Src+SCANPWM_FIRST_CH), 1);
        }
		
        vDrvSetScanPWM((u1Src+SCANPWM_FIRST_CH),u4Start,u4High,u4Low);

        vDrvSetScanPWMStartPhase((u1Src+SCANPWM_FIRST_CH),u4Phase);

        vDrvScanPWMDataFire();
    }
}

void DRVCUST_GetBLScanPwmNumberOfPulses(UINT16* pulseNumber)
{
	UINT8  u1Src=0;
	
	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		*(pulseNumber + u1Src) = arScanPWMPulseNum[u1Src];
	}
}

void DRVCUST_SetBLScanPwmDuty(UINT16* ratio)
{
	UINT16 u2Duty;
	UINT16 u2Devider = 1;
	UINT8  u1Src=0;
	INT32 u4Phase=0;
	INT32 u4Start=0;
	UINT32 u4High=0;
	UINT32 u4Low=0;

	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		u2Duty = *(ratio+u1Src);
		u2Devider = arScanPWMPulseNum[u1Src];
		u4Phase = arScanPWM[u1Src].u4Phase;
		
		if(u2Duty >= 1024)
	    {
	        u2Duty = 1024;
	    }
        
		arScanPWMRatio[u1Src] = u2Duty;
        if(arScanPWM_Mode[u1Src] != eNormal)
            continue;

		u4High = u2Duty * 1000 / 1024;
		u4Low = 1000 - u4High - u4Start;
        if(IO32ReadFldAlign(PWM_SCAN_12,REG_PWM_SCAN_DBG_MODE) != 0)
            LOG(2, "[scan][Duty]phase=%d, start=%d, High=%d, low=%d, PulseNum=%d\n", u4Phase, u4Start, u4High, u4Low, u2Devider);
		
		if(!fgIsScanPWMSetDataLatch())
		{
			vDrvSetScanPWMLatchMode(SCAN_PWM_LATCH_MODE_SETDATA_OFF,SCAN_PWM_LATCH_MODE_OFF,SCAN_PWM_ALIGN_MODE_VSYNC);  // Aligned Vsync
		}
			
		// Invert PWM dimming signal
	    if(DRVCUST_PanelGet(eFlagBacklightDimmingInvert))
	    {
			vDrvSetScanPWMPolarity((u1Src+SCANPWM_FIRST_CH), 1);
	    }
		
		vDrvSetScanPWM((u1Src+SCANPWM_FIRST_CH),u4Start,u4High,u4Low);

		vDrvScanPWMDataFire();

		//arScanPWMRatio[u1Src] = u2Duty;
		arScanPWM[u1Src].u4High = u4High;
		arScanPWM[u1Src].u4Low = u4Low;
	}
}

void DRVCUST_GetBLScanPwmDuty(UINT16* ratio)
{
	UINT8  u1Src=0;
	
	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		*(ratio+u1Src) = arScanPWMRatio[u1Src];
	}
}

void DRVCUST_SetBLScanPwmPhase(UINT32* commonPhase, INT32* phase)
{
	UINT16 u2VTotal;
	UINT8  u1Src=0;
	UINT16 u2Duty;
	UINT16 u2Devider = 1;
	INT32 u4Phase=0;
	UINT32 u4MPhase=0;
	INT32  wSPhase=0;

	u2VTotal = wDrvGetPnlVTotal();

	u4ScanPWMcommonPhase = *commonPhase;
	u4MPhase = u4ScanPWMcommonPhase;
	
	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		u2Duty = arScanPWMRatio[u1Src];
		u2Devider = arScanPWMPulseNum[u1Src];
		if(u2Duty >= 1024)
	    {
	        u2Duty = 1024;
	    }

		wSPhase = *(phase+u1Src);
		u4Phase = ((INT32)u4MPhase + wSPhase)*1000/u2VTotal;
		if (u4Phase >= 1000)
			u4Phase -= 1000;
		else if ((u4Phase+1000) < 0)
			u4Phase += 1000;
        if(IO32ReadFldAlign(PWM_SCAN_12,REG_PWM_SCAN_DBG_MODE) != 0)
            LOG(2, "[scan][Phase]phase=%d PulseNum=%d\n", u4Phase,u2Devider);

		if(!fgIsScanPWMSetDataLatch())
		{
			vDrvSetScanPWMLatchMode(SCAN_PWM_LATCH_MODE_SETDATA_OFF,SCAN_PWM_LATCH_MODE_OFF,SCAN_PWM_ALIGN_MODE_VSYNC);  // Aligned Vsync
		}
			
		// Invert PWM dimming signal
	    if(DRVCUST_PanelGet(eFlagBacklightDimmingInvert))
	    {
			vDrvSetScanPWMPolarity((u1Src+SCANPWM_FIRST_CH), 1);
	    }

		vDrvSetScanPWMStartPhase((u1Src+SCANPWM_FIRST_CH),u4Phase);

		vDrvScanPWMDataFire();

		arScanPWMPhase[u1Src] = *(phase+u1Src);
		arScanPWM[u1Src].u4Phase = u4Phase;
	}
}


void DRVCUST_GetBLScanPwmPhase(UINT32* commonPhase, INT32* phase)
{
	UINT8  u1Src=0;

	*commonPhase = u4ScanPWMcommonPhase;
	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		*(phase+u1Src) = arScanPWMPhase[u1Src];
	}
}

void DRVCUST_InitBLScanPWM(UINT16 pulseNumber, UINT16 ratio, UINT32 commonPhase, INT32 phase)
{
	UINT16 u2VTotal;
	UINT8  u1Src=0;
	UINT16 u2Duty;
	INT32  wSPhase=0;

    LOG(2, "[scan][Init]pulse=%d, duty(1024)=%d, commPhase(line)=%d, pphase(line)=%d\n", pulseNumber, ratio, commonPhase, phase);
	
    u2VTotal = wDrvGetPnlVTotal();

	u4ScanPWMcommonPhase = commonPhase;
	
	for (u1Src=0;u1Src<SCANPWM_MAX_CH;u1Src++)
	{
		arScanPWMPulseNum[u1Src] = pulseNumber;
		arScanPWMRatio[u1Src] = ratio;
		arScanPWMPhase[u1Src] = phase;

		u2Duty = arScanPWMRatio[u1Src];
		if(u2Duty >= 1024)
	    {
	        u2Duty = 1024;
	    }
		arScanPWM[u1Src].u4Start = 0;
		arScanPWM[u1Src].u4High = u2Duty * 1000 / 1024;
		arScanPWM[u1Src].u4Low = 1000 - arScanPWM[u1Src].u4High - arScanPWM[u1Src].u4Start;
		arScanPWM[u1Src].u4Phase = (u4ScanPWMcommonPhase+arScanPWMPhase[u1Src])*1000/u2VTotal;
		LOG(2, "[scan][Init]phase=%d, start=%d, High=%d, low=%d\n", arScanPWM[u1Src].u4Phase, arScanPWM[u1Src].u4Start, arScanPWM[u1Src].u4High, arScanPWM[u1Src].u4Low);
		LOG(2, "[scan][Init]pulse=%d\n", arScanPWMPulseNum[u1Src]);
	}
}

/**************************************************************************/
//         Customized PWM Driver ---- common part
/**************************************************************************/
void DRVCUST_SetBLPwmMode(UINT8 channel, UINT8 mode)  // mode(0:normal; 1: high fix; 2: low fix)
{
    if((channel > eBLK_Pulse_Port3) || (mode > eLow_Fix))
    {
        LOG(0, "DRVCUST_SetBLPwmMode: Parameter Incorrect! [Channel(%d),Mode(%d)]\n",
                channel,mode);
        return;
    }
    LOG(2, "[scan]DRVCUST_SetBLPwmMode(%d %d)\n", channel,mode);

	switch(channel)
    {
        case PWM_I_DUTY:    // I-Duty    (pwm0)
        case PWM_I_PEAK:    // I-Peak   ( pwm1)
            arPWM_Status[channel].Mode = mode;
            DRVCUST_SetBLPwmDuty(channel, arPWM_Status[channel].u2duty);
            break;
        case eBLK_Pulse_Port0:    // BLK_Pulse-Port[0]    (Scanning pwm0)
        case eBLK_Pulse_Port1:    // BLK_Pulse-Port[1]    (Scanning pwm1)
        case eBLK_Pulse_Port2:    // BLK_Pulse-Port[2]    (Scanning pwm2)
        case eBLK_Pulse_Port3:    // BLK_Pulse-Port[3]    (Scanning pwm3)
            switch(mode)
            {
                case eNormal:
                    arScanPWM_Mode[channel-eBLK_Pulse_Port0] = eNormal;
                    DRVCUST_SetBLScanPwmDuty(arScanPWMRatio);
                    break;
                case eHigh_Fix:
                case eLow_Fix:
                    arScanPWM_Mode[channel-eBLK_Pulse_Port0] = mode;				
                    vDrvSetScanFixHighLow(channel+abs(SCANPWM_FIRST_CH-eBLK_Pulse_Port0),mode);
                    vDrvScanPWMDataFire();
                    break;
            }
            break;
    }
}

UINT8 DRVCUST_GetBLPwmMode(UINT8 channel)  // mode(0:normal; 1: high fix; 2: low fix)
{
    if(channel > eBLK_Pulse_Port3)
    {
        LOG(0, "DRVCUST_GetBLPwmMode: Parameter Incorrect! [Channel(%d)]\n",
                channel);
        return 0xFF;
    }

	switch(channel)
    {
        case PWM_I_DUTY:    // I-Duty    (pwm0)
        case PWM_I_PEAK:    // I-Peak   ( pwm1)
            return (UINT8)(arPWM_Status[channel].Mode);
            break;
        case eBLK_Pulse_Port0:    // BLK_Pulse-Port[0]    (Scanning pwm0)
        case eBLK_Pulse_Port1:    // BLK_Pulse-Port[1]    (Scanning pwm1)
        case eBLK_Pulse_Port2:    // BLK_Pulse-Port[2]    (Scanning pwm2)
        case eBLK_Pulse_Port3:    // BLK_Pulse-Port[3]    (Scanning pwm3)
            return arScanPWM_Mode[channel-eBLK_Pulse_Port0];
            //return (u1DrvGetScanFixHighLow(channel-1));
            break;
        default:
            return 0xFF;
    }
}

UINT8 DRVCUST_GetBLControlState(void)
{
	return (fgBLCustomCtrl);
}
