/******************************************************************************/
/*                              SCRAMNet GT                                   */
/******************************************************************************/
/*                                                                            */
/* Copyright (c) 2020 Curtiss-Wright Controls Electronic System, Inc.         */
/*     dtn_support@curtisswright.com 800-252-5601 (U.S. only) 937-252-5601    */
/*                                                                            */
/* This program is free software; you can redistribute it and/or              */
/* modify it under the terms of the GNU General Public License                */
/* as published by the Free Software Foundation; either version 2             */
/* of the License, or (at your option) any later version.                     */
/*                                                                            */
/* See the GNU General Public License for more details.                       */
/*                                                                            */
/******************************************************************************/

#define APP_VER_STRING "GT SEEPROM Programmer (gtseeprom) rev. 1.00 (2006/03/21)"

/**************************************************************************/
/****** TAKING CARE OF ALL POSSIBLE OS TYPES - COPY-PASTE *****************/
/**************************************************************************/
/* control by PLATFORM_WIN, PLATFORM_UNIX, PLATFORM_VXWORKS */          /**/
#include <math.h>                                                       /**/
#include <stdio.h>                                                      /**/
#include <time.h>                                                       /**/
#include <string.h>                                                     /**/
#include <ctype.h>                                                      /**/
                                                                        /**/
#include "systypes.h"                      /* this will get a path */   /**/
#include "usys.h"                          /* this will get a path */   /**/
                                                                        /**/
#define PARSER                                                          /**/
#define MAIN_IN      int main(int argc, char **argv) {                  /**/
                                                                        /**/
#if defined PLATFORM_WIN || defined PLATFORM_RTX                        /**/
    #include <windows.h>                                                /**/
                                                                         /**/
#elif PLATFORM_VXWORKS                                                  /**/
    #undef PARSER                                                       /**/
    #undef MAIN_IN                                                      /**/
    #define VXL     40                                                  /**/
    #define MAIN_IN int APP_VxW_NAME(char *cmdLine) { \
            int argc; char argv0[]="?\0"; char *argv[VXL]={argv0};      /**/
    #define PARSER     argc = parseCls(cmdLine, argv);                  /**/
                                                                        /**/
static int parseCls(char *cLine, char *argv[VXL])                       /**/
{                                                                       /**/
    char *token, *ptr = NULL;                                           /**/
    int numTokens = 1;                                                  /**/
    char seps[] = " ,\t\n";                                             /**/
    if(cLine == NULL) return numTokens; /* no command line given */     /**/
    printf(" \b"); token=(char*)strtok_r(cLine,seps,&ptr);              /**/
    while(token != NULL)                                                /**/
    { argv[numTokens] = token;                                          /**/
      numTokens++;                                                      /**/
      token = (char*)strtok_r(NULL, seps, &ptr);                        /**/
    }                                                                   /**/
    return numTokens;                                                   /**/
}                                                                       /**/
#endif                                                                  /**/
/************ END OF TAKING CARE OF ALL POSSIBLE OS TYPES *****************/

/**************************************************************************/
/******************* HARDWARE SPECIFIC INCLUDES ***************************/
/**************************************************************************/

#define HW_SCGT
#include "scgtapi.h"
#define HAND scgtHandle
#define APP_VxW_NAME gtseeprom

/**************************************************************************/
/************** APPLICATION-SPECIFIC CODE STARTS HERE *********************/
/**************************************************************************/

/**************************************************************************/
/**************************   I N C L U D E S   ***************************/
/**************************************************************************/

#if !defined PLATFORM_WIN && !defined PLATFORM_RTX
#include <unistd.h>
#endif
#include <fcntl.h>
#include "scgtapi.h"

/**************************************************************************/
/**************************  D A T A  T Y P E S  **************************/
/**************************************************************************/

#define SEEPROM_SIZE   128  /* in 16-bit words */

/* Data structures used by option parsing code */

