/*------------------------------------------------------------------------------

        code for i2c gpio emulation api v2.5

        file name       : i2c_gpio_emul_api.c
        function        : hw & os independent code for i2c gpio emulation api
------------------------------------------------------------------------------*/
#include <I2CEMUL_TYPE.h>
#include <I2CEMUL_MDD.h>
#include <I2CEMUL_PDD.h>
#include <bsp_args.h>
#include <oal_i2cemul.h>

//===== DEBUGGING MACRO DEFINITION =============================================================================
//#define TEST_MODE
//==============================================================================================================

//===== MACRO DEFINITION ======================================================================================
#define I2CEMUL_VERSION         31
#define TRY_COUNT_TO_WAIT       150
//==============================================================================================================

//===== GLOBAL VARIABLE DECLARTION =============================================================================
BOOL bSlaveReady;
//==============================================================================================================

//===== LOCAL FUNCTION DECLARTION ==============================================================================
VOID I2C_Start(BYTE btGroup);
VOID I2C_Stop(BYTE btGroup);
VOID I2C_WriteByte(BYTE btGroup, BYTE btData);
BYTE I2C_ReadByte(BYTE btGroup);
static VOID I2C_WriteBit(BYTE btGroup, BYTE btLevel);
static BYTE I2C_ReadBit(BYTE btGroup);
BYTE I2C_GetACK(BYTE btGroup);
BYTE I2C_SetACK(BYTE btGroup);
BYTE I2C_SetNAK(BYTE btGroup);
BOOL I2C_IsSlaveReady(VOID);
static VOID I2C_WaitToReady(BYTE btGroup);

//==============================================================================================================

//===== EXTERN VARIABLE DECLARTION =============================================================================
extern I2C_EMUL_MAPGPIO     *I2CEMULMapGpioLIst[I2C_MAX_GROUP];
extern I2C_EMUL_UNMAPGPIO   *I2CEMULUnmapGpioList[I2C_MAX_GROUP];
extern I2CEMUL_GPIO         I2CEMULGpioList[I2C_MAX_GROUP];
//==============================================================================================================

//===== EXTERN FUNCTION DECLARTION =============================================================================
extern VOID I2C_EMUL_MapVirtualGPIO0(PVOID pGpioBaseAddr);

extern VOID BusyWaitForOneClock(BYTE btGroup);
extern VOID BusyWaitForHalfClock(BYTE btGroup);
extern VOID BusyWaitForQuarterClock(BYTE btGroup);
//==============================================================================================================

/*------------------------------------------------------------------------------
        name    : I2C_EMUL_Init
        output  : error code
------------------------------------------------------------------------------*/

