/*
 * File name : at24c01d.c
 * Function  : Implements drivers for the AT24C01D EEPROM.
 * Created   : 22 Apr 2020
 * Author    : jm
 */

#include <stdio.h>

#include "at24c01d.h"   // include this first. It must not depend on any other header.

#include "fsl_lpi2c.h"

//************************************************************************
//
// DEFINITIONS AND MACROS
//
//************************************************************************

#define LPI2C_MASTER_BASE	(LPI2C2_BASE)
#define LPI2C_MASTER		((LPI2C_Type *)LPI2C_MASTER_BASE)

//#define LPI2C_ADDR_8BIT	0xA0
#define LPI2C_ADDR_7BIT		0x50    // 0xA0 >> 1
#define LPI2C_BAUDRATE		100000U

#define PAGEBITS			3
#define	PAGESIZE			(1 << 3)
#define	PAGEMASK			(PAGESIZE - 1)

#define RX_TIMEOUT			100000000
#define TX_TIMEOUT			100000000

#define TURN_AUTOSTOP_OFF()	LPI2C_MASTER->MCFGR1 &= ~LPI2C_MCFGR1_AUTOSTOP_MASK
#define TURN_AUTOSTOP_ON()	LPI2C_MASTER->MCFGR1 |=  LPI2C_MCFGR1_AUTOSTOP_MASK

#define MEMORY_SIZE_BYTES	AT24C01D_SIZE_BYTES

//************************************************************************
//
// TYPEDEFS, ENUMS, UNIONS AND STRUCTURES
//
//************************************************************************


//************************************************************************
//
// LOCAL PROTOTYPES
//
//************************************************************************

static int SendReadPreamble(uint32_t uiTimeout);
static int SendWritePreamble(uint32_t uiTimeout);
static int WaitForTXFifoToEmpty(uint32_t uiTimeout);

//************************************************************************
//
// LOCAL VARIABLES AND CONSTS
//
//************************************************************************

//************************************************************************
//
// EXPORTED FUNCTIONS
//
//************************************************************************

/*
 * @brief Read contents of EEPROM.
 * @param[in]  address		Start address (in EEPROM memory space)
 * @param[out] rxBuff		Destination buffer
 * @param[in]  rxSize		Number of bytes to read
 * @return Number of bytes read from device.
 *
 * Read contents of EEPROM. The length returned is normally as requested,
 * but will be truncated if the requested range goes beyond end of device.
 */
int32_t At24c01d_Read(uint32_t address, uint8_t *rxBuff, uint32_t rxSize )
{
    uint8_t readStartAddress;
    status_t result;
    uint32_t uiBytesRead;
    uint32_t uiNumBytes;

    if (address >= MEMORY_SIZE_BYTES)
    {
    	return -1;
    }

    // Do not allow wrap around if address goes beyond end of memory.
    if (address + rxSize > MEMORY_SIZE_BYTES)
    {
        rxSize = MEMORY_SIZE_BYTES - address;  // return truncated range.
    }

    // Check if the bus is already in use (by atecc608a security chip)
    // TODO - perhaps control access with a semaphore?
    while ((result = LPI2C_CheckForBusyBus(LPI2C_MASTER)) != kStatus_Success)
    {
        ;   // TODO - or wait
    }

    // Clear all error flags
    LPI2C_MASTER->MSR = (uint32_t)0x7f00;

    for (uiBytesRead = 0; uiBytesRead < rxSize; uiBytesRead += uiNumBytes)
    {
    	// Only try to read from current address up to the next page boundary
    	uiNumBytes = PAGESIZE - ((address + uiBytesRead) & PAGEMASK);

    	// ...but don't read more bytes than we have left to read
    	if (uiNumBytes > rxSize - uiBytesRead)
    	{
    		uiNumBytes = rxSize - uiBytesRead;
    	}

		TURN_AUTOSTOP_OFF();

		result = SendWritePreamble(RX_TIMEOUT);
		if (result != kStatus_Success)
		{
			return -1;
		}

		TURN_AUTOSTOP_ON();

		// Now send the read start address.
		readStartAddress = (uint8_t) (address + uiBytesRead);
		result = LPI2C_MasterSend(LPI2C_MASTER, &readStartAddress, 1);
		if (result != kStatus_Success)
		{
			return -1;
		}

		TURN_AUTOSTOP_OFF();

		result = SendReadPreamble(RX_TIMEOUT);
		if (result != kStatus_Success)
		{
			return -1;
		}

		// Let the device return data until we stop it with a NAK after the required bytes have been received.
		result = LPI2C_MasterReceive(LPI2C_MASTER, &rxBuff[uiBytesRead], uiNumBytes);
		if (result != kStatus_Success)
		{
			return -1;
		}

		// Done.
		result = LPI2C_MasterStop(LPI2C_MASTER);
		if (result != kStatus_Success)
		{
			return -1;
		}
    }

    return uiBytesRead;
}