typedef struct {
    uint32      unit, helpLevel, revision, doProgram, doWriteAll, doErase;
    uint32      uspr; /* usec per register read, used to create "usleep" */
    uint16      value, inc;
    uint32      addr, count, flags, columns;
    uint16      buf[SEEPROM_SIZE];
    HAND        handle;
} iOptionsType;

/* display modes */
#define D_NO_OFFSETS   0x1
#define D_ADD_OX       0x2
#define D_ONE_COLUMN   0x4
#define D_ONE_ROW      0x8
#define D_REL_OFFSETS  0x10
#define D_DEBUG        0x20

/* op codes */
#define OP_ERASE        0x3
#define OP_ERAL         0x0
#define OP_EWDS         0x0
#define OP_EWEN         0x0
#define OP_READ         0x2
#define OP_WRITE        0x1
#define OP_WRAL         0x0

/* special address for use with certain op codes */
#define ADDR_ERAL       0x80
#define ADDR_EWDS       0x00
#define ADDR_EWEN       0xC0
#define ADDR_WRAL       0x40

/* default delay time */
#define DEF_DELAY       100

/**************************************************************************/
/******************  F U N C T I O N  P R O T O T Y P E S  ****************/
/**************************************************************************/

static int seepromProg( iOptionsType *iO );
static int seepromRead( iOptionsType *iO );
int writeAll(iOptionsType *iO, uint16 val);
int eraseSeeprom(iOptionsType *iO);
static int parseCommand(int argc,char **argv,iOptionsType *iO);
static void printHelpLevel1(iOptionsType *iO);
static void printHelpLevel2();

/**************************************************************************/
/***********************  D E F I N E S & M A C R O S  ********************/
/**************************************************************************/

/*
 * The following 4 macros must correspond to the bit definitions of the SEEPROM
 * programming register of the GT device. However, they should be defined from
 * the perspective of the SEEPROM chip, i.e. SEE_DI corresponds to the DI pin
 * of the SEEPROM and SEE_DO corresponds to the DO pin of the EEPROM. Therefore,
 * when programming the SEEPROM, data is written to the bit defined by macro
 * SEE_DI and read from the bit defined by macro SEE_DO.
 *
 * NOTE: The SEEPROM's DI pin is referred to as SEE_DO in GT hardware manual,
 *       and the DO pin is referred to as SEE_DI (i.e. input/output labels
 *       relative to FPGA pins).
 */

/* bit definitions for GT */
#define SEE_CLK     0x01
#define SEE_CS      0x02
#define SEE_DI      0x04  /* GT manual names this SEE_DO */
#define SEE_DO      0x08  /* GT manual names this SEE_DI */

#define SEE_POP     0x10

/* SEEPROM programming register offset definition for GT */
#define SEEPROM_REG  0x20

static uint32 flags;

#define DPrintF  if(flags & D_DEBUG)printf

/**************************************************************************/
/************************  G T P R O G   C O D E  *************************/
/**************************************************************************/

