#include <stdio.h>
#include "si117x_sys_out.h"
#if (SI117X_BUS == SI117X_I2C)
	#include "i2cspm.h"
	#include "em_i2c.h"
#endif
#if (SI117X_BUS == SI117X_SPI)
	#include "spidrv.h"
#endif

#include "em_gpio.h"
#include "string.h"
//#include "rtcdriver.h"
#include "em_gpio.h"
#include "si117x_functions.h"
#include "udelay.h"


/*  I2C port configuration */
static Si117xPortConfig_t _handle[2];
static int handleCount = 0;

/*  Non-exported Function Prototypes  */
#if (SI117X_BUS == SI117X_I2C)
	static int16_t Si117x_i2c_smbus_write_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t data);
	static int16_t Si117x_i2c_smbus_read_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t *data);
	static int16_t Si117x_i2c_smbus_write_i2c_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t const* values);
	static int16_t Si117x_i2c_smbus_read_i2c_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t* values);
#elif (SI117X_BUS == SI117X_SPI)
  SPIDRV_HandleData_t spidrv_handles[2];
	static int32_t Si117x_spi_write_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t data);
	static int32_t Si117x_spi_read_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t *data);
	static int32_t Si117x_spi_read_spi_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t* values);
#endif


/*  Critical Sections in this code are meant to prevent I2C or SPI collisions. If bus transactions occur
 *  inside an interrupt context then collisions can occur.  In this case, the code should disable any
 *  interrupts in which a collision may occur.  The "safe" thing to do is to ensure that bus transactions
 *  never occur in the interrupt context.  In this case critical sections are not needed.
 */
#define SI117X_USE_CRITICAL_SECTIONS  0

#if (SI117X_USE_CRITICAL_SECTIONS == 1)
	void Si117xEnterCriticalSection(void)
	{
	   /*  add code here to disable any interrupts that may impact this section
	    *  typically this is any interrupt in which an I2C or SPI transaction may occur
	    */

	}

	void Si117xExitCriticalSection(void)
	{
		   /*  add code here to re-enable any interrupts that may impact this section
		    *  typically this is any interrupt in which an I2C or SPI transaction may occur
		    *  Be careful not to accidently enable interrupts that were not previously disabled
		    */

	}

#else
  #define Si117xEnterCriticalSection()     //Do nothing
  #define Si117xExitCriticalSection()      //Do nothing
#endif


/**************************************************************************//**
 * @brief Get the current handle.
 *****************************************************************************/
HANDLE GetCurrentHandle(void)
{
  return &_handle[handleCount];
}

/**************************************************************************//**
 * @brief Write to Si117x register
 *****************************************************************************/
int32_t Si117xWriteToRegister(HANDLE si117x_handle, uint8_t address, uint8_t data)
{
#if (SI117X_BUS == SI117X_I2C)
  return Si117x_i2c_smbus_write_byte_data(si117x_handle, address, data);
#elif (SI117X_BUS == SI117X_SPI)
  return Si117x_spi_write_byte_data(si117x_handle, address, data);
#endif
}

/**************************************************************************//**
 * @brief Read from Si117x register.
 *****************************************************************************/
int32_t Si117xReadFromRegister(HANDLE si117x_handle, uint8_t address)
{
  uint8_t data;
#if (SI117X_BUS == SI117X_I2C)
  Si117x_i2c_smbus_read_byte_data(si117x_handle, address, &data);
#elif (SI117X_BUS == SI117X_SPI)
  Si117x_spi_read_byte_data(si117x_handle, address, &data);
#endif
  return data;
}

/**************************************************************************//**
 * @brief block write to si117x
 * Block writes should never be used.
 *****************************************************************************/
int32_t Si117xBlockWrite(HANDLE si117x_handle,
                     uint8_t address, uint8_t length, uint8_t const *values)
{
#if (SI117X_BUS == SI117X_I2C)
  return Si117x_i2c_smbus_write_i2c_block_data(si117x_handle,
                                               address,
                                               length,
                                               values);
#elif (SI117X_BUS == SI117X_SPI)
  // Block write is not supported.
  (void) si117x_handle;
  (void) address;
  (void) length;
  (void) values;
  return -1;
#endif
}

