// main.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <memory.h>
#include "crc.h"
#include "i2c.h"
#include "gff.h"

struct FlashDesc {
  const char* device_name;
  uint32_t    jedec_id;
  uint32_t    size_kb;
  uint32_t    page_size;
  uint32_t    block_size_kb;
};

static const FlashDesc FlashDevices[] = {
    // name,        Jedec ID,    sizeK, page size, block sizeK
    {"AT25DF041A" , 0x1F4401,      512,       256, 64},
    {"AT25DF161"  , 0x1F4602, 2 * 1024,       256, 64},
    {"AT26DF081A" , 0x1F4501, 1 * 1024,       256, 64},
    {"AT26DF0161" , 0x1F4600, 2 * 1024,       256, 64},
    {"AT26DF161A" , 0x1F4601, 2 * 1024,       256, 64},
    {"AT25DF321" ,  0x1F4701, 4 * 1024,       256, 64},
    {"AT25DF512B" , 0x1F6501,       64,       256, 32},
    {"AT25DF512B" , 0x1F6500,       64,       256, 32},
    {"AT25DF021"  , 0x1F3200,      256,       256, 64},
    {"AT26DF641" ,  0x1F4800, 8 * 1024,       256, 64},
    // Manufacturer: ST 
    {"M25P05"     , 0x202010,       64,       256, 32},
    {"M25P10"     , 0x202011,      128,       256, 32},
    {"M25P20"     , 0x202012,      256,       256, 64},
    {"M25P40"     , 0x202013,      512,       256, 64},
    {"M25P80"     , 0x202014, 1 * 1024,       256, 64},
    {"M25P16"     , 0x202015, 2 * 1024,       256, 64},
    {"M25P32"     , 0x202016, 4 * 1024,       256, 64},
    {"M25P64"     , 0x202017, 8 * 1024,       256, 64},
    // Manufacturer: Windbond 
    {"W25X10"     , 0xEF3011,      128,       256, 64},
    {"W25X20"     , 0xEF3012,      256,       256, 64},
    {"W25X40"     , 0xEF3013,      512,       256, 64},
    {"W25X80"     , 0xEF3014, 1 * 1024,       256, 64},
    {"W25Q80BL"   , 0xef4014, 1 * 1024,       256, 64},
    // Manufacturer: Macronix 
    {"MX25L512"   , 0xC22010,       64,       256, 64},
    {"MX25L3205"  , 0xC22016, 4 * 1024,       256, 64},
    {"MX25L6405"  , 0xC22017, 8 * 1024,       256, 64},
    {"MX25L8005"  , 0xC22014,     1024,       256, 64},
    // Microchip
    {"SST25VF512" , 0xBF4800,       64,       256, 32},
    {"SST25VF032" , 0xBF4A00, 4 * 1024,       256, 32},
    {NULL , 0, 0, 0, 0}
};

enum ECommondCommandType {
  E_CC_NOOP = 0,
  E_CC_WRITE = 1,
  E_CC_READ = 2,
  E_CC_WRITE_AFTER_WREN = 3,
  E_CC_WRITE_AFTER_EWSR = 4,
  E_CC_ERASE = 5
};

uint32_t SPICommonCommand(ECommondCommandType cmd_type,
    uint8_t cmd_code,
    uint8_t num_reads,
    uint8_t num_writes,
    uint32_t write_value) {
  num_reads &= 3;
  num_writes &= 3;
  write_value &= 0xFFFFFF;
  uint8_t reg_value = (cmd_type << 5) | 
    (num_writes << 3) |
    (num_reads << 1);

  WriteReg(0x60, reg_value);
  WriteReg(0x61, cmd_code);
  switch (num_writes) {
  case 3:
    WriteReg(0x64, write_value >> 16);
    WriteReg(0x65, write_value >> 8);
    WriteReg(0x66, write_value);
    break;
  case 2:
    WriteReg(0x64, write_value >> 8);
    WriteReg(0x65, write_value);
    break;
  case 1:
    WriteReg(0x64, write_value);
    break;
  }
  WriteReg(0x60, reg_value | 1); // Execute the command
  uint8_t b;
  do {
    b = ReadReg(0x60);
  } while (b & 1);  // TODO: add timeout and reset the controller
  switch (num_reads) {
  case 0: return 0;
  case 1: return ReadReg(0x67);
  case 2: return (ReadReg(0x67) << 8) | ReadReg(0x68);
  case 3: return (ReadReg(0x67) << 16) | (ReadReg(0x68) << 8) | ReadReg(0x69);
  }
  return 0;
}

void SPIRead(uint32_t address, uint8_t *data, int32_t len) {
  WriteReg(0x60, 0x46);
  WriteReg(0x61, 0x3);
  WriteReg(0x64, address>>16);
  WriteReg(0x65, address>>8);
  WriteReg(0x66, address);
  WriteReg(0x60, 0x47); // Execute the command
  uint8_t b;
  do {
    b = ReadReg(0x60);
  } while (b & 1);  // TODO: add timeout and reset the controller
  while (len > 0) {
    int32_t read_len = len;
    if (read_len > 32)
      read_len = 32;
    ReadBytesFromAddr(0x70, data, read_len);
    data += read_len;
    len -= read_len;
  }
}

