/*-----------------------------------------------------------------------------
//     $Date: 2004/08/19 17:01:31 $
//     $RCSfile: flasher.c,v $
//-----------------------------------------------------------------------------
//
//     XILINX IS PROVIDING THIS DESIGN, CODE, OR INFORMATION "AS IS"
//     SOLELY FOR USE IN DEVELOPING PROGRAMS AND SOLUTIONS FOR
//     XILINX DEVICES.  BY PROVIDING THIS DESIGN, CODE, OR INFORMATION
//     AS ONE POSSIBLE IMPLEMENTATION OF THIS FEATURE, APPLICATION
//     OR STANDARD, XILINX IS MAKING NO REPRESENTATION THAT THIS
//     IMPLEMENTATION IS FREE FROM ANY CLAIMS OF INFRINGEMENT,
//     AND YOU ARE RESPONSIBLE FOR OBTAINING ANY RIGHTS YOU MAY REQUIRE
//     FOR YOUR IMPLEMENTATION.  XILINX EXPRESSLY DISCLAIMS ANY
//     WARRANTY WHATSOEVER WITH RESPECT TO THE ADEQUACY OF THE
//     IMPLEMENTATION, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OR
//     REPRESENTATIONS THAT THIS IMPLEMENTATION IS FREE FROM CLAIMS OF
//     INFRINGEMENT, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
//     FOR A PARTICULAR PURPOSE.
//     
//     (c) Copyright 2004 Xilinx, Inc.
//     All rights reserved.
//
//---------------------------------------------------------------------------*/

#include "xio.h"
#include "xuartns550_l.h"
#include "xparameters.h"
#include "strataflash.h"

#define DEBUG 1
#if DEBUG
#define debug_print(x)  printf(x)
#define debug_printnum(x)  printnum(x)
#define debug_printch(x) XUartNs550_SendByte(UART_BASEADDR, (x))
#else
#define debug_print(x)
#define debug_printnum(x)
#define debug_printch(x)
#endif

#define UART_BASEADDR XPAR_OPB_UART16550_0_BASEADDR
#define UART_CLOCK    XPAR_XUARTNS550_CLOCK_HZ

#if !SIM
/* #define UART_BAUDRATE 9600                      /* real hardware */
#define UART_BAUDRATE 38400                      /* real hardware */
#else
#define UART_BAUDRATE (UART_CLOCK / 16 / 3)     /* simulation */
#endif

#define BUFSIZE         512*1024
#define ESCAPE          0x1b

unsigned char buf[BUFSIZE];

static void printf(char *s)
{
  while(*s)
  {
    XUartNs550_SendByte(UART_BASEADDR, *s);
    s++;
  }
}

static void printnum(unsigned int val)
{
  char s[10];
  int i;

  s[9] = 0;
  i = 9;
  do
  {
    i--;
    s[i] = (val % 10) + '0';
    val /= 10;
  } while (val != 0);
  printf(&s[i]);
}
    
static void error(char *s, unsigned int line, int exitVal)
{
  printf("error; ");
  if (line > 0)
  {
    printf("in line ");
    printnum(line);
    printf(", ");
  }
  printf(s);
  printf("\r\n");
  exit(exitVal);
}

static unsigned char receiveChar(void)
{
  unsigned char ch;

  ch = XUartNs550_RecvByte(UART_BASEADDR);
  if (ch == ESCAPE)
    error("termination by user interaction", 0, -1);
  return ch;
}

static void receiveS()
{
  unsigned char ch;
  
  do
  {
    ch = receiveChar();
  } while (ch != 'S');
}

static int getByte(unsigned char *val)
{
  unsigned char ch;
  unsigned char i;

  *val = 0;
  for(i=0; i < 2; i++)
  {
    *val = *val << 4;
    ch = receiveChar();
    if ((ch >= '0') && (ch <= '9'))
      *val |= (ch - '0');
    else if ((ch >= 'A') && (ch <= 'F'))
      *val |= (ch - 'A' + 10);
    else
      return -1;
  }
  
  return 0;
}


static int getAddress(unsigned int *adr,
                      unsigned char *crc,
                      unsigned int *len,
                      unsigned char adrLen)
{
  int res;
  unsigned char val;
  
  *adr = 0;
  while (adrLen > 0)
  {
    res = getByte(&val);
    if (res != 0)
      return res;
    buf[*len] = val;
    (*len)++;
    *crc += val;
    *adr = ((*adr) << 8) | val;
    adrLen--;
  }
  return 0;
}