/**************************************************************************//**
 * @brief Block read from Si117x.
 *****************************************************************************/
int32_t Si117xBlockRead(HANDLE si117x_handle,
                    uint8_t address, uint8_t length, uint8_t *values)
{
#if (SI117X_BUS == SI117X_I2C)
	return Si117x_i2c_smbus_read_i2c_block_data(si117x_handle,
                           address,    length,     values);
#elif (SI117X_BUS == SI117X_SPI)
	return Si117x_spi_read_spi_block_data(si117x_handle,
	                           address,    length,     values);
#endif
}

/**************************************************************************//**
 * @brief Disable GPIO interrupt for Si117x interrupt.
 *****************************************************************************/
void Si117xDisableInterrupt (void)
{
  uint8_t i;
  for(i=0; i < handleCount; i++)
  {
    GPIO_IntDisable(1 << _handle[i].irqPin);
  }
}

/**************************************************************************//**
 * @brief Enable GPIO interrupt for Si117x.
 *****************************************************************************/
void Si117xEnableInterrupt (void)
{
  uint8_t i;
  Si117xPortConfig_t handle;
  for(i=0; i < handleCount; i++)
  {
    handle = _handle[i];
    if (GPIO_PinInGet(handle.irqPort, handle.irqPin) == 0)
      GPIO_IntSet(1<<handle.irqPin);
    GPIO_IntEnable(1<<handle.irqPin);
  }
}



/**************************************************************************//**
 * @brief Initialize low level handle and clear irq queue.
 *****************************************************************************/
int16_t Si117xInit(void *port, int options, HANDLE *si117x_handle)
{
  int16_t error = 0;
  uint8_t data;

  (void) options;

  *si117x_handle = (HANDLE)(&(_handle[handleCount]));
	((Si117xPortConfig_t*)*si117x_handle)->irqPort = ((Si117xPortConfig_t*)port)->irqPort;
	((Si117xPortConfig_t*)*si117x_handle)->irqPin = ((Si117xPortConfig_t*)port)->irqPin;

#if (SI117X_BUS == SI117X_I2C)
  ((Si117xPortConfig_t*)*si117x_handle)->i2cPort = ((Si117xPortConfig_t*)port)->i2cPort;
	((Si117xPortConfig_t*)*si117x_handle)->i2cAddress = ((Si117xPortConfig_t*)port)->i2cAddress << 1;
#elif (SI117X_BUS == SI117X_SPI)
	_handle[handleCount].spiHandle = &spidrv_handles[handleCount];

  Si117xPortConfig_t *pc = *si117x_handle;
  SPIDRV_Init(pc->spiHandle, ((Si117xPortConfig_t*)port)->spiPortConfig );
#endif

  //handleCount += 1;

  data = Si117xReadFromRegister(*si117x_handle, REG_PART_ID);

  if ((data != 0x71) && (data != 0x72) && (data != 0x73) && (data != 0x74) && (data != 0x75) &&
    	(data != 0x81) && (data != 0x82) && (data != 0x83) && (data != 0x84) && (data != 0x85))
  	error = -1;

  return error;
}


/**************************************************************************//**
 * @brief Close Si117x.
 *****************************************************************************/
int16_t Si117xClose(HANDLE si117x_handle)
{
#if (SI117X_BUS == SI117X_I2C)
  Si117xPortConfig_t* handle;
  handle = (Si117xPortConfig_t *)si117x_handle;
  handle->i2cAddress = 0xff;
#elif (SI117X_BUS == SI117X_SPI)
  SPIDRV_DeInit((SPIDRV_Handle_t) si117x_handle);
#endif
  return 0;
}


#if (SI117X_BUS == SI117X_I2C)
/**************************************************************************//**
 * @brief Write to Si117x i2c.
 *****************************************************************************/
