/* 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: dtvbm11 $
 * $Date: 2015/07/28 $
 * $RCSfile: eeprom_hw.c,v $
 * $Revision: #1 $
 *
 *---------------------------------------------------------------------------*/

/** @file eeprom_if.c
 *  This file is the implementation of EEPROM interface definition.
 */

//---------------------------------------------------------------------------
// Include files
//---------------------------------------------------------------------------
#include "x_lint.h"

LINT_EXT_HEADER_BEGIN
#include "drvcust_if.h"
#include "eeprom_if.h"
#include "nor_debug.h"
#include "x_hal_5381.h"
#include "x_bim.h"
#include "x_pinmux.h"
#include "x_pdwnc.h"
#include "x_timer.h"
#ifdef CC_NAND_ENABLE
#include "nand_if.h"
#endif

#ifdef CC_MSDC_ENABLE
#include "msdc_if.h"
#endif


#ifdef REPLACE_EEPROM_WITH_NOR
#include "nor_if.h"
#endif
LINT_EXT_HEADER_END
//---------------------------------------------------------------------------
// Configurations
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Constant definitions
//---------------------------------------------------------------------------
/// set EEPROM word size.
#define EEP_SIF_WORD_SIZE               (256)
/// EEPROM page size mask.
#define EEP_SIF_PAGE_MASK               (0xFF)
/// EEPROM MSB page size mask. This can support up to 64 kbits eeprom.
#define EEP_SIF_MSB_PAGE_MASK           (0xFF)

/// Device address LSB [b3, b2, b1] are from Data Offset MSB [A10,A9,A8].
#define EEP_GET_UPPER_ADDR(offset)      ((UINT8)(((offset) >> 7) & 0x0E))

/// Use OSAI function option
#if defined (CC_MTK_LOADER)
#define USE_OSAI            0
#else
#define USE_OSAI            1
#endif


//---------------------------------------------------------------------------
// Type definitions
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Macro definitions
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Imported variables
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Imported functions
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Static function forward declarations
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Static variables
//---------------------------------------------------------------------------

/// ST M24C32 EEPROM decvice address.
static UINT8 _u1EEPROMDevBus  = 0;
static UINT8 _u1EEPROMDevAddr = 0xA0;

/// EEPROM decvice size (bytes).
static UINT32 _u4EEPROMSize = 0;

/// EEPROM decvice clock divider of 27 Mhz.
static UINT16 _u2EEPROMClkDiv = 0x200;

/// EEPROM Page Write size
static UINT8 _u1EEPROMPage = 8;

//EEPROM page write reset time
static UINT32 _u4EEPROMRstTime = 20;

//EEPROM enable retry 
static BOOL _fgEEPROMEnableRetry = FALSE;

static UINT32 _u4EEPROMSpeedInit = 0;
static UINT32 _u4EEPROMSizeInit = 0;
static UINT32 _u4EEPROMPageInit = 0;

/// Use OSAI function option
#ifndef CC_MTK_LOADER
static HANDLE_T _hUserSema     = NULL_HANDLE;
#endif

#ifdef CLI_SUPPORT


#define EEP_SUPPORT_TOTAL  5
static EEP_HISTORY eEP_HISTORY[EEP_SUPPORT_TOTAL];
#endif



//---------------------------------------------------------------------------
// Inter-file functions
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
// Public functions
//---------------------------------------------------------------------------

//-------------------------------------------------------------------------
/** EEPROM_SetSpeed
 *  Set EEPROM device I2C clk rate divider.
 *  @param I2C clk rate divider.
 *  @return  VOID
 */
//-------------------------------------------------------------------------
#ifdef CLI_SUPPORT


VOID EEPROM_OpertionCount(VOID)
{
	INT32 i4Devnum,byte,max_write_byte=0,index=0;
	
	for(i4Devnum=0;i4Devnum<EEP_SUPPORT_TOTAL&& eEP_HISTORY[i4Devnum]._i4Devnum!=0;i4Devnum++)
		{
		for(byte =0;byte<EEPROM_ARRAYINDEX;byte++)
			{
				if(eEP_HISTORY[i4Devnum]._i4writebytescount[byte] >max_write_byte)
					{
						max_write_byte=eEP_HISTORY[i4Devnum]._i4writebytescount[byte];
						index = byte;
					}
				Printf("u%dDev=0x%X, byte=0x%X, writebytescount=%d.\n",i4Devnum,
               		eEP_HISTORY[i4Devnum]._i4Devnum,
               		byte,
               		eEP_HISTORY[i4Devnum]._i4writebytescount[byte]
               		);
			}

		Printf("u%dDev=0x%X, max_write_byte=0x%X, writebytescount=%d.\n",i4Devnum,
               		eEP_HISTORY[i4Devnum]._i4Devnum,
               		max_write_byte,
               		eEP_HISTORY[i4Devnum]._i4writebytescount[index]
               		);
		
		}
		
}
#endif

