/****************************************************************************/
/* Copyright 2004 MBARI.                                                    */
/* Monterey Bay Aquarium Research Institute Proprietary Information.        */
/* All rights reserved.                                                     */
/****************************************************************************/
#include <16f873.h>
#include <stdlib.h>
#include <string.h>
#include "vid_switch.h"
#include "errors.h"

#if DEBUG
#device ICD=TRUE
#fuses XT,NOWDT,NOPROTECT,PUT,NOBROWNOUT
#else
#fuses XT,NOWDT,NOPROTECT,PUT
#endif

#use delay(clock=3580000)
#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7, ERRORS)

#define vsUpdate(X) output_bit(PIN_C0, X)
#define vsCS(X)     output_bit(PIN_C2, X)
#define vsMode(X)   output_bit(PIN_A0, X)

#include "cmd_io.c"


#define LED_PIN PIN_B5

/* the EEPROM size and CS pin must be specified before using eeprom.c */
#define EEPROM_SIZE     0x800 /* 2 KBytes */
#define EEPROM_CS_PIN   PIN_C1
#include "eeprom.c"

/* The indexes of the following array are used as video switch destination 
values.  The element at each index is the video source.  The most significant 
bit of the first byte in the array is used to determine whether a video switch
state record is valid. */
char vsState[16];

int debugMode = false;

/* setup the SPI bus for the video switch chip */
void vsSetupSpi()
{
    byte* reg = SSPSTAT;

    setup_spi(SPI_MASTER | SPI_H_TO_L | SPI_CLK_DIV_16);
    
    /* set CKE bit in SSPSTAT */
    *reg |= 0x40;
}

int vsSetChan(int src, int dst)
{
    char msb = 0;
    char lsb = 0;
    
    /* check source range */
    if ( (src < 0) || (src > 15) )
    {
        if ( echoMode )
            printf("SRC '%d' out of range [0,15]\r\n", src);
        return ERR_SRC_RANGE;
    }
    
    /* check destination range */
    if ( (dst < 0) || (dst > 15) )
    {
        if ( echoMode )
            printf("DST '%d' out of range [0,15]\r\n", dst);
        return ERR_DST_RANGE;
    }
    
    /* modify the video switch state array */
    vsState[dst] = src;

    /* setup the SPI bus for video switch access */
    vsSetupSpi();
    
    /* assert the chip select */
    vsCS(LO);
    
    /* write out the command */
    /* 
    MSB
    0 - don't care
    0 - don't care
    0 - IC address 3
    0 - IC address 2
    0 - IC address 1
    0 - IC address 0
    ? - output B3
    ? - output B2
    */
    msb = dst >> 2;

    /*
    LSB
    ? - output B1
    ? - output B0
    1 - output enabled
    1 - gain set 2:1
    ? - input address B3
    ? - input address B2
    ? - input address B1
    ? - input address B0
    */
    lsb = dst << 6;
    
    /* only turn on the outputs for destisnation 0-7 */
    if ( dst < 8)
        lsb |= 0x30;
    
    lsb |= src;

    spi_write(msb);
    spi_write(lsb);
    
    /* strobe the update line */
    vsUpdate(LO);
    vsUpdate(HI);

    /* deassert the chip select */
    vsCS(HI);
    
    return ERR_SUCCESS;
}

/* Get the address of the active state array within the EEPROM */
long vsGetStateAddr()
{
   long i;
   
   /* get record address by scanning every 16th byte of the EEPROM looking 
   for HI upper nibble */
   for (i = 0; i < EEPROM_SIZE; i += 16)
   {
       if ( eepromRead(i) & 0x80 )
       {
           if ( debugMode )
               printf("vsGetStateAddr() = %ld\r\n", i);
           return i;
       }
   }

   if ( echoMode )
   {
       printf("No records found in EEPROM!\r\n");
       printf("Reinitializing EEPROM to default state\r\n");
   }

   /* if no upper nibbles are hi set first upper nibble hi and zero the first 
   record */
   eepromWrite(0, 0x80);
    
   for (i = 1; i < 16; ++i)
       eepromWrite(i, 0);
    
   return 0;
}

int vsStoreState()
{
    long old_add, new_add;
    long i;
    /* get record address by scanning every 16th byte of the EEPROM looking 
    for HI upper nibble */
    old_add = vsGetStateAddr();

    if ( debugMode )
        printf("vsStoreState(): old_add = %ld\r\n", old_add);
    
    /* add 16 to the address modulo the EEPROM size */
    new_add = (old_add + 16) % EEPROM_SIZE;
    
    if ( debugMode )
        printf("vsStoreState(): new_add = %ld\r\n", new_add);
    
    /* write the new state starting at the EEPROM address */
    for (i = 0; i < 16; ++i )
        eepromWrite((i + new_add), vsState[i]);

    /* tag new record with as valid */
    eepromWrite(new_add, (eepromRead(new_add) | 0x80));

    /* mark the old record with zero, if power fails you're screwed */
    eepromWrite(old_add, 0);
    
    return ERR_SUCCESS;
}

void vsLoadState()
{
    long address, i;
    /* get record address by scanning every 16th byte of the EEPROM looking 
    for HI upper nibble */
    address = vsGetStateAddr();

    /* read out 16 byte record starting at the address */
    for (i = 0; i < 16; ++i)
        vsState[i] = (eepromRead(i + address) & 0x0F);
}