void PrintManufacturer(uint32_t id) {
  switch (id) {
  case 0x20: printf("ST"); break;
  case 0xef: printf("Winbond"); break;
  case 0x1f: printf("Atmel"); break;
  case 0xc2: printf("Macronix"); break;
  case 0xbf: printf("Microchip"); break;
  default: printf("Unknown");break;
  }
}

static const FlashDesc* FindChip(uint32_t jedec_id) {
  const FlashDesc* chip = FlashDevices;
  while (chip->jedec_id != 0) {
    if (chip->jedec_id == jedec_id)
      return chip;
    chip++;
  }
  return NULL;
}

uint8_t SPIComputeCRC(uint32_t start, uint32_t end) {
  WriteReg(0x64, start >> 16);
  WriteReg(0x65, start >> 8);
  WriteReg(0x66, start);

  WriteReg(0x72, end >> 16);
  WriteReg(0x73, end >> 8);
  WriteReg(0x74, end);

  WriteReg(0x6f, 0x84);
  uint8_t b;
  do
  {
    b = ReadReg(0x6f);
  } while (!(b & 0x2));  // TODO: add timeout and reset the controller
  return ReadReg(0x75);
}

uint8_t GetManufacturerId(uint32_t jedec_id) {
  return jedec_id >> 16;
}

void SetupChipCommands(uint32_t jedec_id) {
  uint8_t manufacturer_id = GetManufacturerId(jedec_id);
  switch (manufacturer_id) {
  case 0xEF:
    // These are the codes for Winbond
    WriteReg(0x62, 0x6);  // Flash Write enable op code
    WriteReg(0x63, 0x50); // Flash Write register op code
    WriteReg(0x6a, 0x3);  // Flash Read op code.
    WriteReg(0x6b, 0xb);  // Flash Fast read op code.
    WriteReg(0x6d, 0x2);  // Flash program op code.
    WriteReg(0x6e, 0x5);  // Flash read status op code.
    break;
  default:
    printf("Can not handle manufacturer code %02x\n", manufacturer_id);
    exit(-6);
    break;
  }
}

bool SaveFlash(const char *output_file_name, uint32_t chip_size) {
  FILE *dump = fopen(output_file_name, "wb");
  uint32_t addr = 0;
  InitCRC();
  do {
    uint8_t buffer[1024];
    printf("Reading addr %x\r", addr);
    SPIRead(addr, buffer, sizeof(buffer));
    fwrite(buffer, 1, sizeof(buffer), dump);
    ProcessCRC(buffer, sizeof(buffer));
    addr += sizeof(buffer);
  } while (addr < chip_size);
  printf("done.\n");
  fclose(dump);
  uint8_t data_crc = GetCRC();
  uint8_t chip_crc = SPIComputeCRC(0, chip_size - 1);
  printf("Received data CRC %02x\n", data_crc);
  printf("Chip CRC %02x\n", chip_crc);
  return data_crc == chip_crc;
}

uint64_t GetFileSize(FILE* file) {
  uint64_t current_pos;
  uint64_t result;
  current_pos = _ftelli64(file);
  fseek(file, 0, SEEK_END);
  result = _ftelli64(file);
  _fseeki64(file, current_pos, SEEK_SET);
  return result;
}

static uint8_t* ReadFile(const char *file_name, uint32_t* size) {
  FILE *file = fopen(file_name, "rb");
  uint8_t* result = NULL;
  if (NULL == file) {
    printf("Can't open input file %s\n", file_name);
    return result;
  }
  uint64_t file_size64 = GetFileSize(file);
  if (file_size64 > 8*1024*1024) {
    printf("This file looks to big %lld\n", file_size64);
    fclose(file);
    return result;
  }
  uint32_t file_size = (uint32_t)file_size64;
  result = new uint8_t[file_size];
  if (NULL == result) {
    printf("Not enough RAM.\n");
    fclose(file);
    return result;
  }
  fread(result, 1, file_size, file);
  fclose(file);
  if (memcmp("GMI GFF V1.0", result, 12) == 0) {
    printf("Detected GFF image.\n");
    // Handle GFF file
    if (file_size < 256) {
      printf("This file looks to small %d\n", file_size);
      delete [] result;
      return NULL;
    }
    uint32_t gff_size = ComputeGffDecodedSize(result + 256,
      file_size - 256);
    if (gff_size == 0) {
      printf("GFF Decoding failed for this file\n");
      delete [] result;
      return NULL;
    }
    uint8_t* gff_data = new uint8_t[gff_size];
    if (NULL == gff_data) {
      printf("Not enough RAM.\n");
      delete [] result;
      return NULL;
    }
    DecodeGff(result + 256, file_size - 256, gff_data);
    // Replace the encoded buffer with the decoded data.
    delete [] result;
    result = gff_data;
    file_size = gff_size;
  }
  if (NULL != size) {
    *size = file_size;
  }
  return result;
}

