/***************************************************************************** 
* File Name:    fram.c 
*
* Date Created: 5/12/20
*
* Author:       Bob Theumer				 
*
* Description:  Software for interface to nonvolatile fram chip (25040A)
*               on EV2 Speedo & Tach gauges. FRAM is connected via SPI bus
*               and stores the gauge's meter readings.
*
* Function List:  
*                   Public
*                       spi_init()
*                       framInit()
*                       framReadDataByte()
*                       framWriteDataByte()
*                       framWriteDataPage()
*                   Private
*                       readFRAMStatusRegister()
*                       readByteFromMemory()
*                       sendByteToMemory()
*                       writeEnableSequence()
*                       void spi_event_handler()
*
* Notes:
*
* CSRCS Log
* $Log$
* Revision 1.0  2020-05-26 10:08:06-07  bobt
* Initial revision
*
*
* Copyright (c) 2020  ISSPRO Inc.
*****************************************************************************/
/* Include shared modules, which are used for whole project*/
//#include "PE_Types.h"
#include "AppTypes.h"
#include "assert.h"
#include "stdbool.h"
#include "nrf_drv_spi.h"

/* Include application files */
#include "fram.h"
#include "custom_board.h"



// Local Definitions
#define BYTE_MAX_VALUE  0x00FF  // 255
#define PAGE_MAX_BYTES  16
#define SPI_INSTANCE  0 /**< SPI instance index. */
static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0);  /**< SPI instance. */
static volatile bool spi_xfer_done;  /**< Flag used to indicate that SPI instance completed the transfer. */


// Private Functions
static uint8_t readFRAMStatusRegister(void);
static uint8_t readByteFromMemory(void);
static void sendByteToMemory(uint8_t data);
static void writeEnableSequence(void);
void spi_event_handler(nrf_drv_spi_evt_t const *p_event, void *p_context);

// Private Data
//TODO

/*****************************************************************************
*
* Function:     spi_init()
*
* Description:  Initializes the spi channel on nrf52832.
*                 
* Input:        None
*
* Output:       None
*
* Notes:        None
*
*****************************************************************************/ 
void spi_init()
{

    nrf_drv_spi_config_t spi_config = NRF_DRV_SPI_DEFAULT_CONFIG;
    spi_config.ss_pin   = SPI_SS_PIN;
    spi_config.miso_pin = SPI_MISO_PIN;
    spi_config.mosi_pin = SPI_MOSI_PIN;
    spi_config.sck_pin  = SPI_SCK_PIN;
    APP_ERROR_CHECK(nrf_drv_spi_init(&spi,&spi_config,spi_event_handler,NULL));

}
/*****************************************************************************
*
* Function:     framInit()
*
* Description:  Initializes the signals used to access the FRAM chip.
*                 
* Input:        None
*
* Output:       None
*
* Notes:        This assumes SPI port has been configured already.
*
*****************************************************************************/  

void framInit(void)
{
    nrf_gpio_pin_set(SPI_SS_PIN);            // Ensure active high chip select is high
}


/*****************************************************************************
*
* Function:     framReadDataByte()
*
* Description:  Function reads one byte from a defined FRAM address location.
*                                  
* Input:        uint16_t - address of FRAM location to be read from.
*                          Range: address can be between 0 thru 511 (0x1ff).
*
* Output:       uint8_t - data read from FRAM location
*                         Range: data can be between 0 thru 255 (0xff)
*
* Notes:        This function takes only about 65 uSec to complete. 
*               See Data sheet 8ICFM25040A Pages 7 & 8 for more details.
*
*****************************************************************************/

uint8_t framReadDataByte(uint16_t address)
{
    uint8_t cmd = FRAM_READ_MEM_CMD;
    uint8_t rxData;

    // Check if memory address is more than 8-bits.
    if (address > BYTE_MAX_VALUE)
    {
        // Yes, need to set the A8 bit in command.
        cmd |= FRAM_A8_BIT; 
    }

    nrf_gpio_pin_clear(SPI_SS_PIN);            // Activate chip select by clearing it

    // Read consists of its command first, then address byte (8-bits).
    sendByteToMemory(cmd);
    sendByteToMemory((uint8_t)(address & BYTE_MAX_VALUE));

    rxData = readByteFromMemory();
  	 
    nrf_gpio_pin_set(SPI_SS_PIN);            // Disable chip select by setting high
 
    return rxData; 
}