VOID EEPROM_SetClkDiv(UINT16 u2Speed)
{
    if (u2Speed < 100)
    {
        LOG(1, "%s(%d) speed too fast.\n", __FILE__, __LINE__);
        return;
    }

    _u2EEPROMClkDiv = u2Speed;
}

//-------------------------------------------------------------------------
/** EEPROM_InitClkDiv
 *  get EEPROM device I2C clk rate divider.
 *  @param  VOID
 *  @return  VOID
 */
//-------------------------------------------------------------------------
VOID EEPROM_InitClkDiv(VOID)
{
    if (!_u4EEPROMSpeedInit)
    {
        _u2EEPROMClkDiv = (UINT16)DRVCUST_InitGet(eSystemEepromClkDiv);
        _u4EEPROMSpeedInit = 1;
    }
}

//-------------------------------------------------------------------------
/** EEPROM_Init
 *  Do necessary EEPROM initialization
 *  @param  VOID
 *  @retval   0        SUCCESS.
 *  @retval   <0    Error
 */
//-------------------------------------------------------------------------
INT16 EEPROM_Init(VOID)// only called in eeprom_moudle.c -> mtk_mod.c -> mtk_init()
{
    static BOOL fgEEPROM_Init=FALSE;
    UINT32 u4Var;
#ifndef CC_MTK_LOADER
    INT32 i4Ret;
#endif
    
    if (!fgEEPROM_Init )
    {
#if USE_OSAI
        // create semaphore
        VERIFY(x_sema_create(&_hUserSema, X_SEMA_TYPE_BINARY, X_SEMA_STATE_UNLOCK) == OSR_OK);
#endif
		EEP_SEMA_Init();

#if defined(REPLACE_EEPROM_WITH_FLASH) || defined(REPLACE_EEPROM_WITH_EMMC) || defined(REPLACE_EEPROM_WITH_NOR)
        //no need to sif init
#else
        SIF_Init();
#endif
        EEPROM_InitClkDiv();

#if defined(CC_MTK_LOADER)
        u4Var = DRVCUST_InitGet(eSystemEepromBus);
        _u1EEPROMDevBus = u4Var;

        u4Var = DRVCUST_InitGet(eSystemEepromAddress);
        _u1EEPROMDevAddr = u4Var;

        u4Var = DRVCUST_InitGet(eSystemEepromPageSize);
        _u1EEPROMPage = u4Var;

        u4Var = DRVCUST_InitGet(eSysEepromResetTime);
        _u4EEPROMRstTime = u4Var;

        u4Var = DRVCUST_InitGet(eSysEepromEnableRetry);
        _fgEEPROMEnableRetry = u4Var;
        
#else
        i4Ret = DRVCUST_OptQuery(eSystemEepromBus, &u4Var);
        if (i4Ret == 0)
        {
            _u1EEPROMDevBus = (UINT8) u4Var;
        }

        i4Ret = DRVCUST_OptQuery(eSystemEepromAddress, &u4Var);
        if (i4Ret == 0)
        {
            _u1EEPROMDevAddr = (UINT8) u4Var;
        }

        i4Ret = DRVCUST_OptQuery(eSystemEepromPageSize, &u4Var);
        if (i4Ret == 0)
        {
            _u1EEPROMPage = (UINT8) u4Var;
        }

        i4Ret = DRVCUST_OptQuery(eSysEepromResetTime, &u4Var);
        if (i4Ret == 0)
        {
            _u4EEPROMRstTime = (UINT8) u4Var;
        }

        i4Ret = DRVCUST_OptQuery(eSysEepromEnableRetry, &u4Var);
        if (i4Ret == 0)
        {
            _fgEEPROMEnableRetry = (UINT8) u4Var;
        }
#endif
        
        fgEEPROM_Init = TRUE;
    }
    return 0;
}


//-------------------------------------------------------------------------
/** EEPROM_SetSize
 *  Set EEPROM device size.
 *  @param device size in bytes
 *  @return  VOID.
 */
//-------------------------------------------------------------------------
VOID EEPROM_SetSize(UINT32 u4Size)
{
    _u4EEPROMSize = u4Size;
}


//-------------------------------------------------------------------------
/** EEPROM_GetSize
 *  get EEPROM device size.
 *  @param  VOID
 *  @return  device size in bytes.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_GetSize(VOID)
{
    if (!_u4EEPROMSizeInit)
    {
        _u4EEPROMSize = DRVCUST_InitGet(eSystemEepromSize);
        _u4EEPROMSizeInit = 1;
    }

    return (INT32)_u4EEPROMSize;
}


//-------------------------------------------------------------------------
/** EEPROM_GetPage
 *  get EEPROM page write size.
 *  @param  VOID
 *  @return  device size in bytes.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_GetPage(VOID)
{
    if (!_u4EEPROMPageInit)
    {
        _u1EEPROMPage = DRVCUST_InitGet(eSystemEepromPageSize);
        _u4EEPROMPageInit = 1;
    }

    return (INT32)_u1EEPROMPage;
}


//-------------------------------------------------------------------------
/** EEPROM_SetPage
 *  Set EEPROM page write size.
 *  @param device size in bytes
 *  @return  VOID.
 */