BYTE I2C_EMUL_Init(BYTE btGroup, PVOID pGpioBaseAddr, PVOID pBSPArgs, BYTE btSlaveAddress, DWORD dwSpeed)
{       
        I2C_EMUL_DBGPRINT("+++I2C_EMUL_Init()\n\r");

        RETAILMSG(TRUE, (TEXT("[I2CEMUL] VERSION INFO : %d\r\n"), I2CEMUL_VERSION));
        I2CEMULGpioList[btGroup].dwSpeed = dwSpeed;
        I2CEMULGpioList[btGroup].pBSPArgs = (BSP_ARGS *)pBSPArgs;
        I2CEMULGpioList[btGroup].btSlaveAddress = btSlaveAddress;

        I2CEMULMapGpioLIst[I2CEMUL_G0] = I2C_EMUL_MapVirtualGPIO0;

        I2CEMULMapGpioLIst[btGroup](pGpioBaseAddr);

        I2C_EMUL_LineInit(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        I2C_EMUL_SDAHIGH(btGroup);
        
        I2C_EMUL_DBGPRINT("---I2C_EMUL_Init()\n\r");
        return I2C_EMUL_ERR_SUCCESS;
}

BYTE I2C_EMUL_SetSlaveAddr(BYTE btGroup, BYTE btSlaveAddress)
{       
        I2C_EMUL_DBGPRINT("+++I2C_EMUL_SetSlaveAddr()\n\r");

        I2CEMULGpioList[btGroup].btSlaveAddress = (btSlaveAddress>>1);
        
        I2C_EMUL_DBGPRINT("---I2C_EMUL_SetSlaveAddr()\n\r");
        return I2C_EMUL_ERR_SUCCESS;
}

BYTE I2C_EMUL_SetSpeed(BYTE btGroup, DWORD dwSpeed)
{       
        I2C_EMUL_DBGPRINT("+++I2C_EMUL_SetSpeed()\n\r");

        I2CEMULGpioList[btGroup].dwSpeed = dwSpeed;
        
        I2C_EMUL_DBGPRINT("---I2C_EMUL_SetSpeed()\n\r");
        return I2C_EMUL_ERR_SUCCESS;
}

/*------------------------------------------------------------------------------
        name    : I2C_EMUL_Deinit
        output  : error code
------------------------------------------------------------------------------*/
BYTE I2C_EMUL_Deinit(BYTE btGroup)
{
        I2CEMULUnmapGpioList[btGroup]();
        return I2C_EMUL_ERR_SUCCESS;
}

/*------------------------------------------------------------------------------
        name    : I2C_EMUL_Write
        output  : error code
------------------------------------------------------------------------------*/
BYTE I2C_EMUL_Write(BYTE btGroup, BYTE btRegAddr, BYTE btRegData )
{
        BYTE bAck, bRtn=I2C_EMUL_ERR_SUCCESS;

        I2C_Start(btGroup);
        I2C_WriteByte(btGroup, (BYTE)((I2CEMULGpioList[btGroup].btSlaveAddress<<1)&0xFE) );
        bAck = I2C_GetACK(btGroup);
        if(bAck == LOW)
        {
                I2C_WriteByte(btGroup, btRegAddr);
                bAck = I2C_GetACK(btGroup);
                if(bAck == LOW)
                {
                        I2C_WriteByte(btGroup, btRegData);
                        bAck = I2C_GetACK(btGroup);
                        if(bAck != LOW)
                        {
                                RETAILMSG(TRUE, (TEXT("[I2CEMUL:W] NO ACK - Reg Data\r\n")));
                                bRtn=I2C_EMUL_ERR_FAILED;
                        }
                }
                else
                {
                        RETAILMSG(TRUE, (TEXT("[I2CEMUL:W] NO ACK - Reg Address\r\n")));
                        bRtn=I2C_EMUL_ERR_FAILED;
                }
        }
        else
        {
                RETAILMSG(TRUE, (TEXT("[I2CEMUL:W] NO ACK - 1st Slave Address\r\n")));
                bRtn=I2C_EMUL_ERR_FAILED;
        }

        I2C_Stop(btGroup);

        return bRtn;
}

BYTE I2C_EMUL_MWrite(BYTE btGroup, PBYTE pbtRegData , DWORD btWCount, BOOL bStop)
{
        BYTE bAck, bRtn=I2C_EMUL_ERR_SUCCESS;

        I2C_Start(btGroup);
        I2C_WriteByte(btGroup, (BYTE)((I2CEMULGpioList[btGroup].btSlaveAddress<<1)&0xFE) );
        bAck = I2C_GetACK(btGroup);
        if(bAck == LOW)
        {
                DWORD i;
                for( i=0; i<btWCount; i++)
                {
                        I2C_WriteByte(btGroup, pbtRegData[i]);
                        bAck = I2C_GetACK(btGroup);
                        if(bAck != LOW)
                        {
                                RETAILMSG(TRUE, (TEXT("[I2CEMUL:W] NO ACK - #%d Data\r\n"), (1+i)));
                                bRtn=I2C_EMUL_ERR_FAILED;
                                break;
                        }
                }
        }
        else
        {
                RETAILMSG(TRUE, (TEXT("[I2CEMUL:W] NO ACK - 1st Slave Address\r\n")));
                bRtn=I2C_EMUL_ERR_FAILED;
        }

        if( (bRtn == I2C_EMUL_ERR_FAILED) || bStop )I2C_Stop(btGroup);

        return bRtn;
}

/*------------------------------------------------------------------------------
        name    : I2C_EMUL_Read
        output  : error code
------------------------------------------------------------------------------*/
BYTE I2C_EMUL_Read(BYTE btGroup, BYTE btRegAddr, PBYTE pbtRegData )
{
        BYTE bAck, bRtn=I2C_EMUL_ERR_SUCCESS;

        I2C_Start(btGroup);
        I2C_WriteByte(btGroup, (BYTE)((I2CEMULGpioList[btGroup].btSlaveAddress<<1)&0xFE) );
        bAck = I2C_GetACK(btGroup);
        if(bAck == LOW)
        {
                I2C_WriteByte(btGroup, btRegAddr);
                bAck = I2C_GetACK(btGroup);
                if(bAck == LOW)
                {
                        I2C_Start(btGroup);     // repeated start
                        I2C_WriteByte(btGroup, (BYTE)((I2CEMULGpioList[btGroup].btSlaveAddress<<1)|0x01) );
                        bAck = I2C_GetACK(btGroup);
                        if(bAck == LOW)
                        {
                                *pbtRegData = I2C_ReadByte(btGroup);
                                bAck = I2C_SetNAK(btGroup);
                        }
                        else
                        {
                                RETAILMSG(TRUE, (TEXT("[I2CEMUL:R] NO ACK - 2nd Slave Address\r\n")));
                                bRtn=I2C_EMUL_ERR_FAILED;
                        }
                }
                else
                {
                        RETAILMSG(TRUE, (TEXT("[I2CEMUL:R] NO ACK - Reg Address\r\n")));
                        bRtn=I2C_EMUL_ERR_FAILED;
                }
        }
        else
        {
                RETAILMSG(TRUE, (TEXT("[I2CEMUL:R] NO ACK - 1st Slave Address\r\n")));
                bRtn=I2C_EMUL_ERR_FAILED;
        }

        I2C_Stop(btGroup);

        return bRtn;
}

BYTE I2C_EMUL_MRead(BYTE btGroup, PBYTE pbtRegData, DWORD btRCount, BOOL bStop)
{
        BYTE bAck, bRtn=I2C_EMUL_ERR_SUCCESS;

        I2C_Start(btGroup);
        I2C_WriteByte(btGroup, (BYTE)((I2CEMULGpioList[btGroup].btSlaveAddress<<1)|0x01) );
        bAck = I2C_GetACK(btGroup);
        if(bAck == LOW)
        {
                DWORD i;
                
                for( i=0; i<(DWORD)(btRCount-1); i++)
                {
                        pbtRegData[i] = I2C_ReadByte(btGroup);
                        I2C_SetACK(btGroup);
                }

                pbtRegData[i] = I2C_ReadByte(btGroup);
                I2C_SetNAK(btGroup);
        }
        else
        {
                RETAILMSG(TRUE, (TEXT("[I2CEMUL:R] NO ACK - 1st Slave Address\r\n")));
                bRtn=I2C_EMUL_ERR_FAILED;
        }

        if((bRtn == I2C_EMUL_ERR_FAILED) || bStop)
                I2C_Stop(btGroup);

        return bRtn;
}

//------------------------------------------------------------------------------
// function definitions : internal function
//------------------------------------------------------------------------------
VOID I2C_Start(BYTE btGroup)
{
        /*      
                        ____
        SDA         __|     |____
                            ____ 
        SCL         ____|      |__

        */      
        I2C_EMUL_SDAHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);       
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);       
        I2C_EMUL_SDALOW(btGroup);
        BusyWaitForQuarterClock(btGroup);       
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);       
}