/*****************************************************************************
*
* Function:     framWriteDataByte()
*
* Description:  Function writes one byte to a defined FRAM address location.
*                                  
* Input:        uint16_t - address of FRAM location to be written to.
*                          Range: address can be between 0 thru 511 (0x1ff).
*               uint8_t - data to be written to the FRAM.
*                         Range: data can be between 0 thru 255 (0xff)
*
* Output:       None
*
* Notes:        This function takes only about 87 uSec to complete. 
*               See Data sheet 8ICFM25040A Pages 7 & 8 for more details.
*
*****************************************************************************/

void framWriteDataByte(uint16_t address, uint8_t data)
{
    uint8_t cmd = FRAM_WRITE_MEM_CMD;

    writeEnableSequence();      // Must set write enable latch first 
    
    nrf_gpio_pin_clear(SPI_SS_PIN);            // Activate chip select by clearing it
	 
    // Check if memory address is more than 8-bits.
    if (address > BYTE_MAX_VALUE)
    {
        // Yes, need to set the A8 bit in command.
        cmd |= FRAM_A8_BIT; 
    }

    // Write consists of its command first, then address byte (8-bits)
    // to write to, and then the data itself (8-bits).
    sendByteToMemory(cmd);
    sendByteToMemory((uint8_t)(address & BYTE_MAX_VALUE));
    sendByteToMemory(data);
    
    nrf_gpio_pin_set(SPI_SS_PIN);            // Disable chip select by setting high
}


/*****************************************************************************
*
* Function:     framWriteDataPage()
*
* Description:  Function to write up to 16 bytes (= 1 page) to FRAM. 
*                 
* Input:        uint16_t - starting address (must be page aligned). 
*               uint8_t* - pointer to the buffer that contains the bytes.
*               uint8_t - number of bytes to be written to a given page.
*                         Range: 1 thru 16 bytes (can't over flow page).
*
* Output:       None
*
* Notes:        This function takes about up to 410 uSec to complete (for
*               16 bytes). Since we only currently write 3 bytes at a time
*               (though we may combine trip meters into one write, TBD)
*               it only takes about 100 uSec which isn't too bad.
*
*               See Data sheet 8ICFM25040A Pages 7 & 8 for more details.
*
*****************************************************************************/  

void framWriteDataPage(uint16_t address, uint8_t *data, uint8_t numBytes)    	
{
    uint8_t cmd = FRAM_WRITE_MEM_CMD;
    uint8_t i;

    // Conduct boundry check for the page write.
    assert((address/PAGE_MAX_BYTES) == ((address+(numBytes - 1))/PAGE_MAX_BYTES));
 
    writeEnableSequence();      // Must set write enable latch first 
    
    nrf_gpio_pin_clear(SPI_SS_PIN);            // Activate chip select by clearing it
	 
    // Check if memory address is more than 8-bits.
    if (address > BYTE_MAX_VALUE)
    {
        // Yes, need to set the A8 bit in command.
        cmd |= FRAM_A8_BIT; 
    }

    // Write consists of its command first, then address byte (8-bits)
    // to write to, and then the data itself (we limit it to 16 bytes).
    sendByteToMemory(cmd);
    sendByteToMemory((uint8_t)(address & BYTE_MAX_VALUE));
    for (i = 0; i < numBytes; i++)
    {
        sendByteToMemory((uint8_t) *data++);
    }
    
    nrf_gpio_pin_set(SPI_SS_PIN);            // Disable chip select by setting high
}  


// Private Functions 
 