/*
 * @brief Write data out to EEPROM.
 * @param[in]  address		Start address (in EEPROM memory space)
 * @param[in]  txBuff		Source buffer
 * @param[in]  rxSize		Number of bytes to write
 * @return Number of bytes written to device.
 *
 * Write data block to EEPROM. The length returned is normally as requested,
 * but will be truncated if the requested range goes beyond end of device.
 */
int32_t At24c01d_Write(uint32_t address, uint8_t *txBuff, uint32_t txSize )
{
    uint8_t writeStartAddress;
    status_t result;
    uint32_t uiBytesSent;
    uint32_t uiNumBytes;

    if (address >= MEMORY_SIZE_BYTES)
    {
    	return -1;
    }

    // Do not allow wrap around if address goes beyond end of memory.
    if (address + txSize > MEMORY_SIZE_BYTES)
    {
        txSize = MEMORY_SIZE_BYTES - address;  // return truncated range.
    }

    // Check if the bus is already in use (by atecc608a security chip) TODO - perhaps control access with a semaphore?
    result = LPI2C_CheckForBusyBus(LPI2C_MASTER);
    if (result != kStatus_Success)
    {
        return -2;
    }

    // Clear all error flags
    LPI2C_MASTER->MSR = (uint32_t)0x7f00;

    for (uiBytesSent = 0; uiBytesSent < txSize; uiBytesSent += uiNumBytes)
    {
        printf("Sent %d of %d\r\n", uiBytesSent, txSize);
    	// Only try to write from current address up to the next page boundary
    	uiNumBytes = PAGESIZE - ((address + uiBytesSent) & PAGEMASK);

    	// ...but don't write more bytes than we have left to write
    	if (uiNumBytes > txSize - uiBytesSent)
    	{
    		uiNumBytes = txSize - uiBytesSent;
    	}

        TURN_AUTOSTOP_OFF();

		// Send write preamble
		result = SendWritePreamble(TX_TIMEOUT);
		if (result != kStatus_Success)
		{
			return -3;
		}

        TURN_AUTOSTOP_OFF();

		// Set and send the write start address.
		writeStartAddress = (uint8_t) (address + uiBytesSent);
		result = LPI2C_MasterSend(LPI2C_MASTER, &writeStartAddress, sizeof(writeStartAddress));
		if (result != kStatus_Success)
		{
			return -4;
		}

        TURN_AUTOSTOP_OFF();

		// Send the data.
        // If the previous data is still being written to the EEPROM (tWR <= 5ms),
        //   this call may return with a kStatus_LPI2C_Nak result value
		result = LPI2C_MasterSend(LPI2C_MASTER, &txBuff[uiBytesSent], uiNumBytes);
		if (result != kStatus_Success)
		{
			return -5;	// Some other non-NAK condition occurred
		}

		result = WaitForTXFifoToEmpty(TX_TIMEOUT);
		if (result != kStatus_Success)
		{
		    printf("ERROR: written %d of %d\r\n", uiBytesSent, txSize);
			return -6;
		}

		// Done.
		result = LPI2C_MasterStop(LPI2C_MASTER);
		if (result != kStatus_Success)
		{
			return -7;
		}

		if (uiBytesSent + uiNumBytes < txSize)
		{
			// We still have more to write, so wait until current write completes
			// According to the AT24C01D datasheet, tWR = 5msec
			// FIXME: make sure this works on both Debug & Release builds
			volatile uint32_t i = 0;
			for (i = 0; i < 10000000; ++i)
			{
				__asm("NOP"); /* delay */
			}
		}
    }

    return uiBytesSent;
}

