/* 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.
 *
 *****************************************************************************/


/*----------------------------------------------------------------------------*
 *
 * $Author: louis.ko $
 * $Date: 2015/07/16 $
 * $RCSfile: ckgen.c $
 * $Revision: #4 $
 *
 *---------------------------------------------------------------------------*/


/*----------------------------------------------------------------------------*
 * Include files
 *---------------------------------------------------------------------------*/
#include "x_lint.h"
#include "x_ckgen.h"
#include "x_hal_5381.h"
#include "x_hal_arm.h"
#include "x_assert.h"
#include "x_printf.h"
#include "x_dram.h"
#include "x_timer.h"
#include "x_debug.h"
#include "drvcust_if.h"
#include "aud_if.h"
#include "hw_vdoin.h"
#include "hw_ckgen.h"
#include "hw_ifpll.h"
#include "hw_pdwnc.h"

#ifndef CC_MTK_LOADER
#include "hw_vfe.h"
#include "nim_drvif.h"
#include "x_drv_cli.h"
#include "hw_pll_group.h"
#endif

//-----------------------------------------------------------------------------
// Configuration
//-----------------------------------------------------------------------------
extern void AudPll2Setting(SAMPLE_FREQ_T eFs);

//-----------------------------------------------------------------------------
// Constant definitions
//-----------------------------------------------------------------------------
#ifdef DEFINE_IS_LOG
#undef DEFINE_IS_LOG
#endif

#ifdef CC_MTK_LOADER
#define DEFINE_IS_LOG(level, fmt...)   Printf(fmt)
#else /* CC_MTK_LOADER */
#define DEFINE_IS_LOG   GPIO_IsLog
#endif /* CC_MTK_LOADER */

//-----------------------------------------------------------------------------
// Static routines
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
/** _CalString() to get the divider of the calibrated PLL.
 *  @retval     Return the selective count of the PLL.
 */
//-----------------------------------------------------------------------------
static CHAR * _CalString(CAL_SRC_T eSource)
{
    switch(eSource)
    {
    case CAL_SRC_SAWLESSPLL:
        return "SAWLESSPLL";
    case CAL_SRC_CPUPLL_D2:
        return "CPUPLL_D2";
    case CAL_SRC_SYSPLL_D2:
        return "SYSPLL_D2";
    case CAL_SRC_APLL1:
        return "APLL1";
    #if 0
    case CAL_SRC_APLL1_D3:
        return "APLL1_D3";
    #endif
    case CAL_SRC_APLL2:
        return "APLL2";
    case CAL_SRC_ADCPLL:
        return "ADCPLL";
    case CAL_SRC_M_CK:
        return "M_CK";
    case CAL_SRC_G3DPLL:
        return "VDECPLL";
    case CAL_SRC_FBOUT:
        return "FBOUT";
    case CAL_SRC_XTAL:
        return "XTAL";
    case CAL_SRC_PIXPLL:
        return "PPLL";
    case CAL_SRC_AD_HSYNC_OUT:
        return "AD_HSYNC_OUT";
    case CAL_SRC_AD_VSYNC_OUT:
        return "AD_VSYNC_OUT";
    case CAL_SRC_AD_SOGY_OUT:
        return "AD_SOGY_OUT";
//  case CAL_SRC_CVBSADC_CKOUT:
//      return "AD_CVBSADC_CKOUT";
    case SRC_CPU_CLK:
        return "SRC_CPU_CLK";
    case SRC_MEM_CLK:
        return "SRC_MEM_CLK";
    case SRC_BUS_CLK:
        return "SRC_BUS_CLK";
    case SRC_TIMER_CLK:
        return "SRC_TIMER_CLK";
    case SRC_VDOIN_MPCLK:
        return "SRC_VDOIN_MPCLK";

    default:
        return "unknown PLL";
    }
}

static void _CalEnablePLL(CAL_SRC_T eSource)
{
    switch(eSource)
    {
        default:
            return;
    }
}

static void _CalSetPLL(CAL_SRC_T eSource)
{
    UNUSED(eSource);
}

static void _CalSetMN(CAL_SRC_T eSource, UINT32 u4Clock)
{
    switch(eSource)
    {
    default:
        break;
    }
}

//-----------------------------------------------------------------------------
/** _CalGetPLLCounter() to get the PLL clock
 *  @retval the return value is the kHz NOT Hz.
 */
