/***************************************************************************//**
 *   @file   SPIFRAM.c
 *   @brief  Gestion de la communication SPI avec la FRAM
 *   @author Kévin (Kevin.marchand@intelligence-electronique.com)
********************************************************************************
 * Copyright 2014(c) PLCD, Inc.
 *
 * Gestion de la communication SPI avec la FRAM
 *
 * Exemple d'utilisation :
 * - Ecriture :
 *      FRAMBeginWrite(0x0000);
 *      FRAMWriteArray((BYTE*)data, nbByte);
 *  Ecrit le nombre d'octets <i>nbByte</i> de <i>data</i> à l'adresse <i>0x0000</i>
 *
 * - Lecture :
 *      FRAMReadArray(0x0000, (BYTE*)readBuff, nbByte);
 *  Lit le nombre d'octets <i>nbByte</i> à l'adresse <i>0x0000</i> dans <i>readBuff</i>
 *
********************************************************************************
 *   SVN Revision: 1
*******************************************************************************/

#define __SPIFRAM_C

/******************************************************************************/
/***************************** Include Files **********************************/
/******************************************************************************/
#include "SPIFRAM.h"
#include "HardwareProfile.h"

// If the CS line is not defined, SPIFRAM.c's content will not be compiled.
#if defined(FRAM_CS_TRIS)

// IMPORTANT SPI NOTE: The code in this file expects that the SPI interrupt
//      flag (FRAM_SPI_IF) be clear at all times. If the SPI is shared with
//      other hardware, the other code should clear the FRAM_SPI_IF when it is
//      done using the SPI.

// SPI Serial FRAM buffer size. To enhance performance while
// cooperatively sharing the SPI bus with other peripherals, bytes
// read and written to the memory are locally buffered. Legal
// sizes are 1 to the FRAM page size.
#define FRAM_BUFFER_SIZE        (16)
//
#define FRAM_PAGE_SIZE		(16)

// FRAM SPI opcodes
#define OPCODE_READ    0x03    // Read data from memory array beginning at selected address
#define OPCODE_WRITE   0x02    // Write data to memory array beginning at selected address
#define OPCODE_WRDI    0x04    // Reset the write enable latch (disable write operations)
#define OPCODE_WREN    0x06    // Set the write enable latch (enable write operations)
#define OPCODE_RDSR    0x05    // Read Status register
#define OPCODE_WRSR    0x01    // Write Status register

#define FRAM_MAX_SPI_FREQ     (10000000ul)    // Hz

#define ClearSPIDoneFlag()
static inline __attribute__((__always_inline__)) void WaitForDataByte( void )
{
    while ((FRAM_SPISTATbits.SPITBF == 1) || (FRAM_SPISTATbits.SPIRBF == 0));
}

#define SPIFRAM_ON_BIT          (FRAM_SPISTATbits.SPIEN)

static void DoWrite(void);
static DWORD FRAMAddress;
static BYTE FRAMBuffer[FRAM_BUFFER_SIZE];
static BYTE vBytesInBuffer;

/******************************************************************************/
/************************** Interrupt Routines ********************************/
/******************************************************************************/
#define PROPERFRAM_SPICON1  (0x0003 | 0x0120)   /* 1:1 primary prescale, 8:1 secondary prescale, CKE=1, MASTER mode */

/***************************************************************************//**
 * @brief Initialize SPI module to communicate to serial FRAM.
 * @details Code sets SPI clock to Fosc/16.
 * @param none
 * @return none.
*******************************************************************************/
void FRAMInit(void)
{
    // Initialisation du chip select
    FRAM_CS_IO = 1;
    FRAM_CS_TRIS = 0;     

    ClearSPIDoneFlag();

    // Initialisation des registre du SPI
    FRAM_SPICON1 = PROPERFRAM_SPICON1;
    FRAM_SPICON2 = 0;
    FRAM_SPISTAT = 0;    // clear SPI
    FRAM_SPISTATbits.SPIEN = 1;  
}


/***************************************************************************//**
 * @brief Sets internal address counter to given address.
 * @details
 * @param address - Address at which read is to be performed.
 * @return FRAM_SUCCESS.
*******************************************************************************/
BOOL FRAMBeginRead(DWORD address)
{
    // Save the address and emptry the contents of our local buffer
    FRAMAddress = address;
    vBytesInBuffer = 0;
    return FRAM_SUCCESS;
}

