/************************************************************************

 CAEN SpA - Viareggio
 www.caen.it

 Program: CVupgrade
 Date:    11/10/2007
 Author:  Carlo Tintori (c.tintori@caen.it)

 ------------------------------------------------------------------------
 Description
 ------------------------------------------------------------------------ 
 This program allows to upgrade the firmware of any CAEN VME module that 
 supports the firmware download through the VME bus (except for the 
 bridges V1718 and V2718 that use the program 'CAENVMEUpgrade').
 The firmware upgrade consists in writing the configuration file (Altera 
 Row Binary Format) into the flash memory of the board. Usually the flash
 memory contains two images of the configuration file, called 'standard'
 and 'backup'. The user should overwrite the 'standard' image only; the 
 new firmware will be loaded by the FPGA after the first power cycle.
 If an error occurs while upgrading, it may happen that the FPGA is not 
 properly configured and the board does not respond to the VME access.
 In this case you can restore the old firmware (i.e. the 'backup' image)
 just moving the relevant jumper from STD to BKP and power cycling the 
 board. Now you can retry to upgrade the 'standard' image. 
 Warning: never upgrade the 'backup' image until you are sure that the 
 'standard' works properly.
 This program reads some parameters that define the type of the board
 being upgraded from a file called CVupgrade_params.txt that must be
 in the same directory of CVupgrade. There is one CVupgrade_params file
 for each type of board that can be upgraded.

 ------------------------------------------------------------------------ 
 Usage
 ------------------------------------------------------------------------ 
 Syntax: CVupgrade [options] ConfFile BaseAddress

 Options:
 -backup: write backup image
 -no_verify: disable verify (don't read and compare the flash content)
 -verify_only: read the flash content and compare with the ConfFile file
 -btype: allow to specify the file that contain the parameters for the
        board that is being upgraded (default is CVupgrade_params.txt)

 
 ------------------------------------------------------------------------ 
 Portability
 ------------------------------------------------------------------------ 
 This program is ANSI C and can be compilated on any platform, except 
 for the functions that allow to initialize and access the VME bus.
 If you are using CAEN's VME bridges (both V1718 and V2718), then
 define '_CAENVME' and the compiler will take the functions for the 
 VME access from the CAENVMELib library. If '_CAENVME' is not defined,
 you have to write your functions for the VME access where indicated 
 in the source code.

*************************************************************************/

#define REVISION  "1.0"

#define _CAENVME  // Remove this line if you don't use CAEN's VME bridges

#include <stdio.h>
#ifdef _CAENVME
    #include "CAENVMELib.h"
#else
    // Put your include files for the VME here
#endif

// Opcodes for the flash memory
#define MAIN_MEM_PAGE_READ          0xD2
#define MAIN_MEM_PAGE_PROG_TH_BUF1  0x82


// Global Variables
unsigned long RW_Flash;   // Address of the R/W Flash Register
unsigned long Sel_Flash;  // Address of the Select Flash Register
int RegSize;              // Register Data Size (2 = D16, 4 = D32)
int PageSize;             // Flash Page Size
int FirstPageStd;         // First page of the Standard image
int FirstPageBck;         // First page of the Backup image

#ifdef _CAENVME
    long Handle;                      // handle for the VME controller
    unsigned long Wbuff[2048];        // Buffers for the Multi Read/Write
    unsigned long Addrs[2048]; 
    unsigned long *Rbuff;
    CVAddressModifier AMs[2048];
    CVDataWidth DWs[2048];
    CVErrorCodes ECs[2048];
    int Wpnt = 0, Rpnt = 0;
#endif



