#include <SPI.h>
#include <GD.h>
#include "debug.h"

#include "reload.h"

void reload()
{
  GD.microcode(reload_code, sizeof(reload_code));
}

static PROGMEM prog_uint32_t crc_table[16] = {
    0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
    0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
    0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
    0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c
};

unsigned long crc_update(unsigned long crc, byte data)
{
    byte tbl_idx;
    tbl_idx = crc ^ (data >> (0 * 4));
    crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);
    tbl_idx = crc ^ (data >> (1 * 4));
    crc = pgm_read_dword_near(crc_table + (tbl_idx & 0x0f)) ^ (crc >> 4);

    return crc;
}

static char signature[64];
void setup()
{
  int i;

  Serial.begin(1000000);

#if 0
  pinMode(SS_PIN, OUTPUT);
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  SPI.setBitOrder(MSBFIRST);
  SPI.setDataMode(SPI_MODE0);
  digitalWrite(SS_PIN, HIGH);

  // Copy ROM signature from 0x3700, if present
  for (i = 0; i < 64; i++)
    signature[i] = GD.rd(0x3700 + i);

  if (0) {
    GD.begin();
    GD.fill(0, 0xfe, 16);
    GD.fill(COMM, 0xfe, 16);
    dump(0, 16);
    reload();
    delay(1000);
    dump(COMM, 16);
    dump(0, 16);
    for (;;)
      ;
  }
#endif

  GD.begin();
  reload();
  delay(500);
  for (i = 0; i < 64; i++)
    signature[i] = GD.rd(0x3700 + i);
  Serial.println(signature);

  GD.wr(IOMODE, 'F');
  GD.ascii();

  GD.putstr(0, 0, "Flash utility");

  // Display ROM signature
  GD.putstr(0, 22, "Rom signature:");
  for (i = 0; i < 64; i++)
    GD.wr(24 * 64 + i, signature[i]);

  pinMode(2, OUTPUT);
  SPI.begin();
  digitalWrite(9, HIGH);
  digitalWrite(2, HIGH);
  delay(1);

  Serial.begin(115200);
}

static byte get1()
{
  while (!Serial.available())
    ;
  return Serial.read();
}

static long bytecount;
static char msg[50];

void loop()
{
  int i;

  byte command = get1();
  sprintf(msg, "cmd: %02x", command);
  GD.putstr(0, 4, msg);
  digitalWrite(2, LOW);

  unsigned long len;
  switch (command) {
    case 0: // send without response
    case 1: // send with responses

      len = get1();
      len = (len << 8) | get1();
      bytecount += len;

      while (len--) {
        byte r = SPI.transfer(get1());
        if (command)
          Serial.write(r);
      }
      Serial.write(0x55);   // end with sync response
      break;

    case 2: // crc
      len = get1();
      len = (len << 8) | get1();
      len = (len << 8) | get1();
      len = (len << 8) | get1();

      SPI.transfer(0x03);
      SPI.transfer(0x00);
      SPI.transfer(0x00);
      SPI.transfer(0x00);
      {
        unsigned long crc = ~0;
        while (len--) {
          crc = crc_update(crc, SPI.transfer(0));
        }
        crc = ~crc;
        Serial.write(crc >> 24);
        Serial.write(crc >> 16);
        Serial.write(crc >> 8);
        Serial.write(crc);
      }
      break;

    case 3: // reload
      reload();
      delay(500);
      for (i = 0; i < 64; i++)
        Serial.write(GD.rd(0xf00 + i));
      GD.wr(IOMODE, 'F');

      break;
  }
  digitalWrite(2, HIGH);

  sprintf(msg, "total: %8ld bytes", bytecount);
  GD.putstr(0, 5, msg);
}