//-------------------------------------------------------------------------
VOID EEPROM_SetPage(UINT32 u4Size)
{
    _u1EEPROMPage = u4Size;
}


//---------------------------------------------------------------------------
// Static functions
//---------------------------------------------------------------------------
//#ifndef REPLACE_EEPROM_WITH_FLASH
#if !defined (REPLACE_EEPROM_WITH_FLASH) && !defined(REPLACE_EEPROM_WITH_NOR) && !defined(REPLACE_EEPROM_WITH_EMMC)
static void _Delay(UINT32 u4Delay)
{
#if defined (CC_MTK_LOADER)
    HAL_Delay_us(u4Delay * 1000);
#else
    HAL_TIME_T rT0={0,0}, rT1={0,0}, rTDelta={0,0};
    HAL_GetTime(&rT0);
    do
    {
        x_thread_delay(5);
        HAL_GetTime(&rT1);    
        HAL_GetDeltaTime(&rTDelta, &rT0, &rT1);
    } while((rTDelta.u4Seconds * 1000000 + rTDelta.u4Micros) < u4Delay * 1000);
#endif
}

static VOID _EEPROM_SemaLock(VOID)
{
#if USE_OSAI
    // wait semaphore
    VERIFY(x_sema_lock(_hUserSema, X_SEMA_OPTION_WAIT) == OSR_OK);
#endif 
}


static VOID _EEPROM_SemaUnlock(VOID)
{
#if USE_OSAI
    // release semaphore
    VERIFY(x_sema_unlock(_hUserSema) == OSR_OK);
#endif
}