static int16_t Si117x_i2c_smbus_write_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t data)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  Si117xPortConfig_t* handle;
  uint8_t i2c_write_data[2];
  uint8_t i2c_read_data[1];
  handle = (Si117xPortConfig_t *)si117x_handle;


  Si117xEnterCriticalSection();

  seq.addr  = handle->i2cAddress;
  seq.flags = I2C_FLAG_WRITE;
  /* Select register and data to write */
  i2c_write_data[0] = address | 0x40;
  i2c_write_data[1] = data;
  seq.buf[0].data = i2c_write_data;
  seq.buf[0].len  = 2;
  seq.buf[1].data = i2c_read_data;
  seq.buf[1].len  = 0;



  ret = I2CSPM_Transfer(handle->i2cPort, &seq);

  Si117xExitCriticalSection();

  if (ret != i2cTransferDone)
  {
    return (int16_t)ret;
  }
  return (int16_t)0;
}

/**************************************************************************//**
 * @brief Read from Si117x i2c.
 *****************************************************************************/
static int16_t Si117x_i2c_smbus_read_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t *data)
{
  //  si117x_handle is not used in the EFM32.   We use a global instead
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  uint8_t i2c_write_data[1];
  Si117xPortConfig_t* i2cDrvHandle;
  i2cDrvHandle = (Si117xPortConfig_t *)si117x_handle;

  Si117xEnterCriticalSection();

  seq.addr  = i2cDrvHandle->i2cAddress;
  seq.flags = I2C_FLAG_WRITE_READ;
  /* Select register to start reading from */
  i2c_write_data[0] = address | 0x40;
  seq.buf[0].data = i2c_write_data;
  seq.buf[0].len  = 1;
  /* Select length of data to be read */
  seq.buf[1].data = data;
  seq.buf[1].len  = 1;

  ret = I2CSPM_Transfer(i2cDrvHandle->i2cPort, &seq);

  Si117xExitCriticalSection();

  if (ret != i2cTransferDone)
  {
  	*data = 0xff;
  	return((int) ret);
  }
  return((int) 1);
}

/**************************************************************************//**
 * @brief Write block of data to Si117x i2c.
 *****************************************************************************/
static int16_t Si117x_i2c_smbus_write_i2c_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t const* data)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  uint8_t i2c_write_data[10];
  uint8_t i2c_read_data[1];
  Si117xPortConfig_t* handle;
  int i;
  handle = (Si117xPortConfig_t *)si117x_handle;

  Si117xEnterCriticalSection();

  seq.addr  = handle->i2cAddress;
  seq.flags = I2C_FLAG_WRITE;
  /* Select register to start writing to*/
  i2c_write_data[0] = address;
  for (i=0; i<length;i++)
  {
    i2c_write_data[i+1] = data[i];
  }
  seq.buf[0].data = i2c_write_data;
  seq.buf[0].len  = 1+length;
  seq.buf[1].data = i2c_read_data;
  seq.buf[1].len  = 0;



  ret = I2CSPM_Transfer(handle->i2cPort, &seq);
//  ret = I2CDRV_Transfer(&seq);


  Si117xExitCriticalSection();

  if (ret != i2cTransferDone)
  {
    return((int) ret);
  }

  return((int) 0);
}

/**************************************************************************//**
 * @brief Read block of data from Si117x i2c.
 *****************************************************************************/
