/*************************************************************************//**
 * @file 
 * @brief Si117x reusable functions 
 * @version 16.4.28
 *****************************************************************************
 * @section License
 * <b>(C) Copyright 2016 Silicon Labs, http://www.silabs.com</b>
 ******************************************************************************
 * 
 * This file is licensed under the Silabs License Agreement. See the file
 * "Silabs_License_Agreement.txt" for details. Before using this software for
 * any purpose, you must agree to the terms of that agreement.
 *
 *****************************************************************************/
#include "si117x_functions.h"

/*****************************************************************************/
/**************    Compile Switches   ****************************************/
/*****************************************************************************/

/***************************************************************************//**
 * @brief 
 *   Resets the Si117x, clears any interrupts and initializes the HW_KEY 
 *   register.
 * @param[in] si117x_handle 
 *   The programmer's toolkit handle
 * @retval  0       
 *   Success
 * @retval  <0      
 *   Error
 ******************************************************************************/
int16_t Si117xReset(HANDLE si117x_handle)
{
  int16_t retval = 0;

  //
  // Do not access the Si117x earlier than 25 ms from power-up. 
  // Uncomment the following lines if Si117xReset() is the first
  // instruction encountered, and if your system MCU boots up too 
  // quickly. 
  //
  delay_10ms();
  delay_10ms();
  delay_10ms();

  // Perform the Reset Command
  retval += Si117xWriteToRegister(si117x_handle, REG_COMMAND, CMD_RESET_SW);

  // Delay for 10 ms. This delay is needed to allow the Si117x
  // to perform internal reset sequence. 
  delay_10ms();
  delay_10ms();
  delay_10ms();

  // Initialize HW_KEY
  retval += Si117xWriteToRegister(si117x_handle, REG_HW_KEY, 0x00);

  return retval;
}

/***************************************************************************//**
 * @brief 
 *   Helper function to send a command to the Si117x
 ******************************************************************************/
int16_t _sendCmd(HANDLE si117x_handle, uint8_t command) {
  int16_t response_before = 0, response_after = 0;
  int16_t retval;
  uint8_t count = 0;
  const uint8_t max_count = 5;

  count = 0;
  while (count < max_count) {
    retval = Si117xReadFromRegister(si117x_handle, REG_RESPONSE0);
    if (retval < 0) {
      return retval;  // i2c error occurred
    } else if ((retval & RSP0_COUNTER_ERR) == RSP0_COUNTER_ERR) {
      // CMD_ERR flag was set. Typically caused by a previous invalid command code or read/write to invalid parameter address
      // return the command error code * -1
      return -1;
    } else if ((retval & RSP0_COUNTER_MASK) == response_before && count > 0) {
      // cmd_ctr is stable, okay to execute command
      break;
    }
    response_before = retval & RSP0_COUNTER_MASK;
    count++;
  }

  // Return an error if command counter is not stable.
  // This typically indicates a communication error.
  if (count >= max_count) {
    return -1;
  }

  // Send the command.
  retval = Si117xWriteToRegister(si117x_handle, REG_COMMAND, command);
  if (retval != 0) {
    return retval;
  }

  if (command == 0x1C)
  {
  	delay_10ms();
  }

  // Wait for the change in the command counter, confirming command execution.
  if (command != CMD_RESET_CMD_CTR && command != CMD_RESET_SW && command != CMD_SET_I2C_ADDR) {
    count = 0;
    while (count < max_count) {
      response_after = Si117xReadFromRegister(si117x_handle, REG_RESPONSE0);
      if (response_after < 0) {
        return response_after;  // i2c error occurred
      } else if ((response_after & RSP0_COUNTER_ERR) == RSP0_COUNTER_ERR) {
        // CMD_ERR flag was set. Typically caused by invalid command code or read/write to invalid parameter address
        // return the command error code * -1
        return -1;
      } else if ((response_after & RSP0_COUNTER_MASK) == ((response_before + 1) & RSP0_COUNTER_MASK)) {
        break;
      }
      count++;
    }

    // Return an error if command counter did not increment after max_count retries.
    // This typically indicates a communication error.
    if (count >= max_count) {
      return -1;
    }
  }

  // Command was sent and executed correctly.
  return 0;
}