//-------------------------------------------------------------------------
/** _EEPROM_X_Read
 *  Generic EEPROM read function.
 *  @param  u8Offset     begin address to read.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     read data length in bytes.
 *  @param  u1Bus       SIF bus ID
 *  @param  u2ClkDiv    EEPROM clock divider
 *  @param  u1Addr      EEPROM SIF device address
 *  @param  u4Size      EEPROM Size
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
static INT32 _EEPROM_X_Read(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen,
                            UINT8 u1Bus, UINT16 u2ClkDiv, UINT8 u1Addr, UINT32 u4Size)
{
    UINT32 u4Offset = (UINT32)u8Offset;

    UINT32 u4Index;
    UINT8 u1WordAddr;
    UINT16 u2ByteCnt;
    UINT16 u2Ret;


    // two bytes for two word address.
    UINT8 u1Data[2];
    UINT8 u1DevAddr;
    LOG(7, "eeprom para:%x(%x) u4MemLen:0x%08x\n", u4Offset, u4MemPtr, u4MemLen);

    if (u4MemLen == 0)
    {
        LOG(1, "%s(%d) u4MemLen:0x%08x\n", __FILE__, __LINE__, u4MemLen);
        return 1;
    }

    if ((u4Offset + u4MemLen) > u4Size)
    {
        LOG(1, "%s(%d) u4Offset+u4MemLen:0x%08x\n", __FILE__, __LINE__,
        u4Offset + u4MemLen);
        return 1;
    }


    SIF_Init();

    _EEPROM_SemaLock();
    
    u4Index = 0;

    while (u4Index < u4MemLen)
    {
        if (u4Size <= 0x800)
        {
            // EEPROM word address in 0~255.
            u1WordAddr = u4Offset & EEP_SIF_PAGE_MASK;

            // residual data length in page
            u2ByteCnt = EEP_SIF_WORD_SIZE - u1WordAddr;
            // get min value between user's wanted length and residual data in page.
            u2ByteCnt = (UINT16)MIN((u4MemLen-u4Index), u2ByteCnt);

            // Device address LSB [b3, b2, b1] are from Data Offset MSB [A10,A9,A8].
            u1DevAddr = EEP_GET_UPPER_ADDR(u4Offset);

            // upper device address [b7 ~ b4] is fixed.
            u1DevAddr |= u1Addr;

            u2Ret = SIF_X_Read(u1Bus, u2ClkDiv, u1DevAddr, 1,
                               u1WordAddr, (UINT8 *)u4MemPtr, u2ByteCnt);

            if (u2Ret != u2ByteCnt)
            {
                LOG(3, "[EEPROM] Read WARN 0x%x at 0x%x: only %x expected %x\n", u1DevAddr, u4Offset, u2Ret, u2ByteCnt);
                _EEPROM_SemaUnlock();
                return 1;
            }
            // u2Ret update the actual read bytes.
            u2ByteCnt = u2Ret;
        }
        else
        {
            // EEPROM word address2 (MSB) in 0~32.
            u1Data[0] = (u4Offset >> 8) & EEP_SIF_MSB_PAGE_MASK;

            // EEPROM word address1 (LSB) in 0~255.
            u1Data[1] = (UINT8)(u4Offset & EEP_SIF_PAGE_MASK);

            u2ByteCnt = u4MemLen;

            u2Ret = SIF_X_Write(u1Bus, u2ClkDiv, u1Addr, 0, 0, u1Data, 2);

            // because write protected, this dummy write must be fail.
            if (u2Ret != 2)
            {
                _EEPROM_SemaUnlock();
                return 1;
            }
            // sequential read.
            u2Ret = SIF_X_Read(u1Bus, u2ClkDiv, u1Addr, 0,0,(UINT8 *)u4MemPtr, u2ByteCnt);

            if (u2Ret != u2ByteCnt)
            {
                LOG(3, "[EEPROM] Read WARN 0x%x at 0x%x: only %x expected %x\n", u1Addr, u4Offset, u2Ret, u2ByteCnt);                
                _EEPROM_SemaUnlock();
                return 1;
            }
        }

        u4Index += u2ByteCnt;
        u4Offset += u2ByteCnt;
        u4MemPtr += u2ByteCnt;
    }
	 _EEPROM_SemaUnlock();

    return 0;
}

//-------------------------------------------------------------------------
/** _EEPROM_X_Write
 *  Generic EEPROM write function.
 *  @param  u8Offset     begin address to write.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     writedata length in bytes.
 *  @param  u1Bus       SIF bus ID
 *  @param  u2ClkDiv    EEPROM clock divider
 *  @param  u1Addr      EEPROM SIF device address
 *  @param  u4Size      EEPROM Size
 *  @param  i4SysWP     EEPROM write protect GPIO pin number
 *  @param  i4SysWPEnable EEPROM write protect enable polarity
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
static INT32 _EEPROM_X_Write(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen,
                             UINT8 u1Bus, UINT16 u2ClkDiv, UINT8 u1Addr, UINT32 u4Size,
                             INT32 i4SysWP, INT32 i4SysWPEnable)
{
    UINT32 u4Offset = (UINT32)u8Offset;
	#ifdef CLI_SUPPORT
	UINT32 Eepromoffset = u4Offset;
	#endif
    UINT32 u4Index;

    UINT16 u2ByteCnt;
    UINT16 u2Ret;
    INT32 fgSet;
    INT32 fgOldSet=-1;
    UINT32 u4Retry=0, u4Flag=0;
    UINT8 u1Data[34];
    UINT32 i;
    UINT8 *pData = (UINT8 *)u4MemPtr;
    UINT8 u1DevAddr;
    UINT8 u1WordAddr;

    if (u4MemLen == 0)
    {
        LOG(1, "%s(%d) u4MemLen:0x%08x\n", __FILE__, __LINE__, u4MemLen);
        return 1;
    }

    if ((u4Offset + u4MemLen) > u4Size)
    {
        LOG(1, "%s(%d) u4Offset+u4MemLen:0x%08x\n", __FILE__, __LINE__,
        u4Offset + u4MemLen);
        return 1;
    }

    SIF_Init();

    _EEPROM_SemaLock();
    
    // disable write protect by GPIO.
    if ((i4SysWP >= 0) && (i4SysWPEnable >= 0))
    {
        u4Flag = 1;
#if !defined(CC_MT5365)
        VERIFY(BSP_PinGpioSel(i4SysWP) == 0);
#endif

        // query old WP status. If WP = 0, it may be in factory mode.
        fgOldSet = GPIO_Output(i4SysWP, NULL);

        if (fgOldSet == i4SysWPEnable)
        {
            fgSet = !i4SysWPEnable;
            VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
        }
    }

    u4Index = 0;

    while (u4Index < u4MemLen)
    {
        if (u4Size <= 0x800)
        {
            // EEPROM word address in 0~255.
            u1WordAddr = u4Offset & EEP_SIF_PAGE_MASK;

            // EEPROM should align the boundary length of 8 bytes when do page write.
            u2ByteCnt = _u1EEPROMPage - (u1WordAddr %_u1EEPROMPage);

            // Max EEPROM R/W length in one time = SIF hardware data port width.
            // Get min value between user's wanted length and EEPROM R/W unit.
            u2ByteCnt = (UINT16)MIN((u4MemLen-u4Index), u2ByteCnt);

            // Device address LSB [b3, b2, b1] are from Data Offset MSB [A10,A9,A8].
            u1DevAddr = EEP_GET_UPPER_ADDR(u4Offset);

            // upper device address [b7 ~ b4] is fixed.
            u1DevAddr |= u1Addr;
            
            u2Ret = SIF_X_Write(u1Bus, u2ClkDiv, u1DevAddr, 1,
                                u1WordAddr, (UINT8 *)u4MemPtr, u2ByteCnt);
			
            // handle EEPROM write error.
            if (u2Ret != u2ByteCnt)
            {
                u4Retry++;
                LOG(3, "[EEPROM] Write WARN 0x%x at 0x%x: only %x expected %x\n", u1DevAddr, u4Offset, u2Ret, u2ByteCnt);                

                // This is redundency disable write protection pin,
                // but it is useful when ARM or 8032 have bad operation on this pin
                if ((i4SysWP >= 0) && (i4SysWPEnable >= 0))
                {
#if !defined(CC_MT5365)          
                    VERIFY(BSP_PinGpioSel(i4SysWP) == 0);
#endif                
                    fgSet = !i4SysWPEnable;
                    VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
                }
                
                if (u4Retry > 10)
                {
                    if ((u4Flag) && (fgOldSet == i4SysWPEnable))
                    {
                        fgSet = i4SysWPEnable;
                        VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
                    }
                    _EEPROM_SemaUnlock();
                    return 1;
                }
            }
            else
            {
                u4Retry = 0;
            }
        }
        else
        {
            // EEPROM word address2 (MSB) in 0~32.
            u1Data[0] = (u4Offset >> 8) & EEP_SIF_MSB_PAGE_MASK;

            // EEPROM word address1 (LSB) in 0~255.
            u1Data[1] = u4Offset & EEP_SIF_PAGE_MASK;

            // EEPROM should align the boundary length of 8 bytes when do page write.
            u2ByteCnt = _u1EEPROMPage - (u1Data[1] %_u1EEPROMPage);

            // Max EEPROM R/W length in one time = SIF hardware data port width.
            // Get min value between user's wanted length and EEPROM R/W unit.
            u2ByteCnt = (UINT16)MIN((u4MemLen-u4Index), u2ByteCnt);
			
			if(u2ByteCnt <= 32)
			{
	            // copy user's data.
	            for (i = 0; i < u2ByteCnt; i++)
	            {
	                u1Data[i + 2] = *(pData + u4Index + i);
	            }
			}
			else
			{
                 Printf("[EEPROM] _u1EEPROMPage is too large, more than 32\n");                
				 return 1;
			}

            u2Ret = SIF_X_Write(u1Bus, u2ClkDiv, u1Addr, 0, 0, u1Data, (UINT16) (u2ByteCnt + 2));

            // handle EEPROM write error.
            if (u2Ret != ((UINT16) (u2ByteCnt + 2)))
            {
                u4Retry++;
                LOG(3, "[EEPROM] Write WARN 0x%x at 0x%x: only %x expected %x\n", u1Addr, u4Offset, u2Ret, u2ByteCnt + 2);                               

                // This is redundency disable write protection pin,
                // but it is useful when ARM or 8032 have bad operation on this pin
                if ((i4SysWP >= 0) && (i4SysWPEnable >= 0))
                {
#if !defined(CC_MT5365)         
                    VERIFY(BSP_PinGpioSel(i4SysWP) == 0);
#endif
                    fgSet = !i4SysWPEnable;
                    VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
                }     
                
                if (u4Retry > 10)
                {
                    if ((u4Flag) && (fgOldSet == i4SysWPEnable))
                    {
                        fgSet = i4SysWPEnable;
                        VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
                    }
                    _EEPROM_SemaUnlock();
                    return 1;
                }
                u2Ret = 0;
            }
            else
            {
                u4Retry = 0;
                // exclude 2 bytes of word address.
                u2Ret -= 2;
            }
        }

        // During internal write cycle, EEPROM disconnects itself from bus,
        // and writes a copy of the data from its internal latches  to the
        // memory cells.
        // Max write time (tw) is 10ms.
        // If SIF_Write get the NACK from slave, it will return the number of bytes
        // that SIF already write. User can continue to write the following data.
        // We use experimental value 20 ms.
        _Delay(_u4EEPROMRstTime);

        // u2Ret update the actual read bytes.
        u2ByteCnt = u2Ret;

        u4Index += u2ByteCnt;
        u4Offset += u2ByteCnt;
        u4MemPtr += u2ByteCnt;
    }

    // write protect by GPIO.
    if ((u4Flag) && (fgOldSet == i4SysWPEnable))
    {
        fgSet = i4SysWPEnable;
        VERIFY(GPIO_Output(i4SysWP, &fgSet) == fgSet);
    }
	#ifdef CLI_SUPPORT
	for(i=0;i<EEP_SUPPORT_TOTAL;i++)
		{
			if(eEP_HISTORY[i]._i4Devnum ==u1Addr)
				{
					UINT32 byte;
					for(byte=Eepromoffset ; byte<=Eepromoffset+u4MemLen;byte++)
					eEP_HISTORY[i]._i4writebytescount[byte]++;
					break;
				}
			if(!eEP_HISTORY[i]._i4Devnum)
				{
					UINT32 byte;
					eEP_HISTORY[i]._i4Devnum = u1Addr;
					for(byte=Eepromoffset ; byte<=Eepromoffset+u4MemLen;byte++)
					eEP_HISTORY[i]._i4writebytescount[byte]++;
					break;
				}

		}
	#endif
	_EEPROM_SemaUnlock();


    
    return 0;
}
#endif

//-------------------------------------------------------------------------
/** EEPROM_Read
 *  SYSTEM EEPROM read function.
 *  @param  u8Offset     begin address to read.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     read data length in bytes.
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_Read(UINT64 u8Offset, unsigned long u4MemPtr, UINT32 u4MemLen)
{    
#ifdef REPLACE_EEPROM_WITH_NOR
	UNUSED(_u1EEPROMDevBus);
	UNUSED(_u1EEPROMDevAddr);
	UNUSED(_u2EEPROMClkDiv);
	UNUSED(_fgEEPROMEnableRetry);
	UNUSED(_u4EEPROMRstTime);

	return NOR_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#else

#ifdef REPLACE_EEPROM_WITH_FLASH

    UNUSED(_u1EEPROMDevBus);
    UNUSED(_u1EEPROMDevAddr);
    UNUSED(_u2EEPROMClkDiv);
    UNUSED(_fgEEPROMEnableRetry);
    UNUSED(_u4EEPROMRstTime);

#ifdef CC_UBOOT
    extern INT32 UBOOT_EEPROM_Read(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen);
    return UBOOT_EEPROM_Read(DRVCUST_InitGet(eEepromDataMemOffset) + u8Offset, u4MemPtr, u4MemLen);
#else
    return NAND_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#endif

#else
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
#ifdef CC_EEP2EMMC_DUAL_BANK_SUPPORT
    Printf("[ZJ UbootDual] MSDC_EEPROM_Read API\n");
    extern INT32 UBOOT_MSDC_EEPROM_Read_Dual_Bank(UINT32 u4Offset, UINT32 u4MemPtr, UINT32 u4MemLen);
    return UBOOT_MSDC_EEPROM_Read_Dual_Bank(u8Offset, u4MemPtr, u4MemLen);
#else
    Printf("[ZJ Uboot] MSDC_EEPROM_Read API\n");
    extern int emmc_read_msdc(UINT64 offset, VOID* addr, UINT32 cnt);
    UINT64 eepromA = DRVCUST_InitGet64(eNANDFlashPartIdEepromA);
    UINT64 partion = DRVCUST_InitGet64((QUERY_TYPE_T)(eNANDFlashPartOffset0 + eepromA));
    return emmc_read_msdc(partion + u8Offset, (VOID *)u4MemPtr, u4MemLen);
#endif
#else
    return MSDC_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#endif

#else

    INT32 i4Ret;
    INT32 i;

    if(_fgEEPROMEnableRetry)
    {
        for (i=0; i<10; i++)
        {
            i4Ret = _EEPROM_X_Read(u8Offset, u4MemPtr, u4MemLen,
                          _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize());
            if (i4Ret == 0)
            {
                return i4Ret;
            }
            else
            {
                Printf("EEPROM_Read()  %d\n",i);
            }
            _Delay(_u4EEPROMRstTime);
        }
        Printf("EEPROM_Read() failed\n");
        
        return 1;
    }
    else 
        return _EEPROM_X_Read(u8Offset, u4MemPtr, u4MemLen,
                          _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize());
#endif
#endif
#endif/*REPLACE_EEPROM_WITH_NOR*/
}