static int download(unsigned int *len)
{
  int res;
  unsigned char crc, checkCrc;
  unsigned char type;
  unsigned char count;
  unsigned char adrLen;
  unsigned int adr;
  unsigned int line;

  printf("downloading: please send s-records or press ESC to abort...\r\n");

  line = 0;
  while(1)
  {
    line++;
    receiveS();
    type = receiveChar();
    switch(type)
    {
    case '0':
    case '5':
      continue;
      break;
    case '7':
    case '8':
    case '9':
      return 0;
      break;
    case '1':
    case '2':
    case '3':
      break;
    default:
      error("invalid type", line, -1);
      return -1;
    }
    adrLen = type - '0' + 1;
    buf[*len] = adrLen;
    (*len)++;
    
    res = getByte(&count);
    if (res != 0)
      error("invalid byte in size field", line, res);
    crc = count;
    count -= (adrLen + 1);
    buf[*len] = count;
    (*len)++;

    res = getAddress(&adr, &crc, len, adrLen);

    if (res != 0)
      error("invalid address", line, res);
    if (adr & 3)
      error("address must be word-aligned (implementation restriction)", line, -1);
    if (count & 3)
      error("size must be a multiple of a word (implementation restriction)", line, -1);
    if((adr < strataflash_BaseAdr) ||
       ((adr + count - 1) > (strataflash_BaseAdr + strataflash_Size)))
      error("address outside flash address space", line, -1);

    while (count > 0)
    {
      res = getByte(&buf[*len]);
      if (res != 0) {
        error("invalid byte in data field", line, res);
      }
      crc += buf[*len];
      (*len)++;
      count--;
    }

    res = getByte(&checkCrc);
    if (res != 0)
      error("invalid byte in crc field", line, res);
    if (((unsigned char) ~checkCrc) != crc)
      error("crc check failed", line, -1);
  }
  printf("download completed successfully\r\n");
  return 0;
}

static int program(unsigned int len)
{
  int res;
  unsigned int i, j;
  unsigned char adrLen;
  unsigned int adr;
  unsigned int count, cnt;
  unsigned int firstBlock, lastBlock, curBlock;
  
  printf("programming: please wait...\r\n");
  curBlock = 0xffffffff;
  i = 0;
  while (i < len)
  {
    adrLen = buf[i++];
    count = buf[i++];

    adr = 0;
    for (j = 0; j < adrLen; j++)
      adr = (adr << 8) | buf[i++];
    
    res = strataflash_AddressToBlock(adr, &firstBlock);
    if (res != 0)
      error("address to block translation for first block failed", 0, res);
    res = strataflash_AddressToBlock(adr+count-1, &lastBlock);
    if (res != 0)
      error("address to block translation for last block failed", 0, res);
    do
    {
      if (firstBlock != curBlock)
      {
        strataflash_EraseBlock(firstBlock);
        curBlock = firstBlock;
      }
      firstBlock++;
    } while (firstBlock <= lastBlock);
    
    cnt = strataflash_Write(adr, &buf[i], count);
    if (cnt != count)
      error("programming failed due to incomplete write operation", 0, -1);
    i += count;
  }
  printf("programming completed\r\n");
  return 0;
}

static int verify(unsigned int len)
{
  unsigned int i, j;
  unsigned char adrLen;
  unsigned int adr;
  unsigned char got;
  unsigned int count;
  
  printf("verifying: please wait...\r\n");
  i = 0;
  while (i < len)
  {
    adrLen = buf[i++];
    count = buf[i++];

    adr = 0;
    for (j = 0; j < adrLen; j++)
      adr = (adr << 8) | buf[i++];
    
    while (count > 0)
    {
      got = XIo_In8(adr);
      if (buf[i] != got)
      {
        printf("address: ");
        printnum(adr);
        printf("; expected: ");
        printnum(buf[i]);
        printf(", got: ");
        printnum(got);
        printf("\r\n");
        error("veryfying failed", 0, -1);
      }
      adr++;
      count--;
      i++;
    }
  }
  printf("verifying completed successfully\r\n");
  return 0;
}

int main()
{
  int res;
  unsigned int len = 0;
  
  XUartNs550_SetBaud(UART_BASEADDR, UART_CLOCK, UART_BAUDRATE);
  XUartNs550_mSetLineControlReg(UART_BASEADDR, XUN_LCR_8_DATA_BITS);

  printf("Xilinx Simple Flash Programmer\r\n");
  printf("==============================\r\n");
  
  res = download(&len);
  if (res != 0)
    error("unknown error", 0, -1);

  res = program(len);
  if (res != 0)
    error("unknown error", 0, -1);
  
  res = verify(len);
  if (res != 0)
    error("unknown error", 0, -1);
  
  return 0;
}