//-----------------------------------------------------------------------------
UINT32 _CalGetPLLCounter(UINT32 u4CalSel)
{

    UINT32 u4Val, u4Mode, u4RefCount;
#ifndef CC_MTK_LOADER
    CRIT_STATE_T rCrit;

    rCrit = x_crit_start();
#endif
    // Turn on Level 2
    if(u4CalSel > SRC_LEVEL_1_CK_OUT)
    {
        vIO32WriteFldAlign(CKGEN_REG_FREQ_DBG_CKCFG, (u4CalSel & 0x1F), FLD_FREQ_DBG_SEL);
    }

    // Select PLL source at REG_PLL_CALIB
    vIO32WriteFldAlign(CKGEN_PLLCALIB, CAL_SRC_SAWLESSPLL, FLD_DBGCKSEL);

    // Select mode, 0: 2048
    vIO32WriteFldAlign(CKGEN_PLLCALIB, 0, FLD_CAL_MODE);

    // Set CAL_SEL, 0 is clk/1, 1 is clk/2, 2 is clk/4
    vIO32WriteFldAlign(CKGEN_PLLCALIB, 0, FLD_CAL_SEL);

    // Select PLL source at REG_PLL_CALIB
    vIO32WriteFldAlign(CKGEN_PLLCALIB, u4CalSel, FLD_DBGCKSEL);

    // Set PLL_CAL_TRIGGER = 1
    vIO32WriteFldAlign(CKGEN_PLLCALIB, 1, FLD_CAL_TRI);

    // Wait calibration finishing.
    while (IO32ReadFldAlign(CKGEN_PLLCALIB, FLD_CAL_TRI)) { }

    // Calculate clock.
    HAL_Delay_us(10);
    u4Val = IO32ReadFldAlign(CKGEN_PLLCALIBCNT, FLD_CALI_CNT);

    u4Mode = IO32ReadFldAlign(CKGEN_PLLCALIB, FLD_CAL_MODE);

    // Turn off Level 2
    vIO32WriteFldAlign(CKGEN_REG_FREQ_DBG_CKCFG, 0, FLD_FREQ_DBG_SEL);

    switch(u4Mode) {
    case 0: u4RefCount = 11; break;
    case 1: u4RefCount = 16; break;
    case 2: u4RefCount = 20; break;
    case 3: u4RefCount = 24; break;
    default: u4RefCount = 11; break;
    }

#ifndef CC_MTK_LOADER
    x_crit_end(rCrit);
#endif
    return (((GET_XTAL_CLK()/1000) * u4Val ) >> u4RefCount);
}

//=====================================================================
// Public

#if CKGENHW_DEBUG

void CKGENDB_IoWr(UINT32 u4Offset, UINT32 u4Value, INT8 *szFile, INT32 Line)
{
    LOG(_W, "%s(%d) write 0x%08x with val 0x%08x\n", szFile, Line, u4Offset, u4Value);
    IO_WRITE32(CKGEN_BASE, u4Offset, u4Value);
}

UINT32 CKGENDB_IoRd(UINT32 u4Offset, INT8 *szFile, INT32 Line)
{
    UINT32 u4Value;

    u4Value = IO_READ32(CKGEN_BASE, u4Offset);
    LOG(_R, "%s(%d) read 0x%08x getting val 0x%08x\n", szFile, Line, u4Offset, u4Value);
    return u4Value;
}

#endif /* CKGENHW_DEBUG */


//=====================================================================
// Interface

void BSP_CkgenInit()
{
    vIO32WriteFldAlign(CKGEN_PLLCALIB, 0, FLD_CAL_MODE);
}

BOOL BSP_Calibrate(CAL_SRC_T eSource, UINT32 u4Clock)
{
    BOOL fgRet = FALSE;
    UINT32 u4CalClk;
    //UINT32 u4Crit;

    // Before Calibration, enable the PLL.
    _CalEnablePLL(eSource);

    _CalSetMN(eSource, u4Clock);

    switch(eSource)
    {
        case CAL_SRC_PIXPLL:
        case CAL_SRC_ADCPLL:
            fgRet = TRUE;
            if (DRVCUST_InitGet(eFlagBootVerbose))
            {
                // Delay a 1 ms to make close loop clock stable
                HAL_Delay_us(1000);

                u4CalClk = _CalGetPLLCounter((UINT32)(eSource));
                LOG(0, "%s now is %d kHz\n", _CalString(eSource), u4CalClk);
            }
            break;
        default:
            return fgRet;
    }

    // Do the post settings.
    _CalSetPLL(eSource);

    return fgRet;
}


