/****************************************************************************/
/* Created 2009 (Anno Domini)                                               */
/****************************************************************************/
/* MBARI SeeStar (See http://www.mbari.org/seestar/) is licensed under a    */
/* Creative Commons Attribution-ShareAlike 4.0 International License.       */
/*                                                                          */
/* You should have received a copy of the license along with this work. If  */
/* not, see <http://creativecommons.org/licenses/by-sa/4.0/>.               */
/*                                                                          */
/* Citation: F. Cazenave, C. Kecy, M. Risi, S.H.D. Haddock (in press)       */
/*    "SeeStar: a low-cost, modular, and open-source camera system          */
/*    for subsea observations", IEEE Oceans 2014                            */
/*                                                                          */
/****************************************************************************/
/* Portions of flash_mem.c were derived from Microchip application          */
/* note "AN1157 - A Serial Bootloader for PIC24F Devices"                   */ 
/****************************************************************************/

#include "flash_mem.h"
#include <GenericTypeDefs.h>

/* for debugging with sprintf and serInstPutString */
#include <stdio.h>
#include "serial.h"
/* for debugging with sprintf and serInstPutString */

#define PARAM_PAGE      0x1000
#define MAX_PAGE_BYTES  0x400

#define FLASH_MEM_DEBUG 0

int __attribute__((space(prog), section(".params"), address(PARAM_PAGE))) paramBuff[512];

/* local functions for reading and writing Flash memory */
static void WriteMem();
static void WriteLatch(WORD page, WORD addrLo, WORD dataHi, WORD dataLo);
static void Erase(WORD page, WORD addrLo);
static DWORD ReadLatch(WORD page, WORD addrLo);

int memRead(char* buff, int size)
{
    int total_words, i, bytes_read;
    DWORD_VAL address;
    DWORD_VAL data;

#if FLASH_MEM_DEBUG 
char debug[64];
#endif
    
    /* if size is too big or too small bailout with error */
    if ( (size < 1) || (size > MAX_PAGE_BYTES) )
        return -1;

    /* calculate total words to read */
    total_words = ((size - 1) / 2) + 1;

    /* clear bytes_read variable */
    bytes_read = 0;
        
    for (i = 0; i < total_words; ++i)
    {
        /* assign address of word to read */
        address.Val = PARAM_PAGE + (2 * i);
        
        /* read the word */
        data.Val = ReadLatch(address.word.HW, address.word.LW);

#if FLASH_MEM_DEBUG 
sprintf(debug, "address.Val  = 0x%08lX\r\n", address.Val);
serInstPutString(0, debug);

sprintf(debug, "data.word.HW = 0x%04X\r\n", data.word.HW);
serInstPutString(0, debug);
sprintf(debug, "data.word.LW = 0x%04X\r\n", data.word.LW);
serInstPutString(0, debug);
#endif

        /* assign the results to the data buffer */
        *(buff++) = data.v[0];
        if ( ++bytes_read == size )
            return 0;

        *(buff++) = data.v[1];
        if ( ++bytes_read == size )
            return 0;
    }

    return 0;
}

int memWrite(char* buff, int size)
{
    int total_words, i, bytes_written;
    DWORD_VAL address;
    DWORD_VAL data;

#if FLASH_MEM_DEBUG 
char debug[64];
#endif

    /* if size is too big or too small bailout with error */
    if ( (size < 1) || (size > MAX_PAGE_BYTES) )
        return -1;

    /* calculate total words to written */
    total_words = ((size - 1) / 2) + 1;

    /* clear bytes_read variable */
    bytes_written = 0;
        
    for (i = 0; i < total_words; ++i)
    {
        /* assign address of word to write */
        address.Val = PARAM_PAGE + (2 * i);
        
        /* assign the results to the data buffer */
        if ( ++bytes_written < (size + 1) )
            data.v[0] = *(buff++);
        else
            data.v[0] = 0xFF;
        
        if ( ++bytes_written < (size + 1) )
            data.v[1] = *(buff++);
        else
            data.v[1] = 0xFF;

        /* set the upper byte and phantom byte to all zeros */
        data.v[2] = 0x00;
        data.v[3] = 0x00;

#if FLASH_MEM_DEBUG 
sprintf(debug, "address.Val  = 0x%08lX\r\n", address.Val);
serInstPutString(0, debug);

sprintf(debug, "data.word.HW = 0x%04X\r\n", data.word.HW);
serInstPutString(0, debug);
sprintf(debug, "data.word.LW = 0x%04X\r\n", data.word.LW);
serInstPutString(0, debug);

sprintf(debug, "data.Val     = 0x%08lX\r\n", data.Val);
serInstPutString(0, debug);
#endif

        /* write that value to the latch */
        WriteLatch(address.word.HW, address.word.LW, data.word.HW, data.word.LW);

        /* write the latch contents to flash memory */
        WriteMem();
        
        /* if you've written size bytes or more, your'e done */
        if ( bytes_written >= size)
            return 0;
    }
    
    return 0;
}

void memErase()
{
    DWORD_VAL sourceAddr;
    
    /* assign address within page to be addressed */
    sourceAddr.Val = PARAM_PAGE;

    Erase(sourceAddr.word.HW, sourceAddr.word.LW);
}

unsigned int memCheckSum()
{
    DWORD_VAL address;
	DWORD_VAL temp;
    unsigned int cs = 0;
    unsigned int i;
    
#define TOTAL_WORDS 0xAC00
        
    for (i = 0; i < TOTAL_WORDS; ++i)
    {
        /* assign address of word to read */
/*        address.Val = 0x0C00 + (2 * i);*/
        address.Val = (2 * i);

        TBLPAG = address.word.HW;

        temp.word.LW = __builtin_tblrdl(address.word.LW);
        temp.word.HW = __builtin_tblrdh(address.word.LW);
        
        /* calc checksum */
        cs += (unsigned int)temp.Val;
    }

    return cs;
}

/****************************************************************************/
/*           Local functions for reading and writing Flash memory           */
/****************************************************************************/
#define PM_WORD_WRITE   0x4003  /* NVM word write opcode */
#define PM_PAGE_ERASE   0x4042  /* NVM page erase opcode */

static void WriteMem()
{
    NVMCON = PM_WORD_WRITE;

    __builtin_write_NVM();

    while (NVMCONbits.WR == 1)
        ; /* wait for bit change */
}	

static void WriteLatch(WORD page, WORD addrLo, WORD dataHi, WORD dataLo)
{
    TBLPAG = page;
    
    __builtin_tblwtl(addrLo, dataLo);
    __builtin_tblwth(addrLo, dataHi);
}

static void Erase(WORD page, WORD addrLo)
{
    WORD temp;	
    
    temp = TBLPAG;
    TBLPAG = page;
    
    NVMCON = PM_PAGE_ERASE;
    
    __builtin_tblwtl(addrLo, addrLo);
    __builtin_write_NVM();
    
    
    while (NVMCONbits.WR == 1)
        ; /* wait for bit change */
    
    TBLPAG = temp;
}

static DWORD ReadLatch(WORD page, WORD addrLo)
{
	DWORD_VAL temp;
	TBLPAG = page;

	temp.word.LW = __builtin_tblrdl(addrLo);
	temp.word.HW = __builtin_tblrdh(addrLo);

	return temp.Val;
}
