#pragma once

#include <WINDOWS.h>
#include <I2CDependency.h>
#include <bsp.h>

//#define IIC_INIT IIC_USR4

typedef enum
{
    GENERAL_ADDR        = 0x00,
    GENERAL_ADDR_MASK   = 0xFF,
	START_BYTE			= 0x01,
	START_BYTE_MASK		= 0xFF,
	CBUS_ADDR    		= 0x02,
	CBUS_ADDR_MASK		= 0xFE,
	FOR_OTHER_BUS		= 0x04,
	FOR_OTHER_BUS_MASK	= 0xFE,
	FOR_FUTURE1			= 0x06,
	FOR_FUTURE1_MASK	= 0xFE,
	HS_MODE				= 0x08,
	HS_MODE_MASK		= 0xF8,
	FOR_FUTURE2			= 0xF8,
	FOR_FUTURE2_MASK	= 0xF8,
	TEN_BIT_ADDR		= 0xF0,
	TEN_BIT_ADDR_MASK	= 0xF8,
	DEFAULT_SLAVE_ADDR	= 0xC0 
} FIRST_BYTE_TYPE;

typedef enum
{
	I2CSTATE_BEGIN = 0,
	I2CSTATE_START_CUSTOM,
	I2CSTATE_START,
	I2CSTATE_SET_ADDR,
	I2CSTATE_SLAVE_DETECTION,
	I2CSTATE_SET_DATA = 5,
	I2CSTATE_GET_DATA,
	I2CSTATE_GET_ACK,	
	I2CSTATE_SET_ACK,	
	I2CSTATE_SET_NAK,	
	I2CSTATE_SET_LOOP = 10,
	I2CSTATE_WAIT_TO_READY,
	I2CSTATE_STOP, 
	I2CSTATE_END,
	I2CSTATE_END_WITH_NO_STOP,
	I2CSTATE_UNDEFINED = 0xFE,
	I2CSTATE_MASK = 0xFF
} I2C_STATE;

typedef  enum
{
    I2CERR_NO_ERROR             = 0x0000,
	I2CERR_INVALID_INIT 		= 0x0200,
	I2CERR_INVALID_SEQUENCE 	= 0x0300,
	I2CERR_BUS_BUSY 			= 0x0400,
	I2CERR_SLAVE_BUSY 			= 0x0500,	
	I2CERR_SLAVE_NO_ACK 		= 0x0600,
	I2CERR_DUPLICATED 			= 0x0700,
	I2CERR_WAIT_FAILED 			= 0x0800,
	I2CERR_WAIT_TIMEOUT 		= 0x0900,
	I2CERR_UNKNOWN       		= 0xFE00,
	I2CERR_ERROR_MASK 			= 0xFF00
} I2C_ERROR;

typedef  enum
{
	I2CMODE_SLAVE_READ,
	I2CMODE_SLAVE_WRITE,
	I2CMODE_MASTER_READ,
	I2CMODE_MASTER_WRITE,
	I2CMODE_END
} I2C_MODE;

typedef enum
{
    I2C_THREAD_STATE_INVALID,
	I2C_THREAD_STATE_INIT,
	I2C_THREAD_STATE_READY,
	I2C_TRHEAD_STATE_RUNNING,
	I2C_THREAD_STATE_SUSPEND
} I2C_THREAD_STATE;

typedef enum
{
	SDA_LOW,
	SDA_HIGH,
	SDA_UNDEFINED
} I2C_SDA;

typedef enum
{
	SCL_LOW,
	SCL_HIGH,
	SCL_UNDEFINED
} I2C_SCL;

typedef struct _STATE_FIFO
{
	DWORD 		State;
	PBYTE		pbtData;
	BYTE		btData;
	DWORD		dwTime;
} STATE_FIFO, *PSTATE_FIFO;

#define I2C_STATE_MAX 30
#define I2C_ACK 0
#define I2C_NAK 1

#define I2C_METHOD_POLLING 		0
#define I2C_METHOD_INTERRUPT 	1

class I2CObject
{
private:
	CRITICAL_SECTION I2COB_CS;

	BOOL		bSuccess;
	BOOL		bInitialized;
	BOOL		bEmulation;

	I2C_MODE 	Mode;

	WORD 		wSlaveAddr;	
	DWORD		dwClockSpeed;
	DWORD		dwClockPSSelBit;
	DWORD		dwClockValue;

