/*
 ****************************************************************************
 * Copyright(c) 2014 NXP Semiconductors                                     *
 * All rights are reserved.                                                 *
 *                                                                          *
 * Software that is described herein is for illustrative purposes only.     *
 * This software is supplied "AS IS" without any warranties of any kind,    *
 * and NXP Semiconductors disclaims any and all warranties, express or      *
 * implied, including all implied warranties of merchantability,            *
 * fitness for a particular purpose and non-infringement of intellectual    *
 * property rights.  NXP Semiconductors assumes no responsibility           *
 * or liability for the use of the software, conveys no license or          *
 * rights under any patent, copyright, mask work right, or any other        *
 * intellectual property rights in or to any products. NXP Semiconductors   *
 * reserves the right to make changes in the software without notification. *
 * NXP Semiconductors also makes no representation or warranty that such    *
 * application will be suitable for the specified use without further       *
 * testing or modification.                                                 *
 *                                                                          *
 * Permission to use, copy, modify, and distribute this software and its    *
 * documentation is hereby granted, under NXP Semiconductors' relevant      *
 * copyrights in the software, without fee, provided that it is used in     *
 * conjunction with NXP Semiconductor products(UCODE I2C, NTAG I2C).        *
 * This  copyright, permission, and disclaimer notice must appear in all    *
 * copies of this code.                                                     *
 ****************************************************************************
 */
/***********************************************************************/
/* INCLUDES                                                            */
/***********************************************************************/
#include "HAL_timer_driver.h"
#include "ntag_driver_intern.h"

/***********************************************************************/
/* DEFINES                                                             */
/***********************************************************************/
#undef NTAG_DEVICE_LIST_BEGIN
#undef NTAG_DEVICE_ENTRY
#undef NTAG_DEVICE_LIST_END