//-------------------------------------------------------------------------
/** EEPROM_Write
 *  SYSTEM EEPROM write function.
 *  @param  u8Offset     begin address to write.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     writedata length in bytes.
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_Write(UINT64 u8Offset, unsigned long u4MemPtr, UINT32 u4MemLen)
{
#ifdef REPLACE_EEPROM_WITH_NOR
	return NOR_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#else

#ifdef REPLACE_EEPROM_WITH_FLASH

#ifdef CC_UBOOT
    return -1;
#else
    return NAND_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#endif

#else
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
#ifdef CC_EEP2EMMC_DUAL_BANK_SUPPORT
    Printf("[ZJ UbootDual] MSDC_EEPROM_Read API\n");
    extern INT32 UBOOT_MSDC_EEPROM_Write_Dual_Bank(UINT32 u4Offset, UINT32 u4MemPtr, UINT32 u4MemLen);
    return UBOOT_MSDC_EEPROM_Write_Dual_Bank(u8Offset, u4MemPtr, u4MemLen);
#else
    Printf("[ZJ Uboot] MSDC_EEPROM_Read API\n");
    extern int emmc_write_msdc(UINT64 offset, VOID* addr, UINT32 cnt);
    UINT64 eepromA = DRVCUST_InitGet64(eNANDFlashPartIdEepromA);
    UINT64 partion = DRVCUST_InitGet64((QUERY_TYPE_T)(eNANDFlashPartOffset0 + eepromA));
    return emmc_write_msdc(partion + u8Offset,(VOID *)u4MemPtr,u4MemLen);
#endif
#else
    return MSDC_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#endif
#else

    UINT32 u4SysEEPROMWPGpio, u4SysEEPROMWPEnablePolarity;
    INT32 i4Ret;
    INT32 i;

    if (0 == DRVCUST_InitQuery(eSysEepromWPGpio, &u4SysEEPROMWPGpio) && 
       (0 == DRVCUST_InitQuery(eSysEepromWPEnablePolarity, &u4SysEEPROMWPEnablePolarity)))
    {
        if(_fgEEPROMEnableRetry)
        {
            for (i=0; i<10; i++)
            {
                i4Ret = _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                                   _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize(),
                                   (INT32)u4SysEEPROMWPGpio, (INT32)u4SysEEPROMWPEnablePolarity);
                if (i4Ret == 0)
                {
                    return i4Ret;
                }
                else
                {
                    Printf("EEPROM_Write()  %d\n",i);
                }
                _Delay(_u4EEPROMRstTime);
            }
            Printf("EEPROM_Write() failed\n");
            
            return 1;
        }
        else 
            return _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                                   _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize(),
                                   (INT32)u4SysEEPROMWPGpio, (INT32)u4SysEEPROMWPEnablePolarity);
    }
    else
    {
        if(_fgEEPROMEnableRetry)
        {
            for (i=0; i<10; i++)
            {
                i4Ret = _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                                   _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize(),
                                   -1, -1);
                if (i4Ret == 0)
                {
                    return i4Ret;
                }
                else
                {
                    Printf("EEPROM_Write()  %d\n",i);
                }
                _Delay(_u4EEPROMRstTime);
            }
            Printf("EEPROM_Write() failed\n");
            
            return 1;
        }
        else 
            return _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                                   _u1EEPROMDevBus, _u2EEPROMClkDiv, _u1EEPROMDevAddr, (UINT32) EEPROM_GetSize(),
                                   -1, -1);
        
    }
#endif
#endif
#endif/*REPLACE_EEPROM_WITH_NOR*/
}