static int16_t Si117x_i2c_smbus_read_i2c_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t* data)
{
  I2C_TransferSeq_TypeDef    seq;
  I2C_TransferReturn_TypeDef ret;
  uint8_t i2c_write_data[1];
  Si117xPortConfig_t* handle;


  //uint16_t t1, t2;

  handle = (Si117xPortConfig_t *)si117x_handle;
  seq.addr  = handle->i2cAddress;
  seq.flags = I2C_FLAG_WRITE_READ;

  Si117xEnterCriticalSection();

  /* Select register to start reading from */
  i2c_write_data[0] = address | 0x40;

  seq.buf[0].data = i2c_write_data;
  seq.buf[0].len  = 1;

  /* Select length of data to be read */
  seq.buf[1].data = data;
  seq.buf[1].len  = length;

  handle = (Si117xPortConfig_t *)si117x_handle;

  ret = I2CSPM_Transfer(handle->i2cPort, &seq);

  Si117xExitCriticalSection();

    if (ret != i2cTransferDone)
  {
    return((int) ret);
  }

  return((int) 0);
}
#endif

#if (SI117X_BUS == SI117X_SPI)
/**************************************************************************//**
 * @brief Write to Si117x SPI.
 * This is a blocking write.
 *****************************************************************************/
static int32_t Si117x_spi_write_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t data)
{
	uint8_t txBuffer[WRITE_BUFFER_LENGTH];
	uint8_t rxBuffer[WRITE_BUFFER_LENGTH];
	Ecode_t ret_code;

	txBuffer[0] = address & ~0x80;		// RWb = 0 for writes.
	txBuffer[1] = data;

  Si117xEnterCriticalSection();

	ret_code = SPIDRV_MTransferB(((Si117xPortConfig_t*) si117x_handle)->spiHandle, txBuffer, rxBuffer, WRITE_BUFFER_LENGTH);

  Si117xExitCriticalSection();

	return ret_code;
}

/**************************************************************************//**
 * @brief Read from Si117x SPI.
 * This is a blocking read.
 *****************************************************************************/
static int32_t Si117x_spi_read_byte_data(HANDLE si117x_handle, uint8_t address, uint8_t *data)
{
	uint8_t txBuffer[READ_BUFFER_LENGTH];
	uint8_t rxBuffer[READ_BUFFER_LENGTH];
	Ecode_t ret_code;

	txBuffer[0] = address | 0x80;		// RWb = 1 for reads
	txBuffer[1] = 0xff;

  Si117xEnterCriticalSection();

	ret_code = SPIDRV_MTransferB(((Si117xPortConfig_t*) si117x_handle)->spiHandle, txBuffer, rxBuffer, READ_BUFFER_LENGTH);

  Si117xExitCriticalSection();

	*data = rxBuffer[1];	// return data
	return ret_code;			// return error code
}


/**************************************************************************//**
 * @brief Read block of data from Si117x SPI.
 * This is a blocking read.
 *****************************************************************************/
static int32_t Si117x_spi_read_spi_block_data(HANDLE si117x_handle, uint8_t address, uint8_t length, uint8_t* data)
{
	uint8_t i;
	const uint16_t comm_length = length + 1;	// +1 to transmit the address byte
	uint8_t txBuffer[comm_length];
	uint8_t rxBuffer[comm_length];
	Ecode_t ret_code;

	txBuffer[0] = address | 0x80;		// RWb = 1 for reads
	rxBuffer[0] = 0xff;

	// Keep MOSI high for the remaining (non register address) bytes.
	for(i = 1; i < length; i++)
	{
		txBuffer[i] = 0xff;
	}

  Si117xEnterCriticalSection();

	ret_code = SPIDRV_MTransferB(((Si117xPortConfig_t*) si117x_handle)->spiHandle, txBuffer, rxBuffer, comm_length);

  Si117xExitCriticalSection();

	// Copy the receive payload (without the first byte) to the output buffer $data
	for(i = 0; i < length; i++)
	{
		data[i] = rxBuffer[i+1];
	}
	return ret_code;
}
#endif


/**************************************************************************//**
 * @brief Hardware implemented delay function. Does not need to be accurate.
 *****************************************************************************/
void delay_ms(int ms) {
  int i;
  for (i = 0; i < ms; i++) {
    UDELAY_Delay(1000);
  }
}

/**************************************************************************//**
 * @brief 10ms delay required by Si117x reset sequence.
 *****************************************************************************/
void delay_10ms(void)
{
  delay_ms(10);
}