/***************************************************************************//**
 * @brief Reads next byte from FRAM, internal address is incremented by one.
 * @details
 * @precond FRAMInit() && FRAMBeginRead() are already called.
 * @param address - Address at which read is to be performed.
 * @return BYTE that was read.
*******************************************************************************/
BYTE FRAMRead(void)
{
    // Check if no more bytes are left in our local buffer
    if(vBytesInBuffer == 0u)
    {
        // Get a new set of bytes
        FRAMReadArray(FRAMAddress, FRAMBuffer, FRAM_BUFFER_SIZE);
        FRAMAddress += FRAM_BUFFER_SIZE;
        vBytesInBuffer = FRAM_BUFFER_SIZE;
    }
    // Return a byte from our local buffer
    return FRAMBuffer[FRAM_BUFFER_SIZE - vBytesInBuffer--];
}

/***************************************************************************//**
 * @brief Reads desired number of bytes in sequential mode.
 * @details This function performs all necessary steps and releases the bus when finished.
 * @param address     - Address from where array is to be read
 * @param buffer      - Caller supplied buffer to hold the data
 * @param length      - Number of bytes to read.
 * @return FRAM_SUCCESS
*******************************************************************************/
BOOL FRAMReadArray(DWORD address, BYTE *buffer, WORD length)
{
    volatile BYTE Dummy;
    BYTE vSPIONSave;
    #if defined(__18CXX)
    BYTE SPICON1Save;
    #elif defined(__C30__)
    WORD SPICON1Save;
    #else
    DWORD SPICON1Save;
    #endif

    // Save SPI state (clock speed)
    SPICON1Save = FRAM_SPICON1;
    vSPIONSave = SPIFRAM_ON_BIT;

    // Configure SPI
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = PROPERFRAM_SPICON1;
    SPIFRAM_ON_BIT = 1;

    FRAM_CS_IO = 0;

    // Send READ opcode
    FRAM_SSPBUF = OPCODE_READ;
    WaitForDataByte();
    Dummy = FRAM_SSPBUF;

    // Send 16 bits address
    FRAM_SSPBUF = ((DWORD_VAL*)&address)->v[1];
    WaitForDataByte();
    Dummy = FRAM_SSPBUF;
    FRAM_SSPBUF = ((DWORD_VAL*)&address)->v[0];
    WaitForDataByte();
    Dummy = FRAM_SSPBUF;

    while(length--)
    {
        FRAM_SSPBUF = 0;
        WaitForDataByte();
        Dummy = FRAM_SSPBUF;
        if(buffer != NULL)
            *buffer++ = Dummy;
    };

    FRAM_CS_IO = 1;
    // Restore SPI state
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = SPICON1Save;
    SPIFRAM_ON_BIT = vSPIONSave;

    return FRAM_SUCCESS;
}


/***************************************************************************//**
 * @brief Modifies internal address counter of FRAM.
 * @details Unlike FRAMSetAddr() in xFRAM.c for I2C FRAM  memories, this function
 *          is used only for writing to the FRAM.
 *          Reads must use FRAMBeginRead(), FRAMRead().
 *          This function does not use the SPI bus.
 * @param address     - address to be set for writing
 * @return FRAM_SUCCESS
*******************************************************************************/
BOOL FRAMBeginWrite(DWORD address)
{
    vBytesInBuffer = 0;
    FRAMAddress = address;
    return FRAM_SUCCESS;
}

/***************************************************************************//**
 * @brief Writes a byte to the write cache, and if full, commits the write.
 * @details Also, if a write boundary is reached the write is committed.
 *          When finished writing, FRAMEndWrite() must be called to commit
 *          any unwritten bytes from the write cache.
 * @precond FRAMInit() && FRAMBeginWrite() are already called.
 * @param val - Byte to be written
 * @return FRAM_SUCCESS
*******************************************************************************/
BOOL FRAMWrite(BYTE val)
{
    FRAMBuffer[vBytesInBuffer++] = val;
    if(vBytesInBuffer >= sizeof(FRAMBuffer))
        DoWrite();
    else if((((BYTE)FRAMAddress + vBytesInBuffer) & (FRAM_PAGE_SIZE-1)) == 0u)
        DoWrite();

    return FRAM_SUCCESS;
}