/***************************************************************************//**
 * @brief 
 *   Sends a NOP command to the Si117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @retval  0       Success
 * @retval  <0      Error
 ******************************************************************************/
int16_t Si117xNop(HANDLE si117x_handle)
{
  return _sendCmd(si117x_handle,0x00);
}


/***************************************************************************//**
 * @brief
 *   Sends the UPDATE_FIFO_COUNT command and returns the current FIFO count
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @param[in] rev_id
 *   The contents REV_ID register of the Si117x. 2 == -B2, 3 == -B3
 *   The resulting FIFO count
 * @retval  >= 0    the FIFO count
 * @retval  < 0      Error
 ******************************************************************************/
int16_t Si117xUpdateFifoCount(HANDLE si117x_handle, uint8_t rev_id)
{
  int16_t retval;
	int16_t high, low, high_check;
	if (rev_id == 2) {
	  retval = _sendCmd(si117x_handle,CMD_UPDATE_FIFO_COUNT);

	  if (retval < 0) {
	    return retval;
	  }

	  high = Si117xReadFromRegister(si117x_handle, REG_FIFO_COUNT_H);
	  low =  Si117xReadFromRegister(si117x_handle, REG_FIFO_COUNT_L);
	}
	else {
    // -B3, no command counter update
    // re-read FIFO counter if there is a rollover of the counter during reading of the multibyte counter
    do
    {
      high = Si117xReadFromRegister(si117x_handle, REG_FIFO_COUNT_H);
      low = Si117xReadFromRegister(si117x_handle, REG_FIFO_COUNT_L);
      high_check = Si117xReadFromRegister(si117x_handle, REG_FIFO_COUNT_H);

      // return error if i2c error occurs
      if (high < 0 || low < 0 || high_check < 0)
        return -1;
    } while( high != high_check);
	}

	retval = (high << 8) + low;
  return retval;
}
/***************************************************************************//**
 * @brief 
 *   Sets up a new I2C address to the Si117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @param[in] value   
 *   The New I2C address
 * @retval  0       
 *   Success
 * @retval  < 0
 *   Error
 ******************************************************************************/
int16_t Si117xSetI2CAddress(HANDLE si117x_handle, uint8_t value)
{
  int16_t retval;

  retval=Si117xWriteToRegister(si117x_handle, REG_HOSTIN0, value);
  retval += _sendCmd(si117x_handle, CMD_SET_I2C_ADDR);
  return retval;
}

/***************************************************************************//**
 * @brief 
 *   Sends a START command to the Si117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @retval  0       
 *   Success
 * @retval  <0      
 *   Error
 ******************************************************************************/
int16_t Si117xStart(HANDLE si117x_handle)
{
  return _sendCmd(si117x_handle, CMD_START);
}

/***************************************************************************//**
 * @brief 
 *   Sends a STOP command to the Si117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @retval  0       
 *   Success
 * @retval  <0      
 *   Error
 ******************************************************************************/
int16_t Si117xStop(HANDLE si117x_handle)
{
  return _sendCmd(si117x_handle, CMD_STOP);
}

/***************************************************************************//**
 * @brief 
 *   Sends a FLUSH_FIFO command to the Si117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @retval  0       
 *   Success
 * @retval  <0      
 *   Error
 ******************************************************************************/
int16_t Si117xFlushFIFO(HANDLE si117x_handle)
{
  Si117xWriteToRegister(si117x_handle, REG_FIFO_FLUSH, 1);
  delay_10ms();
  Si117xWriteToRegister(si117x_handle, REG_FIFO_FLUSH, 0);

  return _sendCmd(si117x_handle, CMD_FLUSH_FIFO);
}

/***************************************************************************//**
 * @brief 
 *   Inserts a shielded synch message in FIFO
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @param[in] value   
 *   The byte value to be inserted in FIFO
 * @retval  0       
 *   Success
 * @retval  <0      
 *   Error
 ******************************************************************************/
int16_t Si117xInsertSynchMsg(HANDLE si117x_handle, uint8_t value)
{
  int16_t retval;

	retval = Si117xWriteToRegister(si117x_handle, REG_HOSTIN0, value);
	retval += _sendCmd(si117x_handle, CMD_INSERT_SYNCH_MSG);
	return retval;
}