MAIN_IN
    iOptionsType   iO;
    int ret = 0;
    static volatile uint32 regVal; /* to ensure register access is performed */
    uint32 j;
    time_t nowTime, startTime;

    PARSER

    memset(&iO, 0, sizeof(iOptionsType));

    ret = parseCommand(argc, argv, &iO);      /* parsing options */

    if( ret || (iO.helpLevel != 0) ) /*1, 2, 3, ...*/
    {
        if ( iO.helpLevel )
            printHelpLevel1(&iO);

        return ret;
    }

    if (iO.count > 1)
        printf("\n%s\n\n", APP_VER_STRING);


    if( iO.unit == -1 )
    {
        printf("\n!!! Must specify a valid unit with option -u !!!\n");
        ret = -1;
        goto done;
    }

    /* Open the GT device for programming */
    if (scgtOpen(iO.unit, &iO.handle) != 0)
    {
        printf("ERROR: could not open driver for unit %d\n", iO.unit);
        goto done;
    }

    if ( !(scgtReadCR(&iO.handle, SEEPROM_REG) & SEE_POP) )
    {
        printf("Serial EEPROM not populated on this GT device\n");
        return -1;
    }

    /* Timer calibration */
    startTime = time(NULL);

    while( ((nowTime = time(NULL)) - startTime) < 1 ){} /* align timer */

    startTime=nowTime;
    j=0;
    while(1) /* measure the rate */
    {
        regVal = scgtReadCR(&iO.handle, SEEPROM_REG); /* do not optimize out */

        if ( ((++j%100) == 0) && (((nowTime=time(NULL)) - startTime) > 0 ))
        {
            break;
        }
    }

    iO.uspr=10000000/j; /* number of 1/10 of us per one read of JTAG register */
    DPrintF("Timing calibration: SEEPROM reg read takes about %d tenths of a us.\n",
            iO.uspr);

    if( iO.doProgram )
    {
        seepromProg(&iO);
    }
    else if ( iO.doErase )
    {
        eraseSeeprom(&iO);
    }
    else if ( iO.doWriteAll )
    {
        writeAll(&iO, iO.value);
    }
    else
    {
        seepromRead(&iO);
    }

    scgtClose(&iO.handle);

done:
    return ret;
}


/***************************************************************************/
/* Function:    gtseepromPrintResults                                      */
/* Description: Prints data buffer and formats it into columns             */
/***************************************************************************/

static int gtseepromPrintResults(iOptionsType *iO, void *buf,
                      int length, int memOffset)
{
    int i, prtOffset, col = 0;
    char *ox = ((iO->flags & D_ADD_OX) ? "0x": "");
    for(i=0; i < (int)length; i++, memOffset++)
    {
        if( !(col%iO->columns) && !(iO->flags & D_NO_OFFSETS))
        {
            prtOffset = memOffset;

            if( iO->flags & D_REL_OFFSETS )
                prtOffset = prtOffset - iO->addr;
                
            printf("0x%.2x: ", prtOffset);
        }

        printf("%s%.4x ", ox,((uint16*)buf)[i]);

        if( !((++col)%iO->columns) )
            printf("\n");
    }

    if( col )
        printf("\n");

    return 0;
}

int usec_delay( iOptionsType *iO, uint32 usec )
{
    uint32 i;
    static volatile uint32 regVal = 0; /* ensure the register access is performed */

    if ( usec > 20000 ) /* 20 msec */
    {
        usysMsTimeDelay((usec+999)/1000);
    }
    else
    {
        for(i = 0; i < (usec*10); i += iO->uspr)
        {
            regVal += scgtReadCR(&iO->handle,SEEPROM_REG);
        }
    }

    return regVal; /* Dummy return. Also ensures register access is performed */
}

static void setBits(iOptionsType *iO, uint32 bits, uint32 val)
{                                      /* used infrequently without clocking */
    uint32 regVal = scgtReadCR(&iO->handle, SEEPROM_REG);

    if (val)
        regVal = regVal | bits;
    else
        regVal = regVal & ~bits;

    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal);
}

static void pulseClock(iOptionsType *iO, uint32 regVal, uint32 flags) /* used infrequently*/
{
    /* Note: 250 ns hold time on SEE_CLK, both low and high */
    if ( !(flags & 0x1) ) /* read regVal */
        regVal = scgtReadCR(&iO->handle, SEEPROM_REG);
    /* else current regVal given as input */

    if ( regVal & SEE_CLK ) /* must lower SEE_CLK */
    {
        scgtWriteCR(&iO->handle, SEEPROM_REG, regVal & ~SEE_CLK);  /* set SEE_CLK port to low  */
        usec_delay(iO, DEF_DELAY);
    }

    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal | SEE_CLK);   /* set SEE_CLK port to high */
    usec_delay(iO, DEF_DELAY);
    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal & ~SEE_CLK);  /* set SEE_CLK port to low  */
    usec_delay(iO, DEF_DELAY);
}