int vsClearState()
{
    long i;

    /* mark all records as inactive */
    for (i = 0; i < EEPROM_SIZE; i += 16)
        eepromWrite(i, 0);

    /* make the first as active and clear out all the sources */
    eepromWrite(0, 0x80);

    for (i = 1; i < 16; ++i)
        eepromWrite(i, 0);

    /* set the source and destinations of all the channels */
    for (i = 0; i < 16; i++)
    {
        vsState[i] = 0;
        vsSetChan(0, i);
    }
    
    return ERR_SUCCESS;
}

/* initialize the video switch */
int vsInit()
{
    int i;

    /* read the state from the double EEPROM */
    vsLoadState();

    /* setup the video switch from the EEPROM record */
    for (i = 0; i < 16; i++)
        vsSetChan(vsState[i], i);

    return ERR_SUCCESS;
}


int showHelp()
{
    printf("S - set dst and src of video channel (e.g. S [DST] [SRC])\r\n");
    printf("I - reinitialize the video switch from EEPROM\r\n");
    printf("G - return the video switch state\r\n");
    printf("V - display firmware revision info\r\n");
    printf("ECHO - enable/disable console echo mode (e.g. ECHO [0|1])\r\n");
    printf("RESET_EE - clear the EERPOM and reinitialize the video switch\r\n");
    printf("? - show this menu\r\n");
    
    return ERR_SUCCESS;
}


int showVersion()
{
    printf("Ventana video switch, rev %s\r\n", VS_VERSION);
    printf("built on %s\r\n", __DATE__);
    
    return ERR_SUCCESS;
}

void processCmds()
{
    char *token;
    char usr_cmd[MAX_CHARS];
    char cmd_str[MAX_CHARS];
    char delim[2];
    int i, dst, src;

    strcpy(delim, " ");

    /* turn LED off */
    output_bit(LED_PIN, OFF);

    if ( !getCommand(usr_cmd) )
        return;

    /* turn LED on */
    output_bit(LED_PIN, ON);

    if ( usr_cmd[0] == '\0' )
    {
        putErr(ERR_SUCCESS);
        return;
    }

    /* grab first token */
    token = strtok(usr_cmd, delim);

    /********************************** S ***********************************/
    strcpy(cmd_str, "S");

    if ( stricmp(token, cmd_str) == 0 )
    {
        dst = atoi(strtok(NULL, delim)); 
        src = atoi(strtok(NULL, delim)); 
        
        /* switch input 0 to output 0 */
        if ( echoMode )
            printf("switching DST%02d <- SRC%02d\r\n", dst, src);

        i = vsSetChan(src, dst);
        
        if ( i != ERR_SUCCESS )
        {
            putErr(i);
            return;  
        }
            
        putErr(vsStoreState());
        return;  
    }
    
    /********************************** I ***********************************/
    strcpy(cmd_str, "I");

    if ( stricmp(token, cmd_str) == 0 )
    {
        putErr(vsInit());
        return;
    }
    
    /********************************** G ***********************************/
    strcpy(cmd_str, "G");

    if ( stricmp(token, cmd_str) == 0 )
    {
        for (i = 0; i < 16; ++i)
        {
            printf("DST%02d SRC%02d\r", i, vsState[i]);
            putEcho();
        }

        putErr(ERR_SUCCESS);

        return;
    }
    
    /********************************** V ***********************************/
    strcpy(cmd_str, "V");

    if ( stricmp(token, cmd_str) == 0 )
    {
        putErr(showVersion());
        return;
    }
    
    /******************************* RESET_EE *******************************/
    strcpy(cmd_str, "RESET_EE");

    if ( stricmp(token, cmd_str) == 0 )
    {
        putErr(vsClearState());
        return;
    }

    /********************************* ECHO *********************************/
    strcpy(cmd_str, "ECHO");

    if ( stricmp(token, cmd_str) == 0 )
    {
        token = strtok(NULL, delim);
        switch ( *token )
        {
            case '0':   setEcho(0);
                        putErr(ERR_SUCCESS);
                        return;
            case '\0':  /* just the root command also enables echo mode */
            case '1':   setEcho(1);
                        putErr(ERR_SUCCESS);
                        return;
            default:    putErr(ERR_BAD_PARAM);
                        return;
        }
    }

    /********************************** ? ***********************************/
    strcpy(cmd_str, "?");

    if ( stricmp(token, cmd_str) == 0 )
    {
        putErr(showHelp());
        return;
    }

    /************************************************************************/
    /***                        start debug commands                      ***/
    /************************************************************************/
    
    /******************************** DEBUG *********************************/
    strcpy(cmd_str, "DEBUG");

    if ( stricmp(token, cmd_str) == 0 )
    {
        token = strtok(NULL, delim);
        switch ( *token )
        {
            case '0':   debugMode = 0;
                        putErr(ERR_SUCCESS);
                        return;
            case '1':   debugMode = 1;
                        putErr(ERR_SUCCESS);
                        return;
            default:    putErr(ERR_BAD_PARAM);
                        return;
        }
    }

    /************************************************************************/
    /***                         end debug commands                       ***/
    /************************************************************************/

    /****************************** END CMDS ********************************/

    putErr(NO_COMMAND_MATCH);
    return;
}

main()
{
    /* set state of digital interface pins on the MAX4355 */
    vsMode(LO);
    vsUpdate(HI);
    vsCS(HI);

    /* initialize the EEPROM */
    eepromInit();

    /* initialize the video switch */
    vsInit();

    showVersion();
        
    /* main loop */
    while(TRUE)
        processCmds();
}