	DWORD		dwFIFOIdxCnt;	
	STATE_FIFO 	StateFIFO[I2C_STATE_MAX];

	//BOOL		bRecordedStateFIFO[IOCTL_I2C_MAX_COUNT];
	//STATE_FIFO	StateFIFOList[IOCTL_I2C_MAX_COUNT][I2C_STATE_MAX];	
public:
	PBYTE		pTxBuffer;
	DWORD		dwTxCount;

	PBYTE		pRxBuffer;
	DWORD		dwRxCount;
public:
	I2CObject();
	~I2CObject();

	VOID		Init(VOID);
	DWORD		Init(BOOL bEmulation, WORD wSlaveAddr, DWORD dwTempClockSpeed);
	VOID		Lock(VOID) {EnterCriticalSection(&I2COB_CS);}
	VOID		Unlock(VOID) {LeaveCriticalSection(&I2COB_CS);}
	BOOL 		SetSlaveAddr(WORD wAddr);
	WORD		GetSlaveAddr(VOID) {return wSlaveAddr;}
	WORD		GetReadSlaveAddr(VOID) {return wSlaveAddr+1;}
	WORD		GetWriteSlaveAddr(VOID) {return wSlaveAddr+0;}
	VOID		SetClockSpeed(DWORD dwTempClockSpeed);
	DWORD		GetClockSpeed(VOID) {return dwClockSpeed;}
	DWORD		GetClockPSSelBit(VOID) {return dwClockPSSelBit;}
	DWORD		GetClockValue(VOID) {return dwClockValue;}

	I2CObject& 	Begin(VOID);
	I2CObject& 	SetStartCustom(DWORD dwMode);	
	I2CObject& 	SetStart(DWORD dwMode);
	I2CObject& 	DetectSlave(VOID);	
	I2CObject& 	SetData(VOID);
	I2CObject& 	GetData(VOID);
	I2CObject& 	GetACK(PBYTE pbtACK);
	I2CObject& 	SetACK(VOID);
	I2CObject& 	SetNAK(VOID);
	I2CObject&	SetLoop(BYTE dwGoBackCnt, DWORD dwLoopCnt);	
	I2CObject& 	WaitToReady(DWORD dwTimeToWait, PBOOL pbSlaveReady);
	I2CObject& 	SetStop(VOID);	
	BOOL 		End(VOID);

	VOID		ClrStateFIFO(VOID);
	BOOL		CheckStateList(VOID);
	VOID		PrintStateList(VOID);
	BOOL		IsValidFIFOIdxCnt(VOID);
	STATE_FIFO& operator[](DWORD index) {return StateFIFO[index];}
};

typedef I2CObject * PI2CObject;

class I2CCtrlContext
{
private:
	CRITICAL_SECTION	I2CHW_CS;
	
protected:
	volatile PSXXXXXX_GPIO_REG	pGPIOReg;
	volatile BSP_ARGS *			pBSPArgs;

	DWORD		dwChannel;	
	DWORD		dwSpeed;
	BOOL		bInitialized;
	BOOL		bSuccess;	
	
	HANDLE		hI2CEvent;
	HANDLE		hI2CDoneEvent;
	HANDLE		hI2CThread;
	DWORD		dwI2CThreadPriority;	

	DWORD		dwFIFOIdx;
	I2CObject*	pCurrServObj;

	DWORD		dwTxDataCount;
	DWORD		dwRxDataCount;
	
	DWORD		dwMethod;

    I2C_ERROR   LastError;
	I2C_THREAD_STATE I2CThreadState;  

	CEDEVICE_POWER_STATE PowerState;

public:
	I2CCtrlContext();
	~I2CCtrlContext();

	VOID		Lock(VOID) {EnterCriticalSection(&I2CHW_CS);}
	VOID		Unlock(VOID) {LeaveCriticalSection(&I2CHW_CS);}
	DWORD		GetMethod(VOID) {return dwMethod;}
	DWORD		I2CProcessException(LPEXCEPTION_POINTERS pException);
	UCHAR		GetSupportablePowerState(VOID) {return 0;/*DX_MASK(D0)|DX_MASK(D2) | DX_MASK(D4);*/}
	VOID		SetPowerState(CEDEVICE_POWER_STATE requestState) {PowerState = requestState;}
	CEDEVICE_POWER_STATE GetPowerState(VOID) {return PowerState;}