/*****************************************************************************
*
* Function:     readFRAMStatusRegister()
*
* Description:  Return actual status of FRAM chip at this moment in time.
*                                  
* Input:        None
*
* Output:       uint8_t     Bit 0 = Not used in FRAM (only EEPROMs).       
*                           Bit 1 = Write Enable Latch (WEL).
*                           Bit 2 = BP0 bit Array Protection
*                           Bit 3 = BP1 bit Array Protection
* 
* Notes:        See Data sheet 8ICFM25040A Page 6 for more details.
*
*****************************************************************************/

static uint8_t readFRAMStatusRegister(void)
{
    uint8_t rxData;

    nrf_gpio_pin_clear(SPI_SS_PIN);            // Activate chip select by clearing it

    sendByteToMemory(FRAM_READ_STATUS_CMD);
 
    rxData = readByteFromMemory();
  	   
    nrf_gpio_pin_set(SPI_SS_PIN);            // Disable chip select by setting high

    return rxData;
}


/*****************************************************************************
*
* Function:     readByteFromMemory()
*
* Description:  Return a byte read from the FRAM memory.
*                                  
* Input:        None
*
* Output:       uint8_t - Actual byte read from the FRAM chip.
*                         Range: data can be between 0 thru 255 (0xff)
*
* Notes:        Function assumes the caller function has already set
*               FRAM Chip Select signal to a Low level logic state.
*
*****************************************************************************/

static uint8_t readByteFromMemory(void)
{
    uint8_t rxData, result;
    uint8_t dummy = FRAM_WRITE_EN_LATCH_CMD;    // Ignored by FRAM
    
    bool empty = false;
  
    // Need 8 more clocks to read data so send out dummy message.
    result = nrf_drv_spi_transfer(&spi, &dummy, 1, NULL, 0);
    assert (result == true);

    // NOTE: do-while seems to work better than just a while?
    do
    {
        empty = &spi_xfer_done;

    } while (!empty);
     
    result = nrf_drv_spi_transfer(&spi,NULL,0,&rxData,1);
    assert (result == true);
  
    return rxData;	
}


/*****************************************************************************
*
* Function:     sendByteToMemory()
*
* Description:  Writes a byte to the FRAM memory.
*                                  
* Input:        uint8_t - Actual byte to be written to the FRAM chip.
*                         Range: data can be between 0 thru 255 (0xff)
*
* Output:       None
*
* Notes:        Function assumes the caller function has already set
*               FRAM Chip Select signal to a Low level logic state.
*
*****************************************************************************/

static void sendByteToMemory(uint8_t data)
{
    uint8_t result;
    
    bool empty = false;
 
    result = nrf_drv_spi_transfer(&spi,&data, 1, NULL, 0);
    assert (result == true);

    // NOTE: do-while seems to work better than just a while?
    do
    {
        empty = &spi_xfer_done;

    } while (!empty);

    // Read what the spi read while you transmit byte.
    result = nrf_drv_spi_transfer(&spi, NULL, 0, &data, 1);
    assert (result == true);
}


/*****************************************************************************
*
* Function:     writeEnableSequence()
*
* Description:  Sends Write Enable Latch (WREN) command to the FRAM. This 
*               is required prior to writing any data to it.
*                                  
* Input:        None
*
* Output:       None
*
* Notes:        Command must be sent singularly, ie between activation &
*               deactiviation of its chip select.
*
*****************************************************************************/

static void writeEnableSequence(void)
{
    nrf_gpio_pin_clear(SPI_SS_PIN);            // Activate chip select by clearing it

    sendByteToMemory(FRAM_WRITE_EN_LATCH_CMD);
 
    nrf_gpio_pin_set(SPI_SS_PIN);            // Disable chip select by setting high
}
/*****************************************************************************
*
* Function:     spi_event_handler()
*
* Description:  Determines when SPI transfer is done
*                                  
* Input:        None
*
* Output:       None
*
* Notes:        None
*
*****************************************************************************/

void spi_event_handler(nrf_drv_spi_evt_t const *p_event, void *p_context)
{
   spi_xfer_done = true;
}