//************************************************************************
//
// LOCAL FUNCTIONS
//
//************************************************************************

/*
 * @brief Wait for TX FIFO to empty
 * @param[in]  uiTimeout		Number of times to loop round while waiting
 * @retval kStatus_Success		All was OK
 * @retval kStatus_Timeout		Operation timed out before FIFO emptied
 *
 * Wait for LPI2C2's TX FIFO to empty.
 */
static int WaitForTXFifoToEmpty(uint32_t uiTimeout)
{
	size_t txCount;
	do
	{
		if (uiTimeout && (--uiTimeout == 0U))
		{
			return kStatus_Timeout;
		}
		LPI2C_MasterGetFifoCounts(LPI2C_MASTER, NULL, &txCount);
	} while (txCount);
	return kStatus_Success;
}

/*
 * @brief Send an I2C read preamble
 * @param[in]  uiTimeout		Number of times to loop round while waiting
 * @retval kStatus_Success		All was OK
 * @retval kStatus_Timeout		Operation timed out before FIFO emptied
 * @retval kStatus_Fail			Slave failed to send ACK signal
 * @retval kStatus_LPI2C_Busy	Another master is currently utilizing the bus
 *
 * Send an I2C read preamble.
 */
static int SendReadPreamble(uint32_t uiTimeout)
{
	int result;
	int flags;

    // Start the reading with start bit.
    result = LPI2C_MasterStart(LPI2C_MASTER, LPI2C_ADDR_7BIT, kLPI2C_Read);
    if (result != kStatus_Success)
    {
        return result;
    }

    // Check that the start bit and the slave address byte have completed sending.
    result = WaitForTXFifoToEmpty(RX_TIMEOUT);
    if (result != kStatus_Success)
    {
        return result;
    }

    // Check that slave has responded with an ACK.
    flags = LPI2C_MasterGetStatusFlags(LPI2C_MASTER);
    if (flags & kLPI2C_MasterNackDetectFlag)
    {
        return kStatus_Fail;
    }

    return kStatus_Success;
}

/*
 * @brief Send an I2C write preamble
 * @param[in]  uiTimeout		Number of times to loop round while waiting
 * @retval kStatus_Success		All was OK
 * @retval kStatus_Timeout		Operation timed out before FIFO emptied
 * @retval kStatus_Fail			Slave failed to send ACK signal
 * @retval kStatus_LPI2C_Busy	Another master is currently utilizing the bus
 *
 * Send an I2C write preamble.
 */
static int SendWritePreamble(uint32_t uiTimeout)
{
	int result;
    uint32_t flags;

    // First a start bit and the slave device address.
    result = LPI2C_MasterStart(LPI2C_MASTER, LPI2C_ADDR_7BIT, kLPI2C_Write);
    if (result != kStatus_Success)
    {
        return result;
    }

    result = WaitForTXFifoToEmpty(RX_TIMEOUT);
    if (result != kStatus_Success)
    {
        return result;
    }

    // Check that slave has responded with an ACK.
    flags = LPI2C_MasterGetStatusFlags(LPI2C_MASTER);
    if (flags & kLPI2C_MasterNackDetectFlag)
    {
        return kStatus_Fail;
    }

    return kStatus_Success;
}

// EOF