//########################################################################
// VME functions
//########################################################################
// You must provide the following functions for the VME access if you 
// don't use the V1718 nor the V2718 as VME controller:
// 
// int VME_Init(void)
// -----------------------------------------------------------------------
// Description: open and initialize the VME controller.
// Return 0 when succeeded, a negative value when failed 
// 
// int VME_Write(unsigned long Addr, unsigned long Data, int Size)
// -----------------------------------------------------------------------
// Description: make a single data write access to the VME bus (A32 or A24)
// Parameters: Addr is the VME address, Data is the data to write 
// (cast to unsigned long for both D16 and D32 access), Size is the type 
// of VME access (2 for D16, 4 for D32)
// Return 0 when succeeded, a negative value when failed 
//
// int VME_Read(unsigned long Addr, unsigned long *Data, int Size)
// -----------------------------------------------------------------------
// the same as for the VME write access except for Data that is a pointer.
//
// int VME_End(void)
// -----------------------------------------------------------------------
// Description: close the VME controller.
// Return 0 when succeeded, a negative value when failed 
//
// NOTE: in the case of CAEN's bridges, it is possible to optimize the
// transfer rate of the series of single data acccess by using the 
// Multi Read/Write functions. To do that, the single read and write
// to the flash memory are not executed immediately; data are saved in
// a buffer and then read/written all together.


#ifndef _CAENVME

    int VME_Init(void) 
    {
    // Put your code for the VME initialization here
    }

    int VME_Write(unsigned long Addr, unsigned long Data, int Size)
    {
    // Put your code for the VME single data write access here
    }

    int VME_Read(unsigned long Addr, unsigned long *Data, int Size)
    {
    // Put your code for the VME single data read access here
    }

    int VME_End(void) 
    {
    // Put your code for closing the VME device here
    }

#else

    // -----------------------------------------------------------------------
    int VME_Init(void) 
    {
        if( CAENVME_Init(cvV2718, 0, 0, &Handle) == cvSuccess )
            return 0;
        else if( CAENVME_Init(cvV1718, 0, 0, &Handle) == cvSuccess )
            return 0;
        else
            return -1;
    }

    // -----------------------------------------------------------------------
    int VME_Write(unsigned long Addr, unsigned long Data, int Size)
    {
        int ret = 0;
        unsigned short Data16;

        if (Addr != RW_Flash) {
            if (Wpnt > 0) {
                ret |= CAENVME_MultiWrite(Handle, Addrs, Wbuff, Wpnt, AMs, DWs, ECs);
                Wpnt = 0;
            }

            if (Rpnt > 0) {
                ret |= CAENVME_MultiRead(Handle, Addrs, Rbuff, Rpnt, AMs, DWs, ECs);
                Rpnt = 0;
            }

            if (Size == 2) { // D16 access
                Data16 = (unsigned short)Data;
                ret |= CAENVME_WriteCycle(Handle, Addr, &Data16, cvA32_U_DATA, Size);
            } else {
                ret |= CAENVME_WriteCycle(Handle, Addr, &Data, cvA32_U_DATA, Size);
            }
        }
        
        // Access to the flash memory (use a MultiWrite)
        else {
            Wbuff[Wpnt] = Data;
            Addrs[Wpnt] = Addr;
            AMs[Wpnt] = cvA32_U_DATA;
            DWs[Wpnt] = Size;
            Wpnt++;
        }
        return (ret);
    }

    // -----------------------------------------------------------------------
    int VME_Read(unsigned long Addr, unsigned long *Data, int Size)
    {
        int ret = 0;
        unsigned short Data16;

        if (Wpnt > 0) {
            ret |= CAENVME_MultiWrite(Handle, Addrs, Wbuff, Wpnt, AMs, DWs, ECs);
            Wpnt = 0;
        }

        if (Addr != RW_Flash) {
            if (Size == 2) { // D16 access
                ret |= CAENVME_ReadCycle(Handle, Addr, &Data16, cvA32_U_DATA, Size);
                *Data = (unsigned long)Data16;
            } else {
                ret |= CAENVME_ReadCycle(Handle, Addr, Data, cvA32_U_DATA, Size);
            }
        }

        // Access to the flash memory (use a MultiRead)
        else {
            if (Rpnt == 0)
                Rbuff = Data; 
            Addrs[Rpnt] = Addr;
            AMs[Rpnt] = cvA32_U_DATA;
            DWs[Rpnt] = Size;
            Rpnt++;
        }
        return (ret);
    }

    // -----------------------------------------------------------------------
    int VME_End(void) 
    {
        return(CAENVME_End(Handle));
    }

#endif;

//########################################################################