#define NTAG_DEVICE_LIST_BEGIN                    struct NTAG_DEVICE ntag_device_list[NTAG_ID_MAX_DEVICES] = \
                                                  {
#ifdef HAVE_NTAG_INTERRUPT
#define NTAG_DEVICE_ENTRY(label, i2c_address, isr)     { NTAG_CLOSED, HAL_I2C_INVALID_HANDLE, i2c_address, isr, {0}, {0} }
#else
#define NTAG_DEVICE_ENTRY(label, i2c_address, isr)     { NTAG_CLOSED, HAL_I2C_INVALID_HANDLE, i2c_address, {0}, {0} }
#endif

#define NTAG_DEVICE_LIST_END                      };

/***********************************************************************/
/* GLOBAL VARIABLES                                                    */
/***********************************************************************/
/* second include of device list for generation of ntag_device_list array */
NTAG_DEVICE_LIST_BEGIN
#include "ntag_device_list.h"
NTAG_DEVICE_LIST_END/***********************************************************************/
/* GLOBAL PUBLIC FUNCTIONS                                             */
/***********************************************************************/

BOOL NTAG_WaitForFDPinState(NTAG_HANDLE_T ntag, NTAG_FD_STATE_T state,
		uint32_t timeout_ms) {

	switch (state) {
	case NTAG_FD_PIN_STATE_HI:
		HAL_ISR_RegisterCallback(ntag->isr, ISR_LEVEL_HI, NULL, NULL);
		return HAL_ISR_SleepWithTimeout(ntag->isr, timeout_ms);

	case NTAG_FD_PIN_STATE_LO:
		HAL_ISR_RegisterCallback(ntag->isr, ISR_LEVEL_LO, NULL, NULL);
		return HAL_ISR_SleepWithTimeout(ntag->isr, timeout_ms);
	default:
		return NTAG_ERROR_INVALID_PARAM;
	}
}

NTAG_HANDLE_T NTAG_InitDevice(NTAG_ID_T ntag_id, HAL_I2C_HANDLE_T i2cbus)
{
	if( ntag_id < NTAG_ID_MAX_DEVICES )
	{
		if( ntag_device_list[ntag_id].status == NTAG_CLOSED )
		{
			ntag_device_list[ntag_id].i2cbus = i2cbus;
			ntag_device_list[ntag_id].status = NTAG_OK;
			return &ntag_device_list[ntag_id];
		}
	}
	return NTAG_INVALID_HANDLE;
}

void NTAG_CloseDevice(NTAG_HANDLE_T ntag) {
	if (ntag) {
		ntag->i2cbus = HAL_I2C_INVALID_HANDLE;
		ntag->status = NTAG_CLOSED;
	}
}

BOOL NTAG_ReadBytes(NTAG_HANDLE_T ntag, uint16_t address, uint8_t *bytes,
		uint16_t len) {
	uint16_t bytes_read = 0;

	if (ntag->status == NTAG_CLOSED)
		return TRUE;

	ntag->status = NTAG_OK;

	while (bytes_read < len) {
		uint8_t current_block = (address + bytes_read) / NTAG_I2C_BLOCK_SIZE;
		uint8_t begin = (address + bytes_read) % NTAG_I2C_BLOCK_SIZE;
		uint8_t current_len = MIN(len - bytes_read,
				NTAG_I2C_BLOCK_SIZE - begin);

		if (current_len < NTAG_I2C_BLOCK_SIZE) {
			size_t i = 0;

			/* read block into ntag->rx_buffer only */
			if (NTAG_ReadBlock(ntag, current_block, NULL, 0))
				break;

			/* modify rx_buffer */
			for (i = 0; i < current_len; i++)
				bytes[bytes_read + i] = ntag->rx_buffer[RX_START + begin + i];
		} else {
			/* full block read */
			if (NTAG_ReadBlock(ntag, current_block, bytes + bytes_read,
			NTAG_I2C_BLOCK_SIZE))
				break;
		}

		bytes_read += current_len;
	}
	return ntag->status;
}

BOOL NTAG_WriteBytes(NTAG_HANDLE_T ntag, uint16_t address, const uint8_t *bytes,
		uint16_t len) {
	uint16_t bytes_written = 0;

	if (ntag->status == NTAG_CLOSED)
		return TRUE;

	ntag->status = NTAG_OK;

	while (bytes_written < len) {
		uint8_t current_block = (address + bytes_written) / NTAG_I2C_BLOCK_SIZE;
		uint8_t begin = (address + bytes_written) % NTAG_I2C_BLOCK_SIZE;
		uint8_t current_len = MIN(len - bytes_written,
				NTAG_I2C_BLOCK_SIZE - begin);

		if (current_len < NTAG_I2C_BLOCK_SIZE) {
			size_t i = 0;

			/* read block into ntag->rx_buffer only */
			if (NTAG_ReadBlock(ntag, current_block, NULL, 0))
				break;

			/* check if it is the first Block(0x00) and not the I2C Addr */
			/* be careful with writing of first byte in management block */
			/* the byte contains part of the serial number on read but   */
			/* on write the I2C address of the device can be modified    */
			if (0x00 == current_block && NTAG_MEM_ADRR_I2C_ADDRESS < begin)
				ntag->rx_buffer[RX_START + 0] = ntag->address;

			/* modify rx_buffer */
			for (i = 0; i < current_len; i++)
				ntag->rx_buffer[RX_START + begin + i] =
						bytes[bytes_written + i];

			/* writeback modified buffer */
			if (NTAG_WriteBlock(ntag, current_block, ntag->rx_buffer + RX_START,
			NTAG_I2C_BLOCK_SIZE))
				break;
		} else {
			/* full block write */
			if (NTAG_WriteBlock(ntag, current_block, bytes + bytes_written,
			NTAG_I2C_BLOCK_SIZE))
				break;
		}

		bytes_written += current_len;
	}

	return ntag->status;
}

BOOL NTAG_ReadRegister(NTAG_HANDLE_T ntag, uint8_t reg, uint8_t *val) {
	ntag->tx_buffer[TX_START + 0] = NTAG_MEM_BLOCK_SESSION_REGS;
	ntag->tx_buffer[TX_START + 1] = reg;

	/* send block number */
	if ( HAL_I2C_OK
			!= HAL_I2C_SendBytes(ntag->i2cbus, ntag->address, ntag->tx_buffer,
					2)) {
		ntag->status = NTAG_ERROR_TX_FAILED;
		return TRUE;
	}

	/* receive bytes */
	if ( HAL_I2C_OK
			!= HAL_I2C_RecvBytes(ntag->i2cbus, ntag->address, ntag->rx_buffer,
					1)) {
		ntag->status = NTAG_ERROR_RX_FAILED;
		return TRUE;
	}

	*val = ntag->rx_buffer[RX_START + 0];
	return FALSE;
}

BOOL NTAG_WriteRegister(NTAG_HANDLE_T ntag, uint8_t reg, uint8_t mask,
		uint8_t val) {
	ntag->tx_buffer[TX_START + 0] = NTAG_MEM_BLOCK_SESSION_REGS;
	ntag->tx_buffer[TX_START + 1] = reg;
	ntag->tx_buffer[TX_START + 2] = mask;
	ntag->tx_buffer[TX_START + 3] = val;

	if ( HAL_I2C_OK
			!= HAL_I2C_SendBytes(ntag->i2cbus, ntag->address, ntag->tx_buffer,
					4)) {
		ntag->status = NTAG_ERROR_TX_FAILED;
		return TRUE;
	}

	return FALSE;
}

BOOL NTAG_ReadConfiguration(NTAG_HANDLE_T ntag, uint8_t reg, uint8_t *val) {
#ifdef NTAG_2k
	uint8_t config = NTAG_MEM_BLOCK_CONFIGURATION_2k;
#elif NTAG_1k
	uint8_t config = NTAG_MEM_BLOCK_CONFIGURATION_1k;
#endif

	uint8_t I2C_Buf[NTAG_I2C_BLOCK_SIZE];
	if (NTAG_ReadBlock(ntag, config, I2C_Buf, NTAG_I2C_BLOCK_SIZE))
		return NTAG_ERR_COMMUNICATION;

	*val = I2C_Buf[reg];
	return NTAG_ERR_OK;
}

BOOL NTAG_WriteConfiguration(NTAG_HANDLE_T ntag, uint8_t reg, uint8_t mask,
		uint8_t val) {
#ifdef NTAG_2k
	uint8_t config = NTAG_MEM_BLOCK_CONFIGURATION_2k;
#elif NTAG_1k
	uint8_t config = NTAG_MEM_BLOCK_CONFIGURATION_1k;
#endif

	uint8_t I2C_Buf[NTAG_I2C_BLOCK_SIZE];
	if (NTAG_ReadBlock(ntag, config, I2C_Buf, NTAG_I2C_BLOCK_SIZE))
		return NTAG_ERR_COMMUNICATION;

	// Clear all other bits of the val
	val = val & mask;

	// Clear specific bit in the Buffer
	I2C_Buf[reg] = I2C_Buf[reg] & ~mask;

	// write bits in the Buffer
	I2C_Buf[reg] = I2C_Buf[reg] | val;

	if (NTAG_WriteBlock(ntag, config, I2C_Buf, NTAG_I2C_BLOCK_SIZE))
		return NTAG_ERR_COMMUNICATION;

	return NTAG_ERR_OK;
}

NTAG_STATUS_T NTAG_GetLastError(NTAG_HANDLE_T ntag) {
	return ntag->status;
}

/***********************************************************************/
/* GLOBAL PRIVATE FUNCTIONS                                            */
/***********************************************************************/
BOOL NTAG_ReadBlock(NTAG_HANDLE_T ntag, uint8_t block, uint8_t *bytes,
		uint8_t len) {
	size_t i = 0;

	ntag->tx_buffer[TX_START] = block;

	/* send block number */
	if ( HAL_I2C_OK
			!= HAL_I2C_SendBytes(ntag->i2cbus, ntag->address, ntag->tx_buffer,
					1)) {
		ntag->status = NTAG_ERROR_TX_FAILED;
		return TRUE;
	}

	/* receive bytes */
	if ( HAL_I2C_OK
			!= HAL_I2C_RecvBytes(ntag->i2cbus, ntag->address, ntag->rx_buffer,
					NTAG_I2C_BLOCK_SIZE)) {
		ntag->status = NTAG_ERROR_RX_FAILED;
		return TRUE;
	}

	len = MIN(len, NTAG_I2C_BLOCK_SIZE);

	/* write to bytes buffer */
	for (i = 0; i < len; i++)
		bytes[i] = ntag->rx_buffer[RX_START + i];

	return FALSE;
}

BOOL NTAG_WriteBlock(NTAG_HANDLE_T ntag, uint8_t block, const uint8_t *bytes,
		uint8_t len) {
	uint8_t ns_reg = 0;
	uint32_t timeout = NTAG_MAX_WRITE_DELAY_MS / 5 + 1;
	size_t i = 0;

	ntag->tx_buffer[TX_START] = block;

	len = MIN(len, NTAG_I2C_BLOCK_SIZE);

	/* copy len bytes */
	for (i = 0; i < len; i++)
		ntag->tx_buffer[TX_START + i + 1] = bytes[i];

	/* zero rest of the buffer */
	for (i = len; i < NTAG_I2C_BLOCK_SIZE; i++)
		ntag->tx_buffer[TX_START + i + 1] = 0;

	/* send block number */
	if ( HAL_I2C_OK
			!= HAL_I2C_SendBytes(ntag->i2cbus, ntag->address, ntag->tx_buffer,
					NTAG_I2C_BLOCK_SIZE+1)) {
		ntag->status = NTAG_ERROR_TX_FAILED;
		return TRUE;
	}

	/* do not wait for completion when writing SRAM */
	if (block >= NTAG_MEM_BLOCK_START_SRAM
			&& block < NTAG_MEM_BLOCK_START_SRAM + NTAG_MEM_SRAM_BLOCKS)
		return ntag->status;

	/* wait for completion */
	do {
		HAL_Timer_delay_ms(5);
		if (NTAG_ReadRegister(ntag, NTAG_MEM_OFFSET_NS_REG, &ns_reg))
			break;
		timeout--;
	} while (timeout && ns_reg & NTAG_NS_REG_MASK_EEPROM_WR_BUSY);

	if (0 == timeout)
		ntag->status = NTAG_ERROR_WRITE_TIMEOUT;

	return ntag->status;
}