static uint32 readDOBit(iOptionsType *iO)
{
    /* this register access must physically read the hardware register.
       No "emulation" can be performed here */
    uint32 regVal = scgtReadCR(&iO->handle, SEEPROM_REG);
    return (regVal & SEE_DO)? 1 : 0;
}

static void clockOutBit(iOptionsType *iO, uint32 bits, uint32 val)
{
    uint32 regVal = scgtReadCR(&iO->handle, SEEPROM_REG);

    if (val)
        regVal = regVal | bits;
    else
        regVal = regVal & ~bits;

    /* port will be set when SEE_CLK set to 0, also embedded pulseClock */
    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal & ~SEE_CLK);
    usec_delay(iO, DEF_DELAY);
    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal | SEE_CLK);
    usec_delay(iO, DEF_DELAY);
    scgtWriteCR(&iO->handle, SEEPROM_REG, regVal & ~SEE_CLK);
    usec_delay(iO, DEF_DELAY);
}

int waitForReady(iOptionsType *iO)
{
    #define MAX_POLL 1000
    int i;

    for ( i = 0; !readDOBit(iO) && (i < MAX_POLL); i++ );

    if ( i == MAX_POLL )
    {
        printf("waitForReady failed\n");
        return -1;
    }

    if ( i > 0 )
        printf("waitForReady %d\n", i);

    return 0;
}

int disableChip(iOptionsType *iO)
{
    setBits(iO, SEE_CS, 0);
    usec_delay(iO, DEF_DELAY);
    return 0;
}

int enableChip(iOptionsType *iO)
{
    clockOutBit(iO, (SEE_CS | SEE_DI), 1);

    if ( waitForReady(iO) )
        return -1;

    return 0;
}

int sendCommand(iOptionsType *iO, int opcode, uint32 address, uint16 *data, int count)
{
    volatile uint32 regVal;
    uint32 mask, command = 0;
    int bits = ((opcode == OP_WRITE) || (opcode == OP_WRAL)) ? 26 : 10;
    int i, j;

    command = (opcode & 0x3) << 24;
    command |= ((address << 16) & 0xff0000);

    if (data )
        command |= ((*data) & 0xffff);

    disableChip(iO);

    if( enableChip(iO) ) /* send the START bit */
    {
        printf("Failed to enable chip\n");
        return -1;
    }

    for ( mask = (1 << 25), i = 0; i < bits; i++, command <<= 1)
    {
        if ( command & mask )
            clockOutBit(iO, SEE_DI, 1);
        else
            clockOutBit(iO, SEE_DI, 0);
    }

    if ( opcode == OP_READ )
    {
        regVal = scgtReadCR(&iO->handle, SEEPROM_REG);

        for ( i = 0; i < count; i++ )
        {
            data[i] = 0;

            /* read data out of SEE_DO and into *data */
            for ( j = 15; j >= 0; j-- )
            {
                pulseClock(iO, regVal, 1);

                if ( readDOBit(iO) )
                {
                    data[i] |= (1 << j);
                }
            }
        }

        pulseClock(iO, regVal, 1);
    }

    disableChip(iO);
    return 0;
}


int writeAll(iOptionsType *iO, uint16 val)
{
    sendCommand(iO, OP_EWEN, ADDR_EWEN, &val, 1); /* Enable writes / Disable write-protection */
    usysMsTimeDelay(10);
    sendCommand(iO, OP_WRAL, ADDR_WRAL, &val, 1); /* Enable writes / Disable write-protection */
    usysMsTimeDelay(10);
    sendCommand(iO, OP_EWDS, ADDR_EWDS, &val, 1); /* Disable writes / Enable write-protection */
    usysMsTimeDelay(10);
    return 0;
}