	virtual BOOL	Open(DWORD dwChannel, DWORD dwMethod, DWORD dwThreadPriority, WORD wSlaveAddr, PSXXXXXX_I2C_REG pI2CReg, PSXXXXXX_GPIO_REG pGPIOReg, PSXXXXXX_SYSCLK_REG pSYSCLKReg, PSXXXXXX_BSP_ARG pBSPArgs) = 0;
	virtual BOOL	Close(VOID) = 0;
	virtual BOOL	StartTransfer(PI2CObject pServiceObj) =0;
	virtual BOOL	PowerUp(VOID) = 0;
	virtual BOOL	PowerDown(VOID) = 0;
	virtual DWORD	GetSysIntr(VOID) = 0;
};

typedef I2CCtrlContext * pI2CCtrlContext;

class I2CHWContext : public I2CCtrlContext
{
private:
	SXXXXXX_I2C_REG 					I2CReg;
	volatile PSXXXXXX_I2C_REG 			pI2CReg;
	volatile PSXXXXXX_SYSCLK_REG 		pSYSCLKReg;

	HANDLE			hI2CHWEvent;
	DWORD			dwI2CSysIntr;	

	inline VOID		I2CBUS_Enable(BOOL bEnable);
	inline VOID		I2CBUS_FilterEnable(BOOL bfilterEnable);
	inline VOID		I2CBUS_InterruptEnable(BOOL bIntEnable);	
	inline VOID		I2CBUS_AckEnable(BOOL bAckEnable);
	inline BOOL		I2CBUS_IsAckEnable(VOID);
	inline BOOL		I2CBUS_IsIntrPending(VOID);
	inline VOID		I2CBUS_ClrIntrPending(VOID);
	inline BOOL		I2CBUS_ModeSelection(I2C_MODE mode);
	inline VOID		I2CBUS_SetClockSpeed(DWORD dwClockPSSelBit, DWORD dwClockValue);
	inline BOOL		I2CBUS_SetSlaveAddr(WORD wSlaveAddr);
	inline VOID		I2CBUS_SetData(PBYTE pbtData);
	inline VOID		I2CBUS_SetData(BYTE btData);	
	inline VOID		I2CBUS_GetData(PBYTE pbtData);
	inline VOID		I2CBUS_SetStart(VOID);
	inline VOID		I2CBUS_SetStop(VOID);
	inline I2C_SCL	I2CBUS_GetSCL(VOID);
	inline VOID		I2CBUS_GetACK(PBYTE pbtData);

	inline VOID		I2CBUS_Enable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bEnable);	
	inline VOID		I2CBUS_FilterEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bfilterEnable);
	inline VOID		I2CBUS_InterruptEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bIntEnable);
	inline VOID		I2CBUS_AckEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bAckEnable);
	inline BOOL		I2CBUS_ModeSelection(PSXXXXXX_I2C_REG pI2CRegMirror, I2C_MODE mode);
	inline VOID		I2CBUS_SetClockSpeed(PSXXXXXX_I2C_REG pI2CRegMirror, DWORD dwClockPSSelBit, DWORD dwClockValue);
	inline VOID		I2CBUS_SetStart(PSXXXXXX_I2C_REG pI2CRegMirror);
	
public:
	I2CHWContext();
	~I2CHWContext();

	BOOL			Open(DWORD dwChannel, DWORD dwMethod, DWORD dwThreadPriority, WORD wSlaveAddr, PSXXXXXX_I2C_REG pI2CReg, PSXXXXXX_GPIO_REG pGPIOReg, PSXXXXXX_SYSCLK_REG pSYSCLKReg, PSXXXXXX_BSP_ARG pBSPArgs);
	BOOL			Close(VOID);
	BOOL			StartTransfer(PI2CObject pServiceObj);

	BOOL			PowerUp(VOID);
	BOOL			PowerDown(VOID);

	BOOL            CreateI2CThread(VOID);
	BOOL            DestroyI2CThread(VOID);

	VOID			PrintI2CRegList(VOID);
	VOID			I2CInterruptThread(VOID);
	VOID			I2CPollingThread(VOID);
	DWORD			GetSysIntr(VOID) {return dwI2CSysIntr;}
};

typedef I2CHWContext * PI2CHWContext;