// *****************************************************************************
// WriteFlashPage: write one page of the flash memory
// Parameters: data: pointer to the data buffer
//             pagenum: flash page number
// Return: 0 on Success, < 0 on error
// *****************************************************************************
int WriteFlashPage(unsigned char *data, int pagenum)
{
    int i, res = 0;
    unsigned long flash_addr;

    flash_addr = (unsigned long)pagenum << 9;
    // Enable flash (/CS = 0)
    res |= VME_Write(Sel_Flash, 0, RegSize);
    // Opcode
    res |= VME_Write(RW_Flash, MAIN_MEM_PAGE_PROG_TH_BUF1, RegSize);
    // Page address
    res |= VME_Write(RW_Flash, (flash_addr>>16) & 0xFF, RegSize);
    res |= VME_Write(RW_Flash, (flash_addr>>8) & 0xFF, RegSize);
    res |= VME_Write(RW_Flash, flash_addr & 0xFF, RegSize);
    // Data
    for (i=0; i < PageSize; i++)
        res |= VME_Write(RW_Flash, (unsigned long)data[i], RegSize);
    // Disable flash (/CS = 1)
    res |= VME_Write(Sel_Flash, 1, RegSize);

    // wait for 20ms
    Sleep(20);
    return res;
}

// *****************************************************************************
// ReadFlashPage: read one page of the flash memory
// Parameters: data: pointer to the data buffer
//             pagenum: flash page number
// Return: 0 on Success, < 0 on error
// *****************************************************************************
int ReadFlashPage(unsigned char *data, int pagenum)
{
    int i, res = 0;
    unsigned long flash_addr;
    unsigned long Data32[2048];

    flash_addr = (unsigned long)pagenum << 9;
    // Enable flash (/CS = 0)
    res |= VME_Write(Sel_Flash, 0, RegSize);
    // Opcode
    res |= VME_Write(RW_Flash, MAIN_MEM_PAGE_READ, RegSize);
    // Page address
    res |= VME_Write(RW_Flash, (flash_addr>>16) & 0xFF, RegSize);
    res |= VME_Write(RW_Flash, (flash_addr>>8) & 0xFF, RegSize);
    res |= VME_Write(RW_Flash, flash_addr & 0xFF, RegSize);
    // additional don't care bytes
    for (i=0; i<4; i++)
        res |= VME_Write(RW_Flash, 0, RegSize);

    // Read the page
    for (i=0; i<PageSize; i++) 
        res |= VME_Read(RW_Flash, Data32+i, RegSize);

    // Disable flash (/CS = 1)
    res |= VME_Write(Sel_Flash, 1, RegSize);

    // transfer the 32bit data buffer to the 8 bit buffer
    // NOTE: if CAEN's bridges are used, the data are actually read from
    // the flash memory with a single MultiRead that is executed before
    // the access to the Sel_Flash register
    for (i=0; i<PageSize; i++) 
        data[i] = (unsigned char)Data32[i];

    return res;
}