int eraseSeeprom(iOptionsType *iO)
{
    sendCommand(iO, OP_EWEN, ADDR_EWEN, NULL, 0); /* Enable writes / Disable write-protection */
    usysMsTimeDelay(10);
    sendCommand(iO, OP_ERAL, ADDR_ERAL, NULL, 0); /* Enable writes / Disable write-protection */
    usysMsTimeDelay(10);
    sendCommand(iO, OP_EWDS, ADDR_EWDS, NULL, 0); /* Disable writes / Enable write-protection */
    usysMsTimeDelay(10);
    return 0;
}

int writeBufToSeeprom(iOptionsType *iO, uint16 *buf, uint32 addr, int length)
{
    int i;

    sendCommand(iO, OP_EWEN, ADDR_EWEN, NULL, 0); /* Enable writes / Disable write-protection */

    for ( i = 0; i < length; i++ )  /* write data word by word */
    {
        usysMsTimeDelay(10);
        sendCommand(iO, OP_WRITE, addr + i, &(buf[i]), 1);
    }

    usysMsTimeDelay(10);
    sendCommand(iO, OP_EWDS, ADDR_EWDS, NULL, 0); /* Disable writes / Enable write-protection */
    return 0;
}

int readBufFromSeeprom(iOptionsType *iO, uint16 *buf, uint32 addr, int length)
{
    return sendCommand(iO, OP_READ, addr, buf, length);
}

static int seepromProg( iOptionsType *iO )
{
    /* get information, and fill in buffer to program */
    int i;

    for ( i = 0; i < SEEPROM_SIZE; i++ )
    {
        iO->buf[i] = iO->value;
        iO->value += iO->inc;
    }

    writeBufToSeeprom(iO, iO->buf, iO->addr, iO->count);
    return 0;
}

static int seepromRead( iOptionsType *iO )
{
    /* get information, and fill in buffer to program */
    if ( readBufFromSeeprom(iO, iO->buf, iO->addr, iO->count) )
    {
        printf ("Failed to read SEEPROM\n");
        return -1;
    }

    gtseepromPrintResults(iO, iO->buf, iO->count, iO->addr);
    return 0;
}


/**************************************************************************/
/*  function:     parseCommand                                            */
/*  description:  Getting argv details into internal usable form          */
/**************************************************************************/
static int parseCommand(int argc, char **argv, iOptionsType *iO)
{
    char *endptr, nullchar = 0;
    int  len, tookParam=0;
    int i,j;                               /* setting defaults */

    iO->count = SEEPROM_SIZE;
    iO->addr = 0;

    iO->unit = -1;
    iO->helpLevel=1;

    if(argc == 1)                                       /* start processing */
        return -1;

    for(i = 1; i < argc; i++, tookParam=0)
    {
        len = strlen(argv[i]);
        if((argv[i][0] != '-') || (len < 2))
        {
            printf("\nERROR: Unexpected option: \"%s\"\n\n", argv[i]);
            return -1;
        }

        if ( (argv[i][1] == '-') )
        {
            if( !strcmp( &argv[i][2], "version" ) )
            {
                printf("%s\n", APP_VER_STRING);
                printf(" - built with API revision %s\n", scgtapiRevisionStr);
                exit (0);
            }
            else if( !strcmp( &argv[i][2], "help" ) )
            {
                iO->helpLevel=2;
                return -1;
            }
        }
                                          /* do options with arguments first*/
        for(j=1; j<len; j++)
        {
            if( (j == (len-1)) && ((i+1) < argc))
            {   /* test for options which take parameters */
                /* these options only valid as last char of -string */
                tookParam = 1;  endptr = &nullchar;
                if(     argv[i][j]=='u') iO->unit=strtoul(argv[i+1],&endptr,0);
                else if(argv[i][j]=='v') iO->value=strtoul(argv[i+1],&endptr,0) & 0xffff;
                else if(argv[i][j]=='o') iO->addr=strtoul(argv[i+1],&endptr,0);
                else if(argv[i][j]=='c') iO->count=strtoul(argv[i+1],&endptr,0);
                else if(argv[i][j]=='i') iO->inc=strtoul(argv[i+1],&endptr,0);
                else if(argv[i][j]=='k') iO->columns=strtoul(argv[i+1],&endptr,0);
                else if(argv[i][j]=='d') iO->flags=strtoul(argv[i+1],&endptr,0);
                else tookParam = 0;

                if( tookParam && (*endptr))
                {
                   printf("\nInvalid parameter \"%s\" for option \"%c\"\n\n",
                          argv[i+1], argv[i][j]);
                   return -1;
                }
            }
            /* options without arguments now */
            if(              argv[i][j]=='W') iO->doProgram = 1;
            else if(         argv[i][j]=='A') iO->doWriteAll = 1;
            else if(         argv[i][j]=='E') iO->doErase = 1;
            else if(tolower(argv[i][j])=='h')
            {
                iO->helpLevel=2;
                if(argc > 2) iO->helpLevel=3;
                return -1;
            }
            else if(!tookParam)
            {
                printf("\nERROR: Unexpected option: \"%c\"\n\n",argv[i][j]);
                return -1;
            }
        }
        if (tookParam) i++;
    }

    iO->helpLevel = 0;

    if ( iO->addr > (SEEPROM_SIZE - 1) )
    {
        printf("\nERROR: Offset too large.\n\n");
        return -1;
    }

    if ( iO->count > (SEEPROM_SIZE - iO->addr) )
        iO->count = SEEPROM_SIZE - iO->addr;

    if( iO->flags & D_ONE_ROW)
        iO->columns = SEEPROM_SIZE; /* may be larger than needed */
    if( iO->flags & D_ONE_COLUMN)
        iO->columns = 1;
    if( !iO->columns ) /* set default number of columns */
        iO->columns = 8;
    if( iO->count == 1 )
        iO->flags |= D_NO_OFFSETS | D_ADD_OX;

    flags = iO->flags;
    return 0;
}