inline VOID				
I2CHWContext::I2CBUS_Enable(BOOL bEnable)
{
	if(bEnable)
	{
		pI2CReg->IICSTAT |= (1<<4);
	}
	else
{
		pI2CReg->IICSTAT &= ~(1<<4);
	}
}

inline VOID				
I2CHWContext::I2CBUS_Enable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bEnable)
{
	if(bEnable)
	{
		pI2CRegMirror->IICSTAT |= (1<<4);
	}
	else
	{
		pI2CRegMirror->IICSTAT &= ~(1<<4);
	}
}

inline VOID				
I2CHWContext::I2CBUS_FilterEnable(BOOL bfilterEnable)
{
	if(bfilterEnable)
	{
		pI2CReg->IICLC |= (1<<2);
	}
	else
	{
		pI2CReg->IICLC &= ~(1<<2);
	}
}

inline VOID		
I2CHWContext::I2CBUS_FilterEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bfilterEnable)
{
	if(bfilterEnable)
	{
		pI2CRegMirror->IICLC |= (1<<2);
	}
	else
	{
		pI2CRegMirror->IICLC &= ~(1<<2);
	}
}

inline VOID		
I2CHWContext::I2CBUS_InterruptEnable(BOOL bIntEnable)
{
	if(bIntEnable)
	{
		pI2CReg->IICCON |= (1<<5);
	}
	else
	{
		pI2CReg->IICCON &= ~(1<<5);
	}
}

inline VOID				
I2CHWContext::I2CBUS_InterruptEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bIntEnable)
{
	if(bIntEnable)
	{
		pI2CRegMirror->IICCON |= (1<<5);
	}
	else
	{
		pI2CRegMirror->IICCON &= ~(1<<5);
	}
}

inline VOID				
I2CHWContext::I2CBUS_AckEnable(BOOL bAckEnable)
{
	if(bAckEnable)
	{
		pI2CReg->IICCON |= (1<<7);
	}
	else
	{
		pI2CReg->IICCON &= ~(1<<7);
	}
}

inline VOID				
I2CHWContext::I2CBUS_AckEnable(PSXXXXXX_I2C_REG pI2CRegMirror, BOOL bAckEnable)
{
	if(bAckEnable)
	{
		pI2CRegMirror->IICCON |= (1<<7);
	}
	else
	{
		pI2CRegMirror->IICCON &= ~(1<<7);
	}
}

inline BOOL		
I2CHWContext::I2CBUS_IsAckEnable(VOID)
{
	return ((pI2CReg->IICCON & (1<<7)) ? TRUE : FALSE);
}

inline BOOL				
I2CHWContext::I2CBUS_IsIntrPending(VOID)
{	
	return ((pI2CReg->IICCON & (1<<4)) ? TRUE : FALSE);
}

inline VOID				
I2CHWContext::I2CBUS_ClrIntrPending(VOID)
{
	pI2CReg->IICCON &= ~(1<<4);
}

inline BOOL				
I2CHWContext::I2CBUS_ModeSelection(I2C_MODE mode)
{
	pI2CReg->IICSTAT &= ~(3<<6);
	pI2CReg->IICSTAT |= ((DWORD)mode<<6);

	return TRUE;
}

inline BOOL				
I2CHWContext::I2CBUS_ModeSelection(PSXXXXXX_I2C_REG pI2CRegMirror, I2C_MODE mode)
{
	pI2CRegMirror->IICSTAT &= ~(3<<6);
	pI2CRegMirror->IICSTAT |= ((DWORD)mode<<6);

	return TRUE;
}

inline VOID				
I2CHWContext::I2CBUS_SetClockSpeed(DWORD dwClockPSSelBit, DWORD dwClockValue)
{
	pI2CReg->IICCON = (pI2CReg->IICCON & ~(0x1<<6)) |(dwClockPSSelBit<<6);
	pI2CReg->IICCON = (pI2CReg->IICCON & ~(0xF<<0)) |(dwClockValue<<0);
}

inline VOID				
I2CHWContext::I2CBUS_SetClockSpeed(PSXXXXXX_I2C_REG pI2CRegMirror, DWORD dwClockPSSelBit, DWORD dwClockValue)
{
	pI2CRegMirror->IICCON = (pI2CRegMirror->IICCON & ~(0x1<<6)) |(dwClockPSSelBit<<6);
	pI2CRegMirror->IICCON = (pI2CRegMirror->IICCON & ~(0xF<<0)) |(dwClockValue<<0);
}