VOID I2C_Stop(BYTE btGroup)
{
        /*      
                            __
        SDA         ____|
                        ___
        SCL         __|  

        */      
        
        //CONTROL SCL/SDA
        I2C_EMUL_SDALOW(btGroup);
        BusyWaitForQuarterClock(btGroup);       
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);       
        I2C_EMUL_SDAHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);       
}

VOID I2C_WriteByte(BYTE btGroup, BYTE btData)
{
        BYTE i;
        bSlaveReady = TRUE;

        //CONTROL SCL/SDA
        for ( i=0 ; i < 8 ; i++ )
        {
                I2C_WriteBit(btGroup, (btData<<i) & 0x80);
        }       
}

static VOID I2C_WriteBit(BYTE btGroup, BYTE btLevel)
{
        /*      
                     __________
        SDA         |__________
                        ____
        SCL         __|     |__

        */      
        if(btLevel == LOW)
                I2C_EMUL_SDALOW(btGroup);
        else
                I2C_EMUL_SDAHIGH(btGroup);
                
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForHalfClock(btGroup);  
        I2C_WaitToReady(btGroup);
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);
}

BYTE I2C_ReadByte(BYTE btGroup)
{
        BYTE i, bRtn=0x00;
        bSlaveReady = TRUE;

        //CONTROL SCL/SDA
        for( i=0 ; i < 8 ; i++ )
        {
                bRtn = bRtn | (I2C_ReadBit(btGroup)<<(7-i));
        }
        
        return bRtn;
}