/**************************************************************************/
/*  function:     printHelpLevel1                                         */
/*  description:  Print hints and call more help if needed                */
/**************************************************************************/
static void printHelpLevel1(iOptionsType *iO)
{
    printf("%s\n\n", APP_VER_STRING);
    printf("Usage: gtseeprom -u unit [-WEA] [-h] [-v value] [-o offset]\n"
           "                 [-c count] [-d dispMode]\n");
#ifdef PLATFORM_VXWORKS
    printf("\nVxWorks users: enclose options list in a set of quotes \" \".\n");
#endif
    printf("\n");
    if(iO->helpLevel>1)
        printHelpLevel2();
}

/**************************************************************************/
/*  function:     printHelpLevel2                                         */
/*  description:  Print more hints                                        */
/**************************************************************************/
static void printHelpLevel2()
{
    printf("\n"
"The gtseeprom utility facilitates reading and writing the SEEPROM on a\n"
"GT device.\n"
"\n");
    printf(
"Options:\n"
"  -u # - SCRAMNet GT board/unit number\n"
"  -W   - Write to SEEPROM\n"
"  -E   - Erase SEEPROM\n"
"  -A   - Write all of SEEPROM with -v # (one SEEPROM command),\n"
"         Option -i not supported when -A.\n"
"  -v # - Value to write (default 0).\n"
"  -o # - Offset at which to begin writing (default 0)\n"
"  -c # - Number of locations to read/write (default all=128)\n"
"  -i # - Increment to add to -v # for each write (default 0)\n"
"  -k # - Columns to use in display on read. If count is 1,\n"
"         then will always be 1. Otherwise, default is 8.\n"
"  -d # - Display modifier flags (positionally coded), default is 0:\n"
"         0x1 - omit offsets, 0x2 - add 0x, 0x4 - one column, 0x8 - one row\n"
"         0x10 - relative offsets, 0x20 - debug\n"
"  -h   - print this help menu\n"
"\n"
);

printf("This version of gtseeprom linked with API revision %s.\n",
            scgtapiRevisionStr);
}
