//////////////////////////////////////////////////////////////////
//
// App:			TWI I/O Expander TCA6416
// Author:		Milan Korinek
// Version:		1.0
// Notice:		Driver of I/O Expander TCA6416
//////////////////////////////////////////////////////////////////

#include "TWI_IO_Expander_TCA6416.h"

static freertos_twi_if _freertos_twi;
static ioexp_tca6416_registers_t _ioexp_regs_images;
static portTickType _max_block_time_ticks;

status_code_t freertos_io_expander_tca6416_init(uint32_t interrupt_priority, portTickType max_block_time_ticks)
{
	uint32_t ret;
	const freertos_peripheral_options_t blocking_driver_options = {
		NULL,											/* This peripheral does not need a receive buffer, so this parameter is just set to NULL. */
		0,												/* There is no Rx buffer, so the rx buffer size is not used. */
		interrupt_priority,								/* The priority used by the TWI interrupts. */
		TWI_I2C_MASTER,									/* Communicating with the EEPROM requires the TWI to be configured as an I2C master. */
		(USE_TX_ACCESS_MUTEX | USE_RX_ACCESS_MUTEX | WAIT_TX_COMPLETE | WAIT_RX_COMPLETE)	/* The blocking driver is to be used, so WAIT_TX_COMPLETE and WAIT_RX_COMPLETE are set. */
	};
	
	/* Initialize to default */
	_freertos_twi = NULL;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_INPUT_PORT_0] = 0x0U;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_INPUT_PORT_1] = 0x0U;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_OUTPUT_PORT_0] = 0xFFU;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_OUTPUT_PORT_1] = 0xFFU;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_POLARITY_INVERSION_PORT_0] = 0x0U;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_POLARITY_INVERSION_PORT_1] = 0x0U;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_CONFIGURATION_PORT_0] = 0xFFU;
	_ioexp_regs_images[IOEXP_TCA6416_CMD_CONFIGURATION_PORT_1] = 0xFFU;
	
	_max_block_time_ticks = max_block_time_ticks;
	
	/* Initialize the FreeRTOS driver for blocking operation.  The
	peripheral clock is configured in this function call. */
	_freertos_twi = freertos_twi_master_init(IOEXP_TWI_BASE, &blocking_driver_options);
	
	/* Check the port was initialized successfully. */
	if (_freertos_twi == NULL) return ERR_NO_MEMORY;
	
	/* Configure the TWI bus parameters.  Do this after calling
	freertos_twi_master_init(). */
	ret = twi_set_speed(IOEXP_TWI_BASE, IOEXP_TWI_CLOCK_HZ, sysclk_get_cpu_hz());
	
	/* Check the port was initialized successfully. */
	if (ret != PASS) return ERR_INVALID_ARG;
	
	return STATUS_OK;
}

status_code_t freertos_io_expander_tca6416_write(ioexp_tca6416_commands_t command, uint32_t data)
{
	status_code_t ret;
	twi_packet_t write_parameters;
	uint8_t buffer[2];
		
	/* Read only registers */
	if ((command == IOEXP_TCA6416_CMD_INPUT_PORT_0) || (command == IOEXP_TCA6416_CMD_INPUT_PORT_1)) {
		return ERR_INVALID_ARG;
	}
		
	/* Unknown register */
	if (command >= IOEXP_TCA6416_CMD_END_MARKER) {
		return ERR_INVALID_ARG;
	}
	
	buffer[0] = (uint8_t)data;
	buffer[1] = (uint8_t)(data >> 8);
	
	/* Data was already written in I/O expander. */
	if ((_ioexp_regs_images[command] == (((uint32_t)buffer[0]) & 0xFF)) &&
	    (_ioexp_regs_images[command + 1] == (((uint32_t)buffer[1]) &0xFF))) {
		return STATUS_OK;
	}

	write_parameters.chip = IOEXP_TWI_ADDR;
	write_parameters.addr[0] = (uint8_t)command;
	write_parameters.addr_length = 1;
	write_parameters.buffer = (void *)(buffer);
	write_parameters.length = 2;
	
	/* The blocking API is being used.  Other tasks will execute while the
	write operation is in progress. */
	ret = freertos_twi_write_packet(_freertos_twi, &write_parameters, _max_block_time_ticks);
	
	if (ret == STATUS_OK) {
		_ioexp_regs_images[command] = (uint32_t)buffer[0];
		_ioexp_regs_images[command + 1] = (uint32_t)buffer[1];
	}
		
	return ret;
}

status_code_t freertos_io_expander_tca6416_set(ioexp_tca6416_commands_t command, uint32_t data_masks,  uint32_t settings_mask)
{
	uint32_t dataTmp;
	status_code_t ret;
	/* Valid registers (always will be written two registers) */
	if (!((command == IOEXP_TCA6416_CMD_OUTPUT_PORT_0) || 
	    (command == IOEXP_TCA6416_CMD_CONFIGURATION_PORT_0) ||
		(command == IOEXP_TCA6416_CMD_POLARITY_INVERSION_PORT_0))) {
		return ERR_INVALID_ARG;
	}
	
	dataTmp = ((_ioexp_regs_images[command + 1] << 8) & 0xFF00UL) | _ioexp_regs_images[command]; /* get actual state of register 0 and 1 */
	dataTmp = dataTmp & ~data_masks;																	 /* clear bit position for writing new value */
	dataTmp = dataTmp | (data_masks & settings_mask);
	
	ret = freertos_io_expander_tca6416_write(command, dataTmp);
		
	return ret;
}

status_code_t freertos_io_expander_tca6416_read(ioexp_tca6416_commands_t command, uint32_t length, uint8_t read_data[])
{
	status_code_t ret;
	twi_packet_t read_parameters;
	uint32_t i;
	uint8_t read_data2[2];
	
	/* Unknown registers to read */
	if ((((uint32_t)command + length) >= IOEXP_TCA6416_CMD_END_MARKER) || (length <= 0) ){
		return ERR_INVALID_ARG;
	}
	
	read_parameters.chip = IOEXP_TWI_ADDR;
	read_parameters.addr[0] = (uint8_t)command;
	read_parameters.addr_length = 1;
	read_parameters.buffer = (read_data2);
	read_parameters.length = length;
	
	ret = freertos_twi_read_packet(_freertos_twi, &read_parameters,	_max_block_time_ticks);
	
	if (ret == STATUS_OK){
		for (i = (uint32_t)command; i < ((uint32_t)command + length); i++){
			_ioexp_regs_images[i] = ((uint32_t)read_data2[i] & 0xFF);
		}
	}
	
	return ret;
}

status_code_t freertos_io_expander_tca6416_get(ioexp_tca6416_commands_t command, uint32_t data_masks,  uint32_t * settings_mask)
{
	status_code_t ret;
	uint8_t read_buffer[2];
	
	/* Valid registers (always will be read two registers) */
	if (!((command == IOEXP_TCA6416_CMD_OUTPUT_PORT_0) || 
		(command == IOEXP_TCA6416_CMD_INPUT_PORT_0) ||
	    (command == IOEXP_TCA6416_CMD_CONFIGURATION_PORT_0) ||
		(command == IOEXP_TCA6416_CMD_POLARITY_INVERSION_PORT_0))) {
		return ERR_INVALID_ARG;
	}
	
	ret = freertos_io_expander_tca6416_read(command, 2, read_buffer);
	if (ret != STATUS_OK) return ret;
	
	*settings_mask = (((uint32_t)read_buffer[1] << 8) & 0xFF00UL) + ((uint32_t)read_buffer[0] & 0xFFUL);
	*settings_mask &= data_masks;
	
	return STATUS_OK;
}