/***************************************************************************//**
 * @brief Writes an array of bytes to the FRAM part.
 * @details This function writes an array of bytes to the FRAM at the address
 *          specified when FRAMBeginWrite() was called.  Page boundary crossing is
 *          handled internally.
 * @precond FRAMInit() was called once and FRAMBeginWrite() was called.
 * @param vData - The array to write to the next memory location
 * @param wLen - The length of the data to be written
 * @return None
*******************************************************************************/
void FRAMWriteArray(BYTE *val, WORD wLen)
{
    while(wLen--)
        FRAMWrite(*val++);
	
    FRAMEndWrite();
}

/***************************************************************************//**
 * @brief Commits any last uncommitted bytes in cache to physical storage.
 * @details Call this function when you no longer need to write any more bytes
 *          at the selected address.
 * @precond FRAMInit() && FRAMBeginWrite() are already called.
 * @return FRAM_SUCCESS
*******************************************************************************/
BOOL FRAMEndWrite(void)
{
    if(vBytesInBuffer)
	DoWrite();

    return FRAM_SUCCESS;
}

static void DoWrite(void)
{
    BYTE i;
    volatile BYTE vDummy;
    BYTE vSPIONSave;
    #if defined(__18CXX)
    BYTE SPICON1Save;
    #elif defined(__C30__)
    WORD SPICON1Save;
    #else
    DWORD SPICON1Save;
    #endif

    // Save SPI state
    SPICON1Save = FRAM_SPICON1;
    vSPIONSave = SPIFRAM_ON_BIT;

    // Configure SPI
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = PROPERFRAM_SPICON1;
    SPIFRAM_ON_BIT = 1;

    // Set the Write Enable latch
    FRAM_CS_IO = 0;
    FRAM_SSPBUF = OPCODE_WREN;
    WaitForDataByte();
    vDummy = FRAM_SSPBUF;
    FRAM_CS_IO = 1;

    // Send WRITE opcode
    FRAM_CS_IO = 0;
    FRAM_SSPBUF = OPCODE_WRITE;
    WaitForDataByte();
    vDummy = FRAM_SSPBUF;

    // Send 16 bits address
    FRAM_SSPBUF = ((DWORD_VAL*)&FRAMAddress)->v[1];
    WaitForDataByte();
    vDummy = FRAM_SSPBUF;
    FRAM_SSPBUF = ((DWORD_VAL*)&FRAMAddress)->v[0];
    WaitForDataByte();
    vDummy = FRAM_SSPBUF;

    for(i = 0; i < vBytesInBuffer; i++)
    {
        // Send the byte to write
        FRAM_SSPBUF = FRAMBuffer[i];
        WaitForDataByte();
        vDummy = FRAM_SSPBUF;
    }

    // Begin the write
    FRAM_CS_IO = 1;

    // Update write address and clear write cache
    FRAMAddress += vBytesInBuffer;
    vBytesInBuffer = 0;

    // Restore SPI State
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = SPICON1Save;
    SPIFRAM_ON_BIT = vSPIONSave;


    // Wait for write to complete
    //while( FRAMIsBusy() );
}

/***************************************************************************//**
 * @brief Reads the status register
 * @details
 * @precond FRAMInit() is already called.
 * @return FALSE if FRAM is not busy, TRUE if FRAM is busy
*******************************************************************************/
BOOL FRAMIsBusy(void)
{
    volatile BYTE_VAL result;
    BYTE vSPIONSave;
    #if defined(__18CXX)
    BYTE SPICON1Save;
    #elif defined(__C30__)
    WORD SPICON1Save;
    #else
    DWORD SPICON1Save;
    #endif

    // Save SPI state
    SPICON1Save = FRAM_SPICON1;
    vSPIONSave = SPIFRAM_ON_BIT;

    // Configure SPI
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = PROPERFRAM_SPICON1;
    SPIFRAM_ON_BIT = 1;

    FRAM_CS_IO = 0;
    // Send RDSR - Read Status Register opcode
    FRAM_SSPBUF = OPCODE_RDSR;
    WaitForDataByte();
    result.Val = FRAM_SSPBUF;

    // Get register contents
    FRAM_SSPBUF = 0;
    WaitForDataByte();
    result.Val = FRAM_SSPBUF;
    FRAM_CS_IO = 1;

    // Restore SPI State
    SPIFRAM_ON_BIT = 0;
    FRAM_SPICON1 = SPICON1Save;
    SPIFRAM_ON_BIT = vSPIONSave;

    return result.bits.b0;
}


#endif //#if defined(FRAM_CS_TRIS)