inline BOOL				
I2CHWContext::I2CBUS_SetSlaveAddr(WORD wSlaveAddr)
{
    if((wSlaveAddr & GENERAL_ADDR_MASK) == GENERAL_ADDR)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE GERNERAL_ADDR\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & START_BYTE_MASK) == START_BYTE)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE START_BYTE\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & CBUS_ADDR_MASK) == CBUS_ADDR)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE CBUS_ADDR\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & FOR_OTHER_BUS_MASK) == FOR_OTHER_BUS)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE FOR_OTHER_BUS\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & FOR_FUTURE1_MASK) == FOR_FUTURE1)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE FOR_FUTURE1\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & HS_MODE_MASK) == HS_MODE)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE HS_MODE\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & FOR_FUTURE2_MASK) == FOR_FUTURE2)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE FOR_FUTURE2\r\n"));
		return FALSE;
	}
	else if((wSlaveAddr & TEN_BIT_ADDR_MASK) == TEN_BIT_ADDR)
	{
		ERRMSG((L"[I2C:E] UNSUPPORTABLE TEN_BIT_ADDR\r\n"));
		return FALSE;
	}
	else 
	{
		pI2CReg->IICADD = (wSlaveAddr & 0xFE);
		return TRUE;
	}
}

#define CurrServObj (*pCurrServObj)

inline VOID 
I2CHWContext::I2CBUS_SetData(PBYTE pbtData)
{	
	pI2CReg->IICDS = (BYTE) (*pbtData & 0xFF);
}

inline VOID		
I2CHWContext::I2CBUS_SetData(BYTE btData)
{
	pI2CReg->IICDS = (BYTE) (btData & 0xFF);
}

inline VOID
I2CHWContext::I2CBUS_GetData(PBYTE pbtData)
{	
	*pbtData = (pI2CReg->IICDS & 0xFF);
}

inline VOID			
I2CHWContext::I2CBUS_SetStart(VOID)
{
	pI2CReg->IICSTAT |= (1<<5);
}

inline VOID			
I2CHWContext::I2CBUS_SetStart(PSXXXXXX_I2C_REG pI2CRegMirror)
{
	pI2CRegMirror->IICSTAT |= (1<<5);
}

inline VOID			
I2CHWContext::I2CBUS_SetStop(VOID)
{
	pI2CReg->IICSTAT &= ~(1<<5);
}

inline I2C_SCL			
I2CHWContext::I2CBUS_GetSCL(VOID)
{
	I2C_SCL scl = SCL_LOW;
#if 0
	I2CBUS_Enable(FALSE);

	if(dwChannel == 0)
	{
		Set_PinFunction(pGPIOReg, GPD04_Input);

		if(Get_PinData(pGPIOReg, GPD04_Input)) scl = SCL_HIGH;
		else scl = SCL_LOW;
		
		Set_PinFunction(pGPIOReg, GPD04_I2C0_SCL);
/*
		pGPIOReg->GPDPUD &= ~(0x3<<6);
		pGPIOReg->GPDCON &= ~(0xF<<12);

		if(pGPIOReg->GPDDAT & (0x1<<3)) sda = SDA_HIGH;
		else sda = SDA_LOW;

		pGPIOReg->GPDCON &= ~(0xF<<12);
		pGPIOReg->GPDCON |= (0x2<<12);	
*/
		}
		else
		{
		Set_PinFunction(pGPIOReg, GPD06_Input);
				
		if(Get_PinData(pGPIOReg, GPD06_Input)) scl = SCL_HIGH;
		else scl = SCL_LOW;

		Set_PinFunction(pGPIOReg, GPD06_I2C1_SCL);	
/*
		pGPIOReg->GPDPUD &= ~(0x3<<10);	
		pGPIOReg->GPDCON &= ~(0xF<<20);
				
		if(pGPIOReg->GPDDAT & (0x1<<5)) sda = SDA_HIGH;
		else sda = SDA_LOW;

		pGPIOReg->GPDCON &= ~(0xF<<20);
		pGPIOReg->GPDCON |= (0x2<<20);		
*/
        }
	
	I2CBUS_Enable(TRUE);
#endif
	return scl;
}

inline VOID		
I2CHWContext::I2CBUS_GetACK(PBYTE pbtData)
{
	*pbtData = (BYTE) pI2CReg->IICSTAT & (1<<0);
}