static bool ShouldProgramPage(uint8_t* buffer, uint32_t size) {
  for (uint32_t idx = 0; idx < size; ++idx) {
    if (buffer[idx] != 0xff) return true;
  }
  return false;
}

bool ProgramFlash(const char *input_file_name, uint32_t chip_size) {
  uint32_t prog_size;
  uint8_t* prog = ReadFile(input_file_name, &prog_size);
  if (NULL == prog) {
    return false;
  }
  printf("Erasing...");fflush(stdout);
  SPICommonCommand(E_CC_WRITE_AFTER_EWSR, 1, 0, 1, 0); // Unprotect the Status Register
  SPICommonCommand(E_CC_WRITE_AFTER_WREN, 1, 0, 1, 0); // Unprotect the flash
  SPICommonCommand(E_CC_ERASE, 0xc7, 0, 0, 0);         // Chip Erase
  printf("done\n");

  //RTD266x can program only 256 bytes at a time.
  uint8_t buffer[256];
  uint8_t b;
  uint32_t addr = 0;
  uint8_t* data_ptr = prog;
  uint32_t data_len = prog_size;
  InitCRC();
  do
  {
    // Wait for programming cycle to finish
    do {
      b = ReadReg(0x6f);
    } while (b & 0x40);

    printf("Writing addr %x\r", addr);
    // Fill with 0xff in case we read a partial buffer.
    memset(buffer, 0xff, sizeof(buffer));
    uint32_t len = sizeof(buffer);
    if (len > data_len) {
      len = data_len;
    }
    memcpy(buffer, data_ptr, len);
    data_ptr += len;
    data_len -= len;

    if (ShouldProgramPage(buffer, sizeof(buffer))) {
      // Set program size-1
      WriteReg(0x71, 255);

      // Set the programming address
      WriteReg(0x64, addr >> 16);
      WriteReg(0x65, addr >> 8);
      WriteReg(0x66, addr);

      // Write the content to register 0x70
      // Out USB gizmo supports max 63 bytes at a time.
      WriteBytesToAddr(0x70, buffer, 63);
      WriteBytesToAddr(0x70, buffer + 63, 63);
      WriteBytesToAddr(0x70, buffer + 126, 63);
      WriteBytesToAddr(0x70, buffer + 189, 63);
      WriteBytesToAddr(0x70, buffer + 252, 4);

      WriteReg(0x6f, 0xa0); // Start Programing
    }
    ProcessCRC(buffer, sizeof(buffer));
    addr += 256;
  } while (addr < chip_size && data_len != 0);
  delete [] prog;

  // Wait for programming cycle to finish
  do {
    b = ReadReg(0x6f);
  } while (b & 0x40);

  SPICommonCommand(E_CC_WRITE_AFTER_EWSR, 1, 0, 1, 0x1c); // Unprotect the Status Register
  SPICommonCommand(E_CC_WRITE_AFTER_WREN, 1, 0, 1, 0x1c); // Protect the flash

  uint8_t data_crc = GetCRC();
  uint8_t chip_crc = SPIComputeCRC(0, addr - 1);
  printf("Received data CRC %02x\n", data_crc);
  printf("Chip CRC %02x\n", chip_crc);
  return data_crc == chip_crc;
}

int main(int argc, char* argv[])
{
  uint8_t b;
  if (!InitI2C()) {
    printf("Can't connect to the USB device. Check the cable.\n");
    return -1;
  }
  printf("Ready\n");
  SetI2CAddr(0x4a);

  const FlashDesc* chip;
  bool cnt;
  do {
    cnt = false;
    if (!WriteReg(0x6f, 0x80)) {  // Enter ISP mode
      printf("Write to 6F failed.\n");
      //return -2;
      cnt = true;
      continue;
    }
    b = ReadReg(0x6f);
    if (!(b & 0x80)) {
      printf("Can't enable ISP mode\n");
      //return -3;
      cnt = true;
      continue;
    }
    uint32_t jedec_id = SPICommonCommand(E_CC_READ, 0x9f, 3, 0, 0);
    printf("JEDEC ID: 0x%02x\n", jedec_id);
    chip = FindChip(jedec_id);
    if (NULL == chip) {
      printf("Unknown chip ID\n");
      cnt = true;
      continue;
    }
  } while(cnt);
  printf("Manufacturer ");
  PrintManufacturer(GetManufacturerId(chip->jedec_id));
  printf("\n");
  printf("Chip: %s\n", chip->device_name);
  printf("Size: %dKB\n", chip->size_kb);

  // Setup flash command codes
  SetupChipCommands(chip->jedec_id);

  b = SPICommonCommand(E_CC_READ, 0x5, 1, 0, 0);
  printf("Flash status register: 0x%02x\n", b);

#if 1
  SaveFlash("flash-test.bin", chip->size_kb * 1024);
#else
  ProgramFlash("backup.bin", chip->size_kb * 1024);
#endif
  CloseI2C();
	return 0;
}