#ifdef CC_EEP2EMMC_DUAL_BANK_SUPPORT
INT32 EEPROM_Header_Read(BOOL bMode, UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen)
{
    UINT32 u4Offset = 0;
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
	return -1;
#else
    u4Offset = (UINT32)(u8Offset&0x00000000FFFFFFFF);
	//Printf("EEPROM_Header_Read(0x%2x)\n",u4Offset);
    if(bMode) {
	return MSDC_EEP_Header_Read(u4Offset, u4MemPtr, u4MemLen);
    } else {
    return MSDC_EEP_Init_Header_Read(u4Offset, u4MemPtr, u4MemLen);
    }
#endif
#endif
}

INT32 EEPROM_Header_Write(BOOL bMode, UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen)
{
    UINT32 u4Offset = 0;
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
	return -1;
#else
    u4Offset = (UINT32)(u8Offset&0x00000000FFFFFFFF);
	//Printf("EEPROM_Header_Write(0x%2x)\n",u4Offset);
    if(bMode) {
	return MSDC_EEP_Header_Write(u4Offset, u4MemPtr, u4MemLen);
    } else {
    return MSDC_EEP_Init_Header_Write(u4Offset, u4MemPtr, u4MemLen);
    }
#endif
#endif
}
INT32 EEPROM_Init_Data_Read(BOOL bBankInfo, UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen)
{
    UINT32 u4Offset = 0;
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
	return -1;
#else
    u4Offset = (UINT32)(u8Offset&0x00000000FFFFFFFF);
    //Printf("EEPROM_Init_Data_Read(0x%2x)\n",u4Offset);
    return MSDC_EEP_Init_Data_Read(bBankInfo, u4Offset, u4MemPtr, u4MemLen);
#endif
#endif

}
INT32 EEPROM_Init_Data_Write(BOOL bBankInfo, UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen)
{
    UINT32 u4Offset = 0;
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
	return -1;
#else
    u4Offset = (UINT32)(u8Offset&0x00000000FFFFFFFF);
    //Printf("EEPROM_Init_Data_Write(0x%2x)\n",u4Offset);
	return MSDC_EEP_Init_Data_Write(bBankInfo, u4Offset, u4MemPtr, u4MemLen);
#endif
#endif
}
#endif