/***************************************************************************//**
 * @brief 
 *   Reloads a new I2C address to the PPG slave based on MISO pin status
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @param[in] value
 *   The New I2C address
 * @retval  0
 *   Success
 * @retval  <0
 *   Error
 ******************************************************************************/
int16_t Si117xReloadI2CAddress(HANDLE si117x_handle, uint8_t value)
{
  int16_t retval;

	retval = Si117xWriteToRegister(si117x_handle, REG_HOSTIN0, value);
	retval += _sendCmd(si117x_handle, CMD_RELOAD_I2C_ADDR);	// CMD_REALOAD_I2C_ADDR
	return retval;
}

/***************************************************************************//**
 * @brief
 *   Sets the three wire SPI mode
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @retval  0
 *   Success
 * @retval  <0
 *   Error
 ******************************************************************************/
int16_t Si117xSetThreeWireSPI(HANDLE si117x_handle)
{
  return _sendCmd(si117x_handle, CMD_SET_THREE_WIRE_SPI);
}

/***************************************************************************//**
 * @brief
 *   Reads a Parameter from the Si117x
 * @param[in] si117x_handle 
 *   The programmer's toolkit handle
 * @param[in] address   
 *   The address of the parameter. 
 * @retval <0       
 *   Error
 * @retval 0-255    
 *   Parameter contents
 ******************************************************************************/
int16_t Si117xParamRead(HANDLE si117x_handle, uint8_t address)
{
  // returns Parameter[address]
  int16_t retval;
  uint8_t cmd = CMD_PARAM_QUERY_BYTE_MASK + (address & 0x3F);

  retval= _sendCmd(si117x_handle, cmd);
  if( retval != 0 ) {
    return retval;
  }

  retval = Si117xReadFromRegister(si117x_handle, REG_RESPONSE1);
  return retval;
}

/***************************************************************************//**
 * @brief 
 *   Writes a byte to an Si117x Parameter
 * @param[in] si117x_handle 
 *   The programmer's toolkit handle
 * @param[in] address 
 *   The parameter address 
 * @param[in] value   
 *   The byte value to be written to the Si117x parameter
 * @retval 0    
 *   Success
 * @retval <0   
 *   Error
 * @note This function ensures that command completion is checked.
 * If setting parameter is not done properly, no measurements will
 * occur. This is the most common error. It is highly recommended
 * that host code make use of this function.
 ******************************************************************************/
int16_t Si117xParamSet(HANDLE si117x_handle, uint8_t address, uint8_t value)
{
  int16_t retval;
  uint8_t buffer[2];

  buffer[0]= value;						// parameter value
  buffer[1]= CMD_PARAM_SET_BYTE_MASK + (address & 0x3F);	// param_write command

  retval = Si117xWriteToRegister(si117x_handle, REG_HOSTIN0, buffer[0]);
  retval += _sendCmd(si117x_handle, buffer[1]);
  return retval;
}


/***************************************************************************//**
 * @brief
 *   Reads and clears an interrupt from the 117x
 * @param[in] si117x_handle
 *   The programmer's toolkit handle
 * @param[in] rev_id
 *   The device revision. Determines the interrupt method
 * . 0 = -B3 (read irq_status to clear), 1 = -B2 (send CMD_CLEAR_IRQ_STATUS)
 * @retval <0
 *   Error
 * @retval 0-255
 *   irq_status - which interrupt sources caused the interrupt
 ******************************************************************************/
int16_t Si117xGetIrqStatus(HANDLE si117x_handle, uint8_t rev_id)
{
	// returns Parameter[address]
	int16_t retval;
	uint8_t irq_status;

	if( rev_id == 2 )
	{
		// -B2, command based approach
		irq_status = Si117xReadFromRegister(si117x_handle, REG_IRQ_STATUS_B2);
		if( irq_status )
		{
			retval=Si117xWriteToRegister(si117x_handle, REG_HOSTIN0, irq_status);
			retval += _sendCmd(si117x_handle, CMD_CLEAR_IRQ_STATUS);
		}
	}
	else
	{
		// -B3, hardware i2c reg approach, push-pull mode
		irq_status = Si117xReadFromRegister(si117x_handle, REG_IRQ_STATUS);
	}

	return irq_status;
}