static BYTE I2C_ReadBit(BYTE btGroup)
{
        BYTE bRtn=0x00;

        /*      
                     __________
        SDA         |__________
                        ____
        SCL         __|     |__

        */      
        I2C_EMUL_SDAHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_WaitToReady(btGroup);
        bRtn = I2C_EMUL_GetSDA(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);
        
        return bRtn;
}

BYTE I2C_GetACK(BYTE btGroup)
{
        BYTE bRtn;

        /*      
                    
        SDA         __________
                           ____
        SCL         __|    |__

        */      

        //CONTROL SCL/SDA
        I2C_EMUL_SDAHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);
        bRtn = I2C_EMUL_GetSDA(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);
        
        return bRtn;
}

BYTE I2C_SetACK(BYTE btGroup)
{
        /*      
                    __________
        SDA         
                           ____
        SCL         __|    |__

        */       

        //CONTROL SCL/SDA
        I2C_EMUL_SDALOW(btGroup);  
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForHalfClock(btGroup);
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SDAHIGH(btGroup);
   
        return LOW;
}

BYTE I2C_SetNAK(BYTE btGroup)
{
        /*      
                        
        SDA         __________
                           ____
        SCL         __|    |__

        */       

        //CONTROL SCL/SDA
        I2C_EMUL_SDAHIGH(btGroup);
        BusyWaitForQuarterClock(btGroup);
        I2C_EMUL_SCLHIGH(btGroup);
        BusyWaitForHalfClock(btGroup);
        I2C_EMUL_SCLLOW(btGroup);
        BusyWaitForQuarterClock(btGroup);

        return HIGH;
}

static VOID I2C_WaitToReady(BYTE btGroup)
{
        DWORD dwTry = TRY_COUNT_TO_WAIT;

        while(dwTry)
        {
                if(I2C_EMUL_GetSCL(btGroup) == HIGH) break;
                BusyWaitForQuarterClock(btGroup);
                dwTry--;
        }
        if(dwTry == 0) 
        {
                RETAILMSG(TRUE, (TEXT("\r\n[I2CEMUL:WRITE_ERROR] SLAVE IS HOLDING\r\n")));
                bSlaveReady = FALSE;
        }
        else if(dwTry != TRY_COUNT_TO_WAIT) BusyWaitForQuarterClock(btGroup);
}

BOOL I2C_IsSlaveReady(VOID)
{
        return bSlaveReady;
}