//-------------------------------------------------------------------------
/** EEPROM_X_Read
 *  Generic EEPROM read function.
 *  @param  u8Offset     begin address to read.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     read data length in bytes.
 *  @param  u1Bus       SIF bus ID
 *  @param  u2ClkDiv    EEPROM clock divider
 *  @param  u1Addr      EEPROM SIF device address
 *  @param  u4Size      EEPROM Size
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_X_Read(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen,
                    UINT8 u1Bus, UINT16 u2ClkDiv, UINT8 u1Addr, UINT32 u4Size)
{
#ifdef REPLACE_EEPROM_WITH_NOR
	return NOR_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#else

#ifdef REPLACE_EEPROM_WITH_FLASH

#ifdef CC_UBOOT
    extern INT32 UBOOT_EEPROM_Read(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen);
    return UBOOT_EEPROM_Read(DRVCUST_InitGet(eEepromDataMemOffset) + u8Offset, u4MemPtr, u4MemLen);
#else
    return NAND_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#endif

#else
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
    extern INT32 UBOOT_EEPROM_Read(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen);
    return UBOOT_EEPROM_Read(DRVCUST_InitGet(eEepromDataMemOffset) + u8Offset, u4MemPtr, u4MemLen);
#else
    return MSDC_EEPROM_Read(u8Offset, u4MemPtr, u4MemLen);
#endif

#else

    INT32 i4Ret;
    INT32 i;

    if(_fgEEPROMEnableRetry)
    {
        for (i=0; i<10; i++)
        {
            i4Ret = _EEPROM_X_Read(u8Offset, u4MemPtr, u4MemLen, u1Bus, u2ClkDiv, u1Addr, u4Size);
            if (i4Ret == 0)
            {
                return i4Ret;
            }
            else
            {
                Printf("_EEPROM_X_Read()  %d\n",i);
            }
            _Delay(_u4EEPROMRstTime);
        }
        Printf("_EEPROM_X_Read() failed\n");

        return 1;
    }
    else 
        return _EEPROM_X_Read(u8Offset, u4MemPtr, u4MemLen, u1Bus, u2ClkDiv, u1Addr, u4Size);
#endif   
#endif
#endif/*REPLACE_EEPROM_WITH_NOR*/
}