UINT32 BSP_GetClock(CAL_SRC_T eSource, UINT8* pu1Band, UINT16* pu2MS, UINT16* pu2NS, UINT16* pu2Counter)
{
    UINT32 u4Clock = 0;

    UNUSED(pu2MS);
    UNUSED(pu2NS);
    UNUSED(pu1Band);

    switch(eSource)
    {
    case SRC_SYSPLL_CLK:
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_SYSPLL_D2);
        u4Clock = u4Clock * 1000 * 2;
        break;
    case SRC_VDECPLL_CLK:
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_VDECPLL_CK588M);
        u4Clock = u4Clock * 1000;
        break;
    case SRC_DMUXPLL_CLK:
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_DMUXPLL_CK396M);
        u4Clock = u4Clock * 1000;
        break;
    case SRC_G3DPLL_CLK:   // G3DPLL
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_G3DPLL);
        u4Clock = u4Clock * 1000;
        break;
    case SRC_CPU_CLK:
        u4Clock = _CalGetPLLCounter(CAL_SRC_HPCPLL_CK1000M);
        u4Clock = u4Clock * 1000;
        break;
    case SRC_MEM_CLK:
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_M_CK);
        u4Clock = u4Clock * 1000; // return digital design dram domain clock.
        break;
    case SRC_BUS_CLK:
        switch (IO32ReadFldAlign(CKGEN_CPU_CKCFG, FLD_BUS_CK_SEL))
        {
            case 0: u4Clock = GET_XTAL_CLK(); break;
            case 1: u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL)/2; break;
            case 2: u4Clock = BSP_GetDomainClock(CAL_SRC_USBPLL)*2/5; break;
            case 3: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2)/2; break;
            case 4: u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL)/3; break;
            case 5: u4Clock = BSP_GetDomainClock(CAL_SRC_USBPLL)/4; break;
            case 6: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2)*2/3; break;
            case 7: u4Clock = (GET_XTAL_CLK() / (1U << (4+IO32ReadFldAlign(CKGEN_MISC_CKCFG, FLD_SLOW_CK)))); break;
            default: u4Clock = GET_XTAL_CLK(); break;
        }
        break;
    case SRC_TIMER_CLK:
        u4Clock = GET_XTAL_CLK();
        break;
    case SRC_POCLK:
        switch (IO32ReadFldAlign(CKGEN_DISP_CKCFG, FLD_POCLK_SEL))
        {
            case 0: u4Clock = BSP_GetDomainClock(CAL_SRC_OCLK); break;
            case 1: u4Clock = BSP_GetDomainClock(CAL_SRC_OCLK)/2; break;
            case 2: u4Clock = BSP_GetDomainClock(SRC_POCLK_DPLL); break;
            case 3: u4Clock = BSP_GetDomainClock(SRC_MIB_OCLK); break;
            default: u4Clock = 0; break;
        }
        break;
    case SRC_VDOIN_MPCLK:
        if (IO32ReadFldAlign(OMUX_00, VDOIN_MPCLK_SEL) == 1) //clk192m
        {
            if (IO32ReadFldAlign(OMUX_VDOIN_CLK_SEL, FLD_VDOIN_CLK_SEL_192)== 0 )
            {
                u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2);
            }
            else
            {
                u4Clock = 0 ; //tapll/2
            }
        }
		else if (IO32ReadFldAlign(OMUX_00, VDOIN_MPCLK_SEL) == 0)  //clk180m
		{
		    if (IO32ReadFldAlign(OMUX_VDOIN_CLK_SEL, FLD_VDOIN_CLK_SEL_180)== 0 )
            {
                u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL)/2;
            }
            else
            {
                u4Clock = BSP_GetDomainClock(SRC_ETHPLL_CLK)/2;
            }
		}
        else
        {
            u4Clock = 0 ;
        }
        break;
    case SRC_MIB_OCLK:
        switch (IO32ReadFldAlign(CKGEN_REG_MIB_OCLK_CKCFG, FLD_MIB_OCLK_SEL))
        {
            case 3: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_CK648M); break;
            case 0: u4Clock = BSP_GetDomainClock(CAL_SRC_XTAL); break;
            case 1: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2); break;
            default:
            case 2: u4Clock = BSP_GetDomainClock(CAL_SRC_ETHPLL_D2); break;
        }
        break;
    case SRC_POCLK_DPLL:
        if (IO32ReadFldAlign(CKGEN_VPCLK_CFG, FLD_POCLK_DPLL_SEL))
        {
            u4Clock = (BSP_GetDomainClock(CAL_SRC_ETHPLL_D2) >> 15) * IO32ReadFldAlign(CKGEN_POCLK_DPLL_DIVCFG, FLD_POCLK_DPLL_IDEAL_PLL_M);
        }
        else
        {
            u4Clock = (BSP_GetDomainClock(CAL_SRC_SYSPLL_D2) >> 15) * IO32ReadFldAlign(CKGEN_POCLK_DPLL_DIVCFG, FLD_POCLK_DPLL_IDEAL_PLL_M);
        }
        break;
    case SRC_MJC_CLK:
        switch (IO32ReadFldAlign(CKGEN_REG_MJC_CKCFG, FLD_MJC_SEL))
        {
            case 0: u4Clock = BSP_GetDomainClock(CAL_SRC_XTAL); break;
            default:  u4Clock = 0; break;
        }
        break;
    case SRC_ETHPLL_CLK:
        u4Clock = _CalGetPLLCounter((UINT32)CAL_SRC_ETHPLL_D2);
        u4Clock = u4Clock * 1000 * 2;
        break;
    case SRC_SMI_CLK:
        switch (IO32ReadFldAlign(CKGEN_REG_SMI_CKCFG, FLD_SMI_CK_SEL))
        {
            case 0: u4Clock = BSP_GetDomainClock(CAL_SRC_XTAL); break;
            case 1: u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL); break;
            case 2: u4Clock = BSP_GetDomainClock(SRC_ETHPLL_CLK) * 2 / 3; break;
            case 3: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2); break;
            default:  u4Clock = 0; break;
        }
        break;
    case SRC_SMI_BUS_CLK:
        switch (IO32ReadFldAlign(CKGEN_REG_SMI_BUS_CKCFG, FLD_SMI_BUS_CK_SEL))
        {
            case 0: u4Clock = BSP_GetDomainClock(CAL_SRC_XTAL); break;
            case 1: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2); break;
            case 2: u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL) * 2 / 3; break;
            case 3: u4Clock = BSP_GetDomainClock(CAL_SRC_SAWLESSPLL) / 2; break;
            case 4: u4Clock = BSP_GetDomainClock(CAL_SRC_USBPLL) * 2 / 5; break;
            case 5: u4Clock = BSP_GetDomainClock(CAL_SRC_USBPLL) / 4; break;
            case 6: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2) * 2 / 3; break;
            case 7: u4Clock = BSP_GetDomainClock(CAL_SRC_SYSPLL_D2) / 2; break;
            default:  u4Clock = 0; break;
        }
        break;
    /*
    case CAL_SRC_SAWLESSPLL:
    case CAL_SRC_CPUPLL_D2:
    case CAL_SRC_SYSPLL_D2:
    case CAL_SRC_APLL1:
    case CAL_SRC_APLL1_D3:
    case CAL_SRC_APLL2:
    case CAL_SRC_APLL2_D3:
    case CAL_SRC_ADCPLL:
    case CAL_SRC_M_CK:
    case CAL_SRC_G3DPLL:
    case CAL_SRC_FBOUT:
    case CAL_SRC_USBPLL:
    case CAL_SRC_ETHPLL_D2:
    case CAL_SRC_XTAL:
    case CAL_SRC_ADC_CKTOUT:
    case CAL_SRC_PIXPLL:
    case CAL_SRC_AD_HSYNC_OUT:
    case CAL_SRC_AD_VSYNC_OUT:
    case CAL_SRC_AD_SOGY_OUT:
    case CAL_SRC_AD_FB_OUT:
        u4Clock = _CalGetPLLCounter((UINT32)eSource);
        u4Clock = u4Clock * 1000;
        break;
    */
    default:
		u4Clock = _CalGetPLLCounter((UINT32)eSource);
		u4Clock = u4Clock * 1000;
        break;
    }

    return u4Clock;
}
#if 0
UINT32 BSP_GetBand(CAL_SRC_T ePll)
{
    UINT32 u4Band = 0xff;

    switch(ePll)
    {
        case SRC_SAWLESSPLL_CLK:
            u4Band = IO32ReadFldAlign(REG_IFPLL_CFG1, FLD_RG_IFPLL_BAND);
            break;
        default:
            break;
    }

    return u4Band;
}

UINT32 BSP_SetBand(CAL_SRC_T ePll, UINT32 u4CalEn, UINT32 u4Band)
{
    switch(ePll)
    {
        case SRC_SAWLESSPLL_CLK:
            vIO32WriteFldAlign(REG_IFPLL_CFG1, u4Band, FLD_RG_IFPLL_BAND);
            break;
        default:
            break;
    }
    return TRUE;
}
#endif
UINT32 BSP_SetVConDebugOut(CAL_SRC_T ePll)
{
    return 0;
}