/*******************************************************************************/
/* MAIN
/*******************************************************************************/
void main(int argc,char *argv[])
{
    int i, j, page, pa, CFsize, NumPages, err=0, done; 
    int image=0, noverify=0, verifyonly=0; 
    char ConfFile[1000];
    char ParamFile[1000] = "CVupgrade_params.txt";
    char Models[1000] = "";
    unsigned char c, *CFdata;
    unsigned char pdw[2048], pdr[2048];
    unsigned long BaseAddress;
    FILE *cf, *bdf;
  
    printf("\n");
    printf("********************************************************\n");
    printf(" CAEN SpA - Front-End Division                          \n");
    printf("--------------------------------------------------------\n");
    printf(" CAEN VME Firmware Upgrade                              \n");
    printf(" Version %s                                             \n", REVISION);
    printf("********************************************************\n\n");

    // Check command arguments (must be at least 2)
    if (argc < 3)  {
        printf("Syntax: CVupgrade [options] ConfFile BaseAddress\n\n");
        printf("Description: write the file 'ConfFile' (Altera Raw Binary Format) into\n");
        printf("the flash memory of board at the specific Base Address (Hex 32 bit)\n\n");
        printf("Options:\n");
        printf("-backup: write backup image\n\n");
        printf("-no_verify: disable verify (don't read and compare the flash content)\n\n");
        printf("-verify_only: read the flash content and compare with the ConfFile file\n\n");
        printf("-btype: allow to specify the file that contain the parameters for the\n");
        printf("        board that is being upgraded (default is CVupgrade_params.txt)\n");
        exit(0);
    }
    sprintf(ConfFile, argv[1]);
    sscanf(argv[2], "%x", &BaseAddress);
    for (i=3; i<argc; i++) {
        if ( strcmp(argv[i],"-backup") == 0 )
            image = 1;
        if ( strcmp(argv[i],"-no_verify") == 0 )
            noverify = 1;
        if ( strcmp(argv[i],"-verify_only") == 0 )
            verifyonly = 1;
    }

    // open the board descriptor file
    bdf = fopen(ParamFile,"r");
    if(bdf == NULL) {
        printf("Can't open file %s\n", ParamFile);
        exit(-1);
    }
    fgets(Models, 1000, bdf);
    fscanf(bdf, "%x", &Sel_Flash);
    Sel_Flash += BaseAddress;
    fscanf(bdf, "%x", &RW_Flash);
    RW_Flash  += BaseAddress;
    fscanf(bdf, "%d", &RegSize);
    fscanf(bdf, "%d", &PageSize);
    fscanf(bdf, "%d", &FirstPageStd);
    fscanf(bdf, "%d", &FirstPageBck);
    fclose(bdf);


    // open and read the configuration file
    cf = fopen(ConfFile,"rb");
    if(cf == NULL) {
        printf("Can't open file %s\n",ConfFile);
        exit(-1);
    }
    // calculate the size
    fseek (cf, 0, SEEK_END);
    CFsize = ftell (cf);
    fseek (cf, 0, SEEK_SET);
    CFdata = malloc(CFsize);
    for(i=0; i<CFsize; i++) {
       // read one byte from file and mirror it (lsb becomes msb)
        c = (unsigned char)fgetc(cf);
        CFdata[i]=0;
        for(j=0; j<8; j++)
            CFdata[i] |= ( (c >> (7-j)) & 1) << j;
    }
    fclose(cf);

    NumPages = (CFsize % PageSize == 0) ? (CFsize / PageSize) : (CFsize / PageSize) + 1;

    /* initialize the vme */
    if (VME_Init() < 0) {
        printf("Cannot open the VME controller!\n");
        exit(-1);
    }

    printf("Board Types: %s\n", Models);
    if (image == 0) {
        printf("Overwriting Standard image of the firmware with %s\n", ConfFile);
        pa = FirstPageStd;
    }
    else {
        printf("Overwriting Backup image of the firmware with %s\n", ConfFile);
        pa = FirstPageBck;
    }

    done = 10;

    // ************************************************
    // Start for loop
    // ************************************************
    for(page=0; page < NumPages; page++)  {
        if(!verifyonly) {
            // Write Page
            if (WriteFlashPage(CFdata + page*PageSize, pa + page) < 0) {
                printf("\nVME Error: the board at Base Address %08X does not respond\n", BaseAddress);
                err = 1;
                break;
                }
        }

        if(!noverify) {
            // Read Page
            if (ReadFlashPage(pdr, pa + page) < 0) {
                printf("\nVME Error: the board at Base Address %08X does not respond\n", BaseAddress);
                err = 1;
                break;
            }
            // Verify Page
            for(i=0; (i<PageSize) && ((page*PageSize+i) < CFsize); i++)  {
                if(pdr[i] != CFdata[page*PageSize + i])  {
                    printf("\nFlash verify error (byte %d of page %d)!\n", i, pa + page);
                    if ((image == 0) && !verifyonly)
                        printf("The STD image can be corrupted! \nMove the jumper to Backup image and cycle the power\n");
                    err = 1;
                    break;
                }
            }
        }
        if (err)
            break;

        if (page == (NumPages-1)) {
            printf("100%% Done\n", done);
        } else if ( page >= (NumPages*done/100) ) {
            printf("%d%% Done\n", done);
            done += 10;
        }
    }  // end of for loop

    if(!err) {
        if (verifyonly) {
            printf("\nFirmware verified successfully. Read %d bytes\n", CFsize);
        } else {
            printf("\nFirmware updated successfully. Written %d bytes\n", CFsize);
            printf("The new firmware will be loaded after a power cycle\n");
        }
    }

    //free(CFdata);
    VME_End();
}