//-------------------------------------------------------------------------
/** EEPROM_X_Write
 *  Generic EEPROM write function.
 *  @param  u8Offset     begin address to write.
 *  @param  u4MemPtr     user's buffer address.
 *  @param  u4MemLen     writedata length in bytes.
 *  @param  u1Bus       SIF bus ID
 *  @param  u2ClkDiv    EEPROM clock divider
 *  @param  u1Addr      EEPROM SIF device address
 *  @param  u4Size      EEPROM Size
 *  @param  i4SysWP     EEPROM write protect GPIO pin number
 *  @param  i4SysWPEnable EEPROM write protect enable polarity
 *  @retval   0        SUCCESS.
 *  @retval   1        FAIL.
 */
//-------------------------------------------------------------------------
INT32 EEPROM_X_Write(UINT64 u8Offset, UINT32 u4MemPtr, UINT32 u4MemLen,
                     UINT8 u1Bus, UINT16 u2ClkDiv, UINT8 u1Addr, UINT32 u4Size, INT32 i4SysWP, INT32 i4SysWPEnable)
{
#ifdef REPLACE_EEPROM_WITH_NOR
		return NOR_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#else

#ifdef REPLACE_EEPROM_WITH_FLASH

#ifdef CC_UBOOT
    return -1;
#else
    return NAND_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#endif

#else
#ifdef REPLACE_EEPROM_WITH_EMMC
#ifdef CC_UBOOT
    return -1;
#else
    return MSDC_EEPROM_Write(u8Offset, u4MemPtr, u4MemLen);
#endif
#else

    INT32 i4Ret;
    INT32 i;

    if(_fgEEPROMEnableRetry)
    {
        for (i=0; i<10; i++)
        {
            i4Ret = _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                             u1Bus, u2ClkDiv, u1Addr, u4Size, i4SysWP, i4SysWPEnable);
            if (i4Ret == 0)
            {
                return i4Ret;
            }
            else
            {
                Printf("_EEPROM_X_Write()  %d\n",i);
            }
            _Delay(_u4EEPROMRstTime);
        }
        Printf("_EEPROM_X_Write() failed\n");
        
        return 1;
    }
    else 
        return _EEPROM_X_Write(u8Offset, u4MemPtr, u4MemLen,
                             u1Bus, u2ClkDiv, u1Addr, u4Size, i4SysWP, i4SysWPEnable);
#endif
#endif
#endif/*REPLACE_EEPROM_WITH_NOR*/
}
