//******************************************
// FLASHROM MODULE
// (also includes SNES repro functions)
//******************************************
#ifdef ENABLE_FLASH8

/******************************************
   Variables
 *****************************************/
// Flashrom
unsigned long flashSize;
byte flashromType;
byte secondID = 1;
unsigned long time;
unsigned long blank;
unsigned long sectorSize;
uint16_t bufferSize;
byte mapping = 0;
boolean byteCtrl = 0;

/******************************************
   Menu
 *****************************************/
// General Flash menu items
static const char flashMenuItemBlankcheck[] PROGMEM = "Blankcheck";
static const char flashMenuItemID[] PROGMEM = "ID";
static const char flashMenuItemRead[] PROGMEM = "Read";
static const char flashMenuItemWrite[] PROGMEM = "Write";
static const char flashMenuItemErase[] PROGMEM = "Erase";
static const char flashMenuItemPrint[] PROGMEM = "Print";

// 8bit Flash menu items
static const char* const menuOptionsFLASH8[] PROGMEM = { flashMenuItemBlankcheck, flashMenuItemErase, flashMenuItemRead, flashMenuItemWrite, flashMenuItemID, flashMenuItemPrint, FSTRING_RESET };

#ifndef ENABLE_FLASH16
// Flash mode menu
static const char modeMenuItem1[] PROGMEM = "CFI Mode";
static const char modeMenuItem2[] PROGMEM = "Standard Mode";
static const char modeMenuItem3[] PROGMEM = "PLCC32 to SNES";
static const char* const menuOptionsMode[] PROGMEM = { modeMenuItem1, modeMenuItem2, modeMenuItem3, FSTRING_RESET };
#endif

// Misc flash strings
const char PROGMEM ATTENTION_3_3V[] = "ATTENTION 3.3V";

#ifdef ENABLE_FLASH16
// Flash start menu
static const char flashMenuItem1[] PROGMEM = "CFI";
static const char flashMenuItem2[] PROGMEM = "8bit Flash";
static const char flashMenuItem3[] PROGMEM = "PLCC32 to SNES";
static const char flashMenuItem4[] PROGMEM = "Eprom";
static const char flashMenuItem5[] PROGMEM = "16bit Flash";
static const char* const menuOptionsFlash[] PROGMEM = { flashMenuItem1, flashMenuItem2, flashMenuItem3, flashMenuItem4, flashMenuItem5, FSTRING_RESET };

// 16bit Flash menu items
static const char* const menuOptionsFLASH16[] PROGMEM = { flashMenuItemBlankcheck, flashMenuItemErase, flashMenuItemRead, flashMenuItemWrite, flashMenuItemID, flashMenuItemPrint, FSTRING_RESET };

// Eprom menu items
static const char epromMenuItem4[] PROGMEM = "Verify";
static const char* const menuOptionsEprom[] PROGMEM = { flashMenuItemBlankcheck, flashMenuItemRead, flashMenuItemWrite, epromMenuItem4, flashMenuItemPrint, FSTRING_RESET };

void flashMenu() {
  display_Clear();
  display_Update();
  mapping = 0;

  // create menu with title and 6 options to choose from
  unsigned char flashSlot;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFlash, 6);
  flashSlot = question_box(F("Select Mode"), menuOptions, 6, 0);

  // wait for user choice to come back from the question box menu
  switch (flashSlot) {
    case 0:
      setupCFI();
      flashSize = 8388608;
      writeCFI_Flash(1, 1, 0);
      verifyFlash();
      print_STR(press_button_STR, 0);
      display_Update();
      wait();
      resetArduino();
      break;

    case 1:
      setup_Flash8();
      id_Flash8();
      wait();
      mode = CORE_FLASH8;
      break;

    case 2:
      mapping = 3;
      setup_Flash8();
      id_Flash8();
      wait();
      mode = CORE_FLASH8;
      break;

    case 3:
      setup_Eprom();
      mode = CORE_EPROM;
      break;

    case 4:
      setup_Flash16();
      id_Flash16();
      wait();
      mode = CORE_FLASH16;
      break;

    case 5:
      resetArduino();
      break;

    default:
      print_MissingModule();  // does not return
  }
}
#else
void flashMenu() {
  display_Clear();
  display_Update();
  mapping = 0;

  // create menu with title and 4 options to choose from
  unsigned char flashMode;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsMode, 4);
  flashMode = question_box(F("Select Flash Mode"), menuOptions, 4, 0);

  // wait for user choice to come back from the question box menu
  switch (flashMode) {
    case 0:
      setupCFI();
      flashSize = 8388608;
      writeCFI_Flash(1, 1, 0);
      verifyFlash();
      print_STR(press_button_STR, 0);
      display_Update();
      wait();
      resetArduino();
      break;

    case 1:
      setup_Flash8();
      id_Flash8();
      wait();
      mode = CORE_FLASH8;
      break;

    case 2:
      mapping = 3;
      setup_Flash8();
      id_Flash8();
      wait();
      mode = CORE_FLASH8;
      break;

    case 3:
      resetArduino();
      break;

    default:
      print_MissingModule();  // does not return
  }
}
#endif

void setupCFI() {
  display_Clear();
  display_Update();
  filePath[0] = '\0';
  sd.chdir("/");
  fileBrowser(F("Select file"));
  display_Clear();
  setup_Flash8();
  identifyCFI_Flash();
  sprintf(filePath, "%s/%s", filePath, fileName);
  display_Clear();
}

void readOnlyMode() {
  display_Clear();
  println_Msg(FS(FSTRING_EMPTY));
  println_Msg(FS(FSTRING_EMPTY));
  println_Msg(F("Read-only Mode!"));
  println_Msg(FS(FSTRING_EMPTY));
  println_Msg(FS(FSTRING_EMPTY));
  display_Update();
}

void flashromMenu8() {
  // create menu with title and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFLASH8, 7);
  mainMenu = question_box(F("Flashrom Writer 8"), menuOptions, 7, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      resetFlash8();
      blankcheck_Flash();
      break;

    case 1:
      if (flashromType != 0) {
        display_Clear();
        println_Msg(F("Warning: This will erase"));
        println_Msg(F("your flashrom/repro"));
        print_STR(press_button_STR, 1);
        display_Update();
        wait();
        rgbLed(black_color);
        println_Msg(FS(FSTRING_EMPTY));
        println_Msg(F("Please wait..."));
        display_Update();
        time = millis();

        switch (flashromType) {
          case 1: eraseFlash29F032(); break;
          case 2: eraseFlash29F1610(); break;
          case 3: eraseFlash28FXXX(); break;
        }

        println_Msg(F("Flashrom erased"));
        display_Update();
        resetFlash8();
      } else {
        readOnlyMode();
      }
      break;

    case 2:
      time = millis();
      resetFlash8();
      readFlash();
      break;

    case 3:
      if (flashromType != 0) {
        filePath[0] = '\0';
        sd.chdir("/");
        fileBrowser(FS(FSTRING_SELECT_FILE));
        display_Clear();
        time = millis();

        switch (flashromType) {
          case 1:
            writeFlash29F032();
            break;
          case 2:
            if (flashid == 0xC2F3)
              writeFlash29F1601();
            else if ((flashid == 0xC2F1) || (flashid == 0xC2F9))
              writeFlash29F1610();
            else if ((flashid == 0xC2C4) || (flashid == 0xC249) || (flashid == 0xC2A7) || (flashid == 0xC2A8) || (flashid == 0xC2C9) || (flashid == 0xC2CB) || (flashid == 0x0149) || (flashid == 0x01C4) || (flashid == 0x01F9) || (flashid == 0x01F6) || (flashid == 0x01D7))
              writeFlash29LV640();
            else if (flashid == 0x017E)
              writeFlash29GL(sectorSize, bufferSize);
            else if ((flashid == 0x0458) || (flashid == 0x0158) || (flashid == 0x01AB) || (flashid == 0x0422) || (flashid == 0x0423))
              writeFlash29F800();
            else if (flashid == 0x0)  // Manual flash config, pick most common type
              writeFlash29LV640();
            break;
          case 3:
            writeFlash28FXXX();
            break;
        }

        delay(100);

        // Reset twice just to be sure
        resetFlash8();
        resetFlash8();

        verifyFlash();
      } else {
        readOnlyMode();
      }
      break;

    case 4:
      time = 0;
      display_Clear();
      resetFlash8();
      println_Msg(F("ID Flashrom"));
      switch (flashromType) {
        case 0: break;
        case 1: idFlash29F032(); break;
        case 2: idFlash29F1610(); break;
        case 3: idFlash28FXXX(); break;
      }
      println_Msg(FS(FSTRING_EMPTY));
      printFlash(40);
      println_Msg(FS(FSTRING_EMPTY));
      display_Update();
      resetFlash8();
      break;

    case 5:
      time = 0;
      display_Clear();
      println_Msg(F("Print first 70Bytes"));
      display_Update();
      resetFlash8();
      printFlash(70);
      break;

    case 6:
      time = 0;
      display_Clear();
      display_Update();
      resetFlash8();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took : "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 0);
  display_Update();
  wait();
}

#ifdef ENABLE_FLASH16
void flashromMenu16() {
  // create menu with title "Flashrom Writer 16" and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFLASH16, 7);
  mainMenu = question_box(F("Flashrom Writer 16"), menuOptions, 7, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      resetFlash16();
      blankcheck16();
      break;

    case 1:
      display_Clear();
      println_Msg(F("Erase Flashrom"));
      display_Update();
      time = millis();
      resetFlash16();
      eraseFlash16();
      println_Msg(F("Flashrom erased."));
      display_Update();
      break;

    case 2:
      display_Clear();
      time = millis();
      resetFlash16();
      readFlash16();
      break;

    case 3:
      filePath[0] = '\0';
      sd.chdir("/");
      fileBrowser(FS(FSTRING_SELECT_FILE));
      display_Clear();
      time = millis();
      if (flashid == 0xC2F3) {
        writeFlash16_29F1601();
      } else if ((flashid == 0xC2C4) || (flashid == 0xC249) || (flashid == 0xC2A7) || (flashid == 0xC2A8) || (flashid == 0xC2C9) || (flashid == 0xC2CB) || (flashid == 0x0149) || (flashid == 0x01C4) || (flashid == 0x01F9) || (flashid == 0x01F6) || (flashid == 0x01D7) || (flashid == 0xC2FC)) {
        writeFlash16_29LV640();
      } else {
        writeFlash16();
      }
      delay(100);
      resetFlash16();
      delay(100);
      verifyFlash16();
      break;

    case 4:
      time = 0;
      display_Clear();
      println_Msg(F("ID Flashrom"));
      idFlash16();
      println_Msg(FS(FSTRING_EMPTY));
      printFlash16(40);
      println_Msg(FS(FSTRING_EMPTY));
      display_Update();
      resetFlash16();
      break;

    case 5:
      time = 0;
      display_Clear();
      println_Msg(F("Print first 70Bytes"));
      display_Update();
      resetFlash16();
      printFlash16(70);
      break;

    case 6:
      time = 0;
      display_Clear();
      display_Update();
      resetFlash16();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took: "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  wait();
}

void epromMenu() {
  // create menu with title "Eprom Writer" and 4 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsEprom, 6);
  mainMenu = question_box(F("Eprom Writer"), menuOptions, 6, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      blankcheck_Eprom();
      break;

    case 1:
      display_Clear();
      time = millis();
      read_Eprom();
      break;

    case 2:
      filePath[0] = '\0';
      sd.chdir("/");
      fileBrowser(FS(FSTRING_SELECT_FILE));
      display_Clear();
      time = millis();
      write_Eprom();
      delay(1000);
      verify_Eprom();
      break;

    case 3:
      filePath[0] = '\0';
      sd.chdir("/");
      fileBrowser(F("Verify against"));
      sprintf(filePath, "%s/%s", filePath, fileName);
      display_Clear();
      time = millis();
      verify_Eprom();
      break;

    case 4:
      display_Clear();
      time = millis();
      print_Eprom(80);
      break;

    case 5:
      time = 0;
      display_Clear();
      display_Update();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took: "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  wait();
}
#endif

/******************************************
   Flash ID
 *****************************************/
void printFlashSize(int index) {
  display_Clear();
  print_Msg(F("Flashsize: "));
  print_Msg(index);
  println_Msg(F("MB"));
}

void printFlashType(int index) {
  display_Clear();
  print_Msg(F("Flashtype: "));
  println_Msg(index);
}

byte selectFlashtype(boolean option) {
  byte selectionByte;
  if (option)
    selectionByte = navigateMenu(0, 3, &printFlashType);
  else
    selectionByte = navigateMenu(1, 8, &printFlashSize);
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display.setCursor(0, 56);  // Display selection at bottom
#endif
  if (option) {
    print_Msg(F("Flash Type: "));
    println_Msg(selectionByte);
  } else {
    print_Msg(F("Flash Size: "));
    print_Msg(selectionByte);
    println_Msg(F("MB"));
  }
  display_Update();
  delay(200);
  return selectionByte;
}

void id_Flash8() {
  // Test if 28FXXX series flash (type 3 flashrom)
  idFlash28FXXX();

  // Print start screen
idtheflash:
  display_Clear();
  display_Update();
  println_Msg(F("Flashrom Writer 8bit"));
  println_Msg("");
  println_Msg("");
  print_Msg(F("Flash ID: "));
  println_Msg(flashid_str);

  if (flashid == 0xC2F1) {
    println_Msg(F("MX29F1610 detected"));
    flashSize = 2097152;
    flashromType = 2;
  } else if (flashid == 0xC2F3) {
    println_Msg(F("MX29F1601 detected"));
    flashSize = 2097152;
    flashromType = 2;
  } else if (flashid == 0xC2F9) {
    println_Msg(F("MX29L3211 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if ((flashid == 0xC2C4) || (flashid == 0xC249)) {
    println_Msg(F("MX29LV160 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 2097152;
    flashromType = 2;
  } else if ((flashid == 0xC2A7) || (flashid == 0xC2A8)) {
    println_Msg(F("MX29LV320 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if ((flashid == 0xC2C9) || (flashid == 0xC2CB)) {
    println_Msg(F("MX29LV640 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 8388608;
    flashromType = 2;
  } else if ((flashid == 0x0149) || (flashid == 0x01C4)) {
    println_Msg(F("AM29LV160 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 2097152;
    flashromType = 2;
  } else if ((flashid == 0x01F9) || (flashid == 0x01F6)) {
    println_Msg(F("AM29LV320 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if (flashid == 0x01D7) {
    println_Msg(F("AM29LV640 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 8388608;
    flashromType = 2;
  } else if (flashid == 0x0141) {
    println_Msg(F("AM29F032B detected"));
    flashSize = 4194304;
    flashromType = 1;
  } else if (flashid == 0x01AD) {
    println_Msg(F("AM29F016B detected"));
    flashSize = 2097152;
    flashromType = 1;
  } else if (flashid == 0x20AD) {
    println_Msg(F("AM29F016D detected"));
    flashSize = 2097152;
    flashromType = 1;
  } else if (flashid == 0x04AD) {
    println_Msg(F("MBM29F016A detected"));
    flashSize = 2097152;
    flashromType = 1;
  } else if (flashid == 0x04D4) {
    println_Msg(F("MBM29F033C detected"));
    flashSize = 4194304;
    flashromType = 1;
  } else if (flashid == 0x04D5) {
    println_Msg(F("MBM29F080C detected"));
    flashSize = 1048576;
    flashromType = 1;
  } else if (flashid == 0x0458) {
    println_Msg(F("MBM29F800BA detected"));
    flashSize = 1048576;
    flashromType = 2;
  } else if (flashid == 0x01AB) {
    println_Msg(F("AM29F400AB detected"));
    flashSize = 131072 * 4;
    flashromType = 2;
  } else if (flashid == 0x0423) {
    println_Msg(F("MBM29F400TC detected"));
    flashSize = 131072 * 4;
    flashromType = 2;
  } else if (flashid == 0x0422) {
    println_Msg(F("MBM29F400BC detected"));
    flashSize = 131072 * 4;
    flashromType = 2;
  } else if (flashid == 0x0158) {
    println_Msg(F("AM29F800BB detected"));
    flashSize = 1048576;
    flashromType = 2;
  } else if (flashid == 0x01A3) {
    println_Msg(F("AM29LV033C detected"));
    flashSize = 131072 * 32;
    flashromType = 1;
  } else if (flashid == 0x017E) {
    // S29GL032M
    if (readByte_Flash(28) == 0x1A) {
      println_Msg(F("S29GL032M detected"));
      flashSize = 4194304;
      sectorSize = 65536;
      bufferSize = 32;
    }
    // S29GL064M
    else if (readByte_Flash(28) == 0x10) {
      println_Msg(F("S29GL064M detected"));
      flashSize = 8388608;
      sectorSize = 65536;
      bufferSize = 32;
    }
    // Unknown S29GL type
    else {
      println_Msg(F("Unknown S29GL Type"));
      flashSize = 8388608;
      sectorSize = 65536;
      bufferSize = 32;
    }
    println_Msg(FS(ATTENTION_3_3V));
    flashromType = 2;
  } else if (flashid == 0xB088) {
    // LH28F016SUT
    println_Msg(F("LH28F016SUT detected"));
    println_Msg(F("ATTENTION 3/5 setting"));
    flashSize = 2097152;
    sectorSize = 65536;
    bufferSize = 256;
    flashromType = 3;
  } else if ((flashid == 0x8916) || (flashid == 0x8917) || (flashid == 0x8918)) {
    // E28FXXXJ3A
    print_Msg(F("E28F"));
    switch (flashid & 0x00f0) {
      case 0x60:
        flashSize = 131072 * 32;
        print_Msg(F("320"));
        break;
      case 0x70:
        flashSize = 131072 * 64;
        print_Msg(F("640"));
        break;
      case 0x80:
        flashSize = 131072 * 128;
        print_Msg(F("128"));
        break;
    }
    println_Msg(F("J3A detected"));
    sectorSize = 131072;
    bufferSize = 32;
    flashromType = 3;
  } else if (flashid == 0xBFB7) {
    println_Msg(F("39F040 detected"));
    flashSize = 524288;
    flashromType = 1;
  } else if (flashid == 0xBFB6) {
    println_Msg(F("39F020 detected"));
    flashSize = 262144;
    flashromType = 1;
  } else if (flashid == 0xBFB5) {
    println_Msg(F("39F010 detected"));
    flashSize = 131072;
    flashromType = 1;
  } else if (secondID == 1) {
    // Read ID a second time using a different command (type 1 flashrom)
    resetFlash8();
    idFlash29F032();
    secondID = 2;
    goto idtheflash;
  } else if (secondID == 2) {
    // Backup first ID read-out
    strncpy(vendorID, flashid_str, 5);
    // Read ID a third time using a different command (type 2 flashrom)
    resetFlash8();
    idFlash29F1610();
    secondID = 3;
    goto idtheflash;
  } else if (secondID == 3) {
    idFlash39SF040();
    secondID = 0;
    goto idtheflash;
  } else {
    // ID not found
    display_Clear();
    println_Msg(F("Flashrom Writer 8bit"));
    println_Msg("");
    print_Msg(F("ID Type 1: "));
    println_Msg(vendorID);
    print_Msg(F("ID Type 2: "));
    println_Msg(flashid_str);
    println_Msg("");
    println_Msg(F("UNKNOWN FLASHROM"));
    println_Msg("");
    // Prints string out of the common strings array either with or without newline
    print_Error(press_button_STR);
    display_Update();
    wait();

    // Select flashrom config manually
    flashSize = selectFlashtype(0) * 1024UL * 1024UL;
    flashromType = selectFlashtype(1);
    flashid = 0;
    sprintf(flashid_str, "%04X", 0);

    // print first 40 bytes of flash
    display_Clear();
    println_Msg(F("First 40 bytes:"));
    println_Msg(FS(FSTRING_EMPTY));
    printFlash(40);
    resetFlash8();
  }
  println_Msg(FS(FSTRING_EMPTY));
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 1);
  display_Update();
  resetFlash8();
}

#ifdef ENABLE_FLASH16
void id_Flash16() {
  // ID flash
  idFlash16();
  resetFlash16();

  display_Clear();
  display_Update();
  println_Msg(F("Flashrom Writer 16bit"));
  println_Msg("");
  print_Msg(F("Flash ID: "));
  println_Msg(flashid_str);
  if (flashid == 0xC2F1) {
    println_Msg(F("MX29F1610 detected"));
    println_Msg("");
    flashSize = 2097152;
    flashromType = 2;
  } else if (flashid == 0xC2F3) {
    println_Msg(F("MX29F1601 detected"));
    flashSize = 2097152;
    flashromType = 2;
  } else if (flashid == 0xC2F9) {
    println_Msg(F("MX29L3211 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if ((flashid == 0xC2C4) || (flashid == 0xC249)) {
    println_Msg(F("MX29LV160 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 2097152;
    flashromType = 2;
  } else if ((flashid == 0xC2A7) || (flashid == 0xC2A8)) {
    println_Msg(F("MX29LV320 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if ((flashid == 0xC2C9) || (flashid == 0xC2CB)) {
    println_Msg(F("MX29LV640 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 8388608;
    flashromType = 2;
  } else if ((flashid == 0x0149) || (flashid == 0x01C4)) {
    println_Msg(F("AM29LV160 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 2097152;
    flashromType = 2;
  } else if ((flashid == 0x01F9) || (flashid == 0x01F6)) {
    println_Msg(F("AM29LV320 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 4194304;
    flashromType = 2;
  } else if (flashid == 0x01D7) {
    println_Msg(F("AM29LV640 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 8388608;
    flashromType = 2;
  } else if (flashid == 0xC2FC) {
    println_Msg(F("MX26L6420 detected"));
    println_Msg(FS(ATTENTION_3_3V));
    flashSize = 8388608;
    flashromType = 2;
  } else {
    print_FatalError(F("Unknown flashrom"));
    println_Msg("");
  }
  println_Msg("");
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 1);
  display_Update();
}
#endif

/******************************************
   Setup
 *****************************************/
#if defined(ENABLE_VSELECT) || defined(ENABLE_3V3FIX)
static const char flashvoltItem1[] PROGMEM = "3.3V";
static const char flashvoltItem2[] PROGMEM = "5V";
static const char* const flashvoltOptions[] PROGMEM = { flashvoltItem1, flashvoltItem2, FSTRING_RESET };

void setup_FlashVoltage() {
  // create menu with title and 3 options to choose from
  unsigned char flashvolt;
  // Copy menuOptions out of progmem
  convertPgm(flashvoltOptions, 3);
  flashvolt = question_box(F("Select Flash Voltage"), menuOptions, 3, 0);

  // wait for user choice to come back from the question box menu
  switch (flashvolt) {
    case 0:
      // Request 3.3V
      setVoltage(VOLTS_SET_3V3);
      break;

    case 1:
      // Request 5V
      setVoltage(VOLTS_SET_5V);
      break;

    case 2:
      resetArduino();
      break;
  }
}
#else
// The compiler will optimize this out when this condition is met.
void setup_FlashVoltage() {}
#endif

void setup_Flash8() {
  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //A16-A23
  DDRL = 0xFF;

  // Set Control Pins to Output RST(PH0) OE(PH1) OE_SNS(PH3) WE(PH4) WE_SNS(PH5) CE(PH6)
  DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
  // Setting RST(PH0) OE(PH1) OE_SNS(PH3) WE(PH4) WE_SNS(PH5) HIGH
  PORTH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5);
  // Setting CE(PH6) LOW
  PORTH &= ~(1 << 6);

  // Set Data Pins (D0-D7) to Input
  DDRC = 0x00;
  // Disable Internal Pullups
  PORTC = 0x00;
}

#ifdef ENABLE_FLASH16
void setup_Flash16() {
  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //A16-A23
  DDRL = 0xFF;

  // Set Control Pins to Output RST(PH0) OE(PH1) BYTE(PH3) WE(PH4) WP(PH5) CE(PH6)
  DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);

  // Set Data Pins (D0-D15) to Input
  DDRC = 0x00;
  DDRA = 0x00;
  // Disable Internal Pullups
  PORTC = 0x00;
  PORTA = 0x00;

  // Setting RST(PH0) OE(PH1) BYTE(PH3) WE(PH4) WP(PH5) HIGH
  PORTH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5);
  // Setting CE(PH6) LOW
  PORTH &= ~(1 << 6);

  delay(100);
}

void setup_Eprom() {
  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //A16-A23
  DDRL = 0xFF;

  // Set Data Pins (D0-D15) to Input
  DDRC = 0x00;
  DDRA = 0x00;
  // Disable Internal Pullups
  PORTC = 0x00;
  PORTA = 0x00;

  // Set Control Pins to Output VPP/OE(PH5) CE(PH6)
  DDRH |= (1 << 5) | (1 << 6);

  // Setting CE(PH6) HIGH
  PORTH |= (1 << 6);
  // Setting VPP/OE(PH5) LOW
  PORTH &= ~(1 << 5);

  // 27C322 is a 4MB eprom
  flashSize = 4194304;
}
#endif

/******************************************
   I/O Functions
 *****************************************/
// Switch data pins to read
void dataIn8() {
  // Set to Input
  DDRC = 0x00;
}

#ifdef ENABLE_FLASH16
// Switch data pins to write
void dataOut16() {
  DDRC = 0xFF;
  DDRA = 0xFF;
}

// Switch data pins to read
void dataIn16() {
  DDRC = 0x00;
  DDRA = 0x00;
}
#endif

/******************************************
   Low level functions
 *****************************************/
void writeByte_Flash(unsigned long myAddress, byte myData) {
  // A0-A7
  PORTF = myAddress & 0xFF;

  // flash adapter (without SRAM save chip)
  if (mapping == 0) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
  }
  /* SNES maskrom flash adapter combined with PLCC32 adapter
  SNES A19 -> 32PLCC A17
  SNES A18 -> 32PLCC A16
  SNES A17 -> 32PLCC A18
  SNES A16 -> 32PLCC OE
  SNES CS/Flash OE -> 32PLCC WE
  */
  else if (mapping == 3) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16
    if (!(((myAddress >> 16) & 0xFF) & 0x1)) {
      PORTL &= ~(1 << 2);
    } else if (((myAddress >> 16) & 0xFF) & 0x1) {
      PORTL |= (1 << 2);
    }
    // A17
    if (!(((myAddress >> 16) & 0xFF) & 0x2)) {
      PORTL &= ~(1 << 3);
    } else if (((myAddress >> 16) & 0xFF) & 0x2) {
      PORTL |= (1 << 3);
    }
    // A18
    if (!(((myAddress >> 16) & 0xFF) & 0x4)) {
      PORTL &= ~(1 << 1);
    } else if (((myAddress >> 16) & 0xFF) & 0x4) {
      PORTL |= (1 << 1);
    }
    // Switch PLCC32 OE to HIGH to disable output
    PORTL |= (1 << 0);
  }
  // SNES LoRom
  else if (mapping == 1) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
  }
  // SNES HiRom
  else if (mapping == 2) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
    // Set PL7 to value of PL6
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 0
      PORTL &= ~(1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 1
      PORTL |= (1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES LoRom repro with 2x 2MB
  else if (mapping == 122) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip BA6(PL6) to address second rom chip
    PORTL ^= (1 << 6);
  }
  // for SNES HiRom repro with 2x 2MB
  else if (mapping == 222) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
    // Flip BA5(PL5) to address second rom chip
    PORTL ^= (1 << 5);
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES ExLoRom repro with 2x 4MB
  else if (mapping == 124) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip A22(PL7) to reverse P0 and P1 roms
    PORTL ^= (1 << 7);
  }
  // for SNES ExHiRom repro with 2x 4MB
  else if (mapping == 224) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A22
    PORTL = (myAddress >> 16) & 0xFF;
    // Set PL7 to inverse of PL6 to reverse P0 and P1 roms
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 1
      PORTL |= (1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 0
      PORTL &= ~(1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES ExLoRom repro with 4x 2MB
  else if (mapping == 142) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip BA6(PL6) to address second rom chip
    PORTL ^= (1 << 6);
    // Flip A22(PL7) to reverse P0 and P1 roms
    PORTL ^= (1 << 7);
  }
  // for SNES ExHiRom repro with 4x 2MB
  else if (mapping == 242) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A22
    PORTL = (myAddress >> 16) & 0xFF;
    // Flip BA5(PL5) to address second rom chip
    PORTL ^= (1 << 5);
    // Set PL7 to inverse of PL6 to reverse P0 and P1 roms
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 1
      PORTL |= (1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 0
      PORTL &= ~(1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }

  // Data
  PORTC = myData;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  // Wait till output is stable
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  if (mapping == 3)
    // Switch PLCC32 WE to LOW
    PORTH &= ~(1 << 3);
  else
    // Switch WE(PH4) WE_SNS(PH5) to LOW
    PORTH &= ~((1 << 4) | (1 << 5));

  // Leave WE low for at least 60ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  if (mapping == 3)
    // Switch PLCC32 WE to HIGH
    PORTH |= (1 << 3);
  else
    // Switch WE(PH4) WE_SNS(PH5) to HIGH
    PORTH |= (1 << 4) | (1 << 5);

  // Leave WE high for at least 50ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");
}

byte readByte_Flash(unsigned long myAddress) {
  // A0-A7
  PORTF = myAddress & 0xFF;

  // flash adapter (without SRAM save chip)
  if (mapping == 0) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
  }
  /* SNES maskrom flash adapter combined with PLCC32 adapter
  SNES A19 -> 32PLCC A17
  SNES A18 -> 32PLCC A16
  SNES A17 -> 32PLCC A18
  SNES A16 -> 32PLCC OE
  SNES CS/Flash OE -> 32PLCC WE
  */
  else if (mapping == 3) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16
    if (!(((myAddress >> 16) & 0xFF) & 0x1)) {
      PORTL &= ~(1 << 2);
    } else if (((myAddress >> 16) & 0xFF) & 0x1) {
      PORTL |= (1 << 2);
    }
    // A17
    if (!(((myAddress >> 16) & 0xFF) & 0x2)) {
      PORTL &= ~(1 << 3);
    } else if (((myAddress >> 16) & 0xFF) & 0x2) {
      PORTL |= (1 << 3);
    }
    // A18
    if (!(((myAddress >> 16) & 0xFF) & 0x4)) {
      PORTL &= ~(1 << 1);
    } else if (((myAddress >> 16) & 0xFF) & 0x4) {
      PORTL |= (1 << 1);
    }
    // Switch PLCC32 WE to HIGH to disable writing
    PORTH |= (1 << 3);
  }
  // SNES LoRom
  else if (mapping == 1) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
  }
  // SNES HiRom
  else if (mapping == 2) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
    // Set PL7 to value of PL6
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 0
      PORTL &= ~(1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 1
      PORTL |= (1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES LoRom repro with 2x 2MB
  else if (mapping == 122) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip BA6(PL6) to address second rom chip
    PORTL ^= (1 << 6);
  }
  // for SNES HiRom repro with 2x 2MB
  else if (mapping == 222) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A23
    PORTL = (myAddress >> 16) & 0xFF;
    // Flip BA5(PL5) to address second rom chip
    PORTL ^= (1 << 5);
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES ExLoRom repro with 2x 4MB
  else if (mapping == 124) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip A22(PL7) to reverse P0 and P1 roms
    PORTL ^= (1 << 7);
  }
  // for SNES ExHiRom repro with 2x 4MB
  else if (mapping == 224) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A22
    PORTL = (myAddress >> 16) & 0xFF;
    // Set PL7 to inverse of PL6 to reverse P0 and P1 roms
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 1
      PORTL |= (1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 0
      PORTL &= ~(1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }
  // for SNES ExLoRom repro with 4x 2MB
  else if (mapping == 142) {
    // A8-A14
    PORTK = (myAddress >> 8) & 0x7F;
    // Set SNES A15(PK7) HIGH to disable SRAM
    PORTK |= (1 << 7);
    // A15-A22
    PORTL = (myAddress >> 15) & 0xFF;
    // Flip BA6(PL6) to address second rom chip
    PORTL ^= (1 << 6);
    // Flip A22(PL7) to reverse P0 and P1 roms
    PORTL ^= (1 << 7);
  }
  // for SNES ExHiRom repro with 4x 2MB
  else if (mapping == 242) {
    // A8-A15
    PORTK = (myAddress >> 8) & 0xFF;
    // A16-A22
    PORTL = (myAddress >> 16) & 0xFF;
    // Flip BA5(PL5) to address second rom chip
    PORTL ^= (1 << 5);
    // Set PL7 to inverse of PL6 to reverse P0 and P1 roms
    if (!(((myAddress >> 16) & 0xFF) & 0x40)) {
      // if PL6 is 0 set PL7 to 1
      PORTL |= (1 << 7);
    } else if (((myAddress >> 16) & 0xFF) & 0x40) {
      // if PL6 is 1 set PL7 to 0
      PORTL &= ~(1 << 7);
    }
    // Switch SNES BA6(PL6) to HIGH to disable SRAM
    PORTL |= (1 << 6);
  }

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  if (byteCtrl) {
    // Setting OE(PH1) LOW
    PORTH &= ~(1 << 1);
  } else if (mapping == 3) {
    // Switch PLCC32 OE to LOW
    PORTL &= ~(1 << 0);
  } else {
    // Setting OE(PH1) OE_SNS(PH3) LOW
    PORTH &= ~((1 << 1) | (1 << 3));
  }

  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Read
  byte tempByte = PINC;

  if (byteCtrl) {
    // Setting OE(PH1) HIGH
    PORTH |= (1 << 1);
  } else if (mapping == 3) {
    // Switch PLCC32 OE to HIGH
    PORTL |= (1 << 0);
  } else {
    // Setting OE(PH1) OE_SNS(PH3) HIGH
    PORTH |= (1 << 1) | (1 << 3);
  }
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  return tempByte;
}

void writeWord_Flash(unsigned long myAddress, word myData) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  // Wait till output is stable
  __asm__("nop\n\t");

  // Switch WE(PH4) to LOW
  PORTH &= ~(1 << 4);

  // Leave WE low for at least 60ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Switch WE(PH4) to HIGH
  PORTH |= (1 << 4);

  // Leave WE high for at least 50ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");
}

word readWord_Flash(unsigned long myAddress) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t");

  // Setting OE(PH1) LOW
  PORTH &= ~(1 << 1);

  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Read
  word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);

  __asm__("nop\n\t");

  // Setting OE(PH1) HIGH
  PORTH |= (1 << 1);
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  return tempWord;
}

/******************************************
  write helper functions
*****************************************/
bool openFileOnSD() {
  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Get rom size from file
    fileSize = myFile.fileSize();
    if (fileSize > flashSize) {
      display_Clear();
      println_Msg(filePath);
      println_Msg(FS(FSTRING_EMPTY));
      print_Msg(F("File:"));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(fileSize);
      print_STR(_bytes_STR, 1);
      print_Msg(F("Flash:"));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(flashSize);
      print_STR(_bytes_STR, 1);
      print_FatalError(file_too_big_STR);
    }
    return true;
  }
  print_STR(open_file_STR, 1);
  display_Update();
  return false;
}

bool openFlashFile() {
  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  print_STR(flashing_file_STR, 0);
  print_Msg(filePath);
  println_Msg(F("..."));
  display_Update();

  return openFileOnSD();
}

bool openVerifyFlashFile() {
  print_STR(verifying_STR, 1);
  display_Update();

  return openFileOnSD();
}

/******************************************
  Command functions
*****************************************/
void writeByteCommand_Flash(byte command) {
  if ((flashid == 0xBFB7) || (flashid == 0xBFB6) || (flashid == 0xBFB5)) {
    //39F040
    writeByte_Flash(0x5555, 0xaa);
    writeByte_Flash(0x2aaa, 0x55);
    writeByte_Flash(0x5555, command);
  } else {
    writeByte_Flash(0x555, 0xaa);
    writeByte_Flash(0x2aa, 0x55);
    writeByte_Flash(0x555, command);
  }
}

void writeByteCommandShift_Flash(byte command) {
  writeByte_Flash(0x5555 << 1, 0xaa);
  writeByte_Flash(0x2aaa << 1, 0x55);
  writeByte_Flash(0x5555 << 1, command);
}

void writeWordCommand_Flash(byte command) {
  writeWord_Flash(0x5555, 0xaa);
  writeWord_Flash(0x2aaa, 0x55);
  writeWord_Flash(0x5555, command);
}

/******************************************
  29F032/39SF040 flashrom functions
*****************************************/
void resetFlash29F032() {
  // Set data pins to output
  dataOut();

  // Reset command sequence
  writeByte_Flash(0x555, 0xf0);

  // Set data pins to input again
  dataIn8();

  delay(500);
}

void idFlash39SF040() {
  // Set data pins to output
  dataOut();

  writeByte_Flash(0x5555, 0xaa);
  writeByte_Flash(0x2aaa, 0x55);
  writeByte_Flash(0x5555, 0x90);

  // Set data pins to input again
  dataIn8();

  // Read the two id bytes into a string
  flashid = readByte_Flash(0) << 8;
  flashid |= readByte_Flash(1);
  sprintf(flashid_str, "%04X", flashid);
}

void idFlash29F032() {
  // Set data pins to output
  dataOut();

  // ID command sequence
  writeByteCommand_Flash(0x90);

  // Set data pins to input again
  dataIn8();

  // Read the two id bytes into a string
  flashid = readByte_Flash(0) << 8;
  flashid |= readByte_Flash(1);
  sprintf(flashid_str, "%04X", flashid);
}

void eraseFlash29F032() {
  // Set data pins to output
  dataOut();

  // Erase command sequence
  writeByteCommand_Flash(0x80);
  writeByteCommand_Flash(0x10);

  // Set data pins to input again
  dataIn8();

  // Read the status register
  byte statusReg = readByte_Flash(0);

  // After a completed erase D7 will output 1
  while ((statusReg & 0x80) != 0x80) {
    // Blink led
    blinkLED();
    delay(100);
    // Update Status
    statusReg = readByte_Flash(0);
  }
}

void writeFlash29F032() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

    // Retry writing, for when /RESET is not connected (floating)
    int dq5failcnt = 0;
    int noread = 0;

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    // Fill sdBuffer
    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      // if (currByte >= 0) {
      //   print_Msg(currByte);
      //   print_Msg(FS(FSTRING_SPACE));
      //   print_Msg(dq5failcnt);
      //   println_Msg(FS(FSTRING_EMPTY));
      // }
      if (!noread) {
        myFile.read(sdBuffer, 512);
      }
      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();

      noInterrupts();
      int blockfailcnt = 0;
      for (int c = 0; c < 512; c++) {
        uint8_t datum = sdBuffer[c];
        dataIn8();
        uint8_t d = readByte_Flash(currByte + c);
        dataOut();
        if (d == datum || datum == 0xFF) {
          continue;
        }
        // Write command sequence
        writeByteCommand_Flash(0xa0);
        // Write current byte
        writeByte_Flash(currByte + c, datum);
        if (busyCheck29F032(currByte + c, datum)) {
          dq5failcnt++;
          blockfailcnt++;
        }
      }
      interrupts();
      if (blockfailcnt > 0) {
        print_Msg(F("Failures at "));
        print_Msg(currByte);
        print_Msg(F(": "));
        print_Msg(blockfailcnt);
        println_Msg(FS(FSTRING_EMPTY));
        dq5failcnt -= blockfailcnt;
        currByte -= 512;
        delay(100);
        noread = 1;
      } else {
        noread = 0;
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
    // Set data pins to input again
    dataIn8();

    // Close the file:
    myFile.close();
  }
}

int busyCheck29F032(uint32_t addr, byte c) {
  int ret = 0;
  // Set data pins to input
  dataIn8();

  if (byteCtrl) {
    // Setting OE(PH1) CE(PH6)LOW
    PORTH &= ~((1 << 1) | (1 << 6));
  } else {
    // Setting OE(PH1) OE_SNS(PH3) CE(PH6)LOW
    PORTH &= ~((1 << 1) | (1 << 3) | (1 << 6));
  }
  // Setting WE(PH4) WE_SNES(PH5) HIGH
  PORTH |= (1 << 4) | (1 << 5);

  //When the Embedded Program algorithm is complete, the device outputs the datum programmed to D7
  for (;;) {
    uint8_t d = readByte_Flash(addr);
    if ((d & 0x80) == (c & 0x80)) {
      break;
    }
    if ((d & 0x20) == 0x20) {
      // From the datasheet:
      // DQ 5 will indicate if the program or erase time has exceeded the specified limits (internal pulse count).
      // Under these conditions DQ 5 will produce a “1”.
      // This is a failure condition which indicates that the program or erase cycle was not successfully completed.
      // Note : DQ 7 is rechecked even if DQ 5 = “1” because DQ 7 may change simultaneously with DQ 5 .
      d = readByte_Flash(addr);
      if ((d & 0x80) == (c & 0x80)) {
        break;
      } else {
        ret = 1;
        break;
      }
    }
  }

  // Set data pins to output
  dataOut();

  if (byteCtrl) {
    // Setting OE(PH1) HIGH
    PORTH |= (1 << 1);
  } else {
    // Setting OE(PH1) OE_SNS(PH3) HIGH
    PORTH |= (1 << 1) | (1 << 3);
  }
  return ret;
}
/******************************************
  29F1610 flashrom functions
*****************************************/

void resetFlash29F1610() {
  // Set data pins to output
  dataOut();

  // Reset command sequence
  writeByteCommandShift_Flash(0xf0);

  // Set data pins to input again
  dataIn8();

  delay(500);
}

void writeFlash29F1610() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currByte = 0; currByte < fileSize; currByte += 128) {
      // Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
      myFile.read(sdBuffer, 128);

      // Blink led
      if (currByte % 3072 == 0)
        blinkLED();

      // Check if write is complete
      delayMicroseconds(100);
      busyCheck29F1610();

      // Write command sequence
      writeByteCommandShift_Flash(0xa0);

      // Write one full page at a time
      for (byte c = 0; c < 128; c++) {
        writeByte_Flash(currByte + c, sdBuffer[c]);
      }
      // update progress bar
      processedProgressBar += 128;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

    // Check if write is complete
    busyCheck29F1610();

    // Set data pins to input again
    dataIn8();

    // Close the file:
    myFile.close();
  }
}

void writeFlash29F1601() {
  if (openFlashFile()) {

    // Set data pins to output
    dataOut();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currByte = 0; currByte < fileSize; currByte += 128) {
      // Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
      myFile.read(sdBuffer, 128);

      // Blink led
      if (currByte % 3072 == 0)
        blinkLED();

      // Check if write is complete
      delayMicroseconds(100);
      busyCheck29F1610();

      // Write command sequence
      writeByteCommandShift_Flash(0xa0);

      // Write one full page at a time
      for (byte c = 0; c < 128; c++) {
        writeByte_Flash(currByte + c, sdBuffer[c]);

        if (c == 127) {
          // Write the last byte twice or else it won't write at all
          writeByte_Flash(currByte + c, sdBuffer[c]);
        }
      }
      // update progress bar
      processedProgressBar += 128;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

    // Check if write is complete
    busyCheck29F1610();

    // Set data pins to input again
    dataIn8();

    // Close the file:
    myFile.close();
  }
}

void idFlash29F1610() {
  // Set data pins to output
  dataOut();

  // ID command sequence
  writeByteCommandShift_Flash(0x90);

  // Set data pins to input again
  dataIn8();

  // Read the two id bytes into a string
  flashid = readByte_Flash(0) << 8;
  flashid |= readByte_Flash(2);
  sprintf(flashid_str, "%04X", flashid);
}

byte readStatusReg() {
  // Set data pins to output
  dataOut();

  // Status reg command sequence
  writeByteCommandShift_Flash(0x70);

  // Set data pins to input again
  dataIn8();

  // Read the status register
  byte statusReg = readByte_Flash(0);
  return statusReg;
}

void eraseFlash29F1610() {
  // Set data pins to output
  dataOut();

  // Erase command sequence
  writeByteCommandShift_Flash(0x80);
  writeByteCommandShift_Flash(0x10);

  // Set data pins to input again
  dataIn8();

  // Read the status register
  byte statusReg = readByte_Flash(0);

  while ((statusReg & 0x80) != 0x80) {
    statusReg = readByte_Flash(0);
    // Blink led
    blinkLED();
    delay(100);
  }
}

// Delay between write operations based on status register
void busyCheck29F1610() {
  // Set data pins to input
  dataIn8();

  // Read the status register
  byte statusReg = readByte_Flash(0);

  while ((statusReg & 0x80) != 0x80) {
    statusReg = readByte_Flash(0);
  }

  // Set data pins to output
  dataOut();
}

/******************************************
  MX29LV flashrom functions
*****************************************/
void busyCheck29LV640(unsigned long myAddress, byte myData) {
  // Set data pins to input
  dataIn8();

  // Read the status register
  byte statusReg = readByte_Flash(myAddress);
  while ((statusReg & 0x80) != (myData & 0x80)) {
    statusReg = readByte_Flash(myAddress);
  }

  // Set data pins to output
  dataOut();
}

void writeFlash29LV640() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      // Fill sdBuffer
      myFile.read(sdBuffer, 512);
      // Blink led
      if (currByte % 4096 == 0)
        blinkLED();
      for (int c = 0; c < 512; c++) {
        // Write command sequence
        writeByte_Flash(0x555 << 1, 0xaa);
        writeByte_Flash(0x2aa << 1, 0x55);
        writeByte_Flash(0x555 << 1, 0xa0);
        // Write current byte
        writeByte_Flash(currByte + c, sdBuffer[c]);
        // Check if write is complete
        busyCheck29LV640(currByte + c, sdBuffer[c]);
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
    // Set data pins to input again
    dataIn8();
    // Close the file:
    myFile.close();
  }
}

/******************************************
  S29GL flashrom functions
*****************************************/
void writeFlash29GL(unsigned long sectorSize, byte bufferSize) {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currSector = 0; currSector < fileSize; currSector += sectorSize) {
      // Blink led
      blinkLED();

      // Write to flashrom
      for (unsigned long currSdBuffer = 0; currSdBuffer < sectorSize; currSdBuffer += 512) {
        // Fill SD buffer
        myFile.read(sdBuffer, 512);

        // Write bufferSize bytes at a time
        for (int currWriteBuffer = 0; currWriteBuffer < 512; currWriteBuffer += bufferSize) {
          // 2 unlock commands
          writeByte_Flash(0x555 << 1, 0xaa);
          writeByte_Flash(0x2aa << 1, 0x55);
          // Write buffer load command at sector address
          writeByte_Flash(currSector + currSdBuffer + currWriteBuffer, 0x25);
          // Write byte count (minus 1) at sector address
          writeByte_Flash(currSector + currSdBuffer + currWriteBuffer, bufferSize - 1);

          // Load bytes into buffer
          for (byte currByte = 0; currByte < bufferSize; currByte++) {
            writeByte_Flash(currSector + currSdBuffer + currWriteBuffer + currByte, sdBuffer[currWriteBuffer + currByte]);
          }

          // Write Buffer to Flash
          writeByte_Flash(currSector + currSdBuffer + currWriteBuffer + bufferSize - 1, 0x29);

          // Read the status register at last written address
          dataIn8();
          byte statusReg = readByte_Flash(currSector + currSdBuffer + currWriteBuffer + bufferSize - 1);
          while ((statusReg & 0x80) != (sdBuffer[currWriteBuffer + bufferSize - 1] & 0x80)) {
            statusReg = readByte_Flash(currSector + currSdBuffer + currWriteBuffer + bufferSize - 1);
          }
          dataOut();
        }
      }
      // update progress bar
      processedProgressBar += sectorSize;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
    // Set data pins to input again
    dataIn8();
    // Close the file:
    myFile.close();
  }
}

/******************************************
  29F800 functions
*****************************************/
void writeFlash29F800() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    // Fill sdBuffer
    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      myFile.read(sdBuffer, 512);
      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();

      for (int c = 0; c < 512; c++) {
        // Write command sequence
        writeByteCommandShift_Flash(0xa0);
        // Write current byte
        writeByte_Flash(currByte + c, sdBuffer[c]);
        busyCheck29F032(currByte + c, sdBuffer[c]);
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

    // Set data pins to input again
    dataIn8();

    // Close the file:
    myFile.close();
  }
}

/******************************************
  28FXXX series flashrom functions
*****************************************/
void idFlash28FXXX() {
  dataOut();
  writeByte_Flash(0x0, 0x90);

  dataIn8();

  // Read the two id bytes into a string
  flashid = readByte_Flash(0) << 8;
  flashid |= readByte_Flash(1);
  sprintf(flashid_str, "%04X", flashid);
}

void resetFlash28FXXX() {
  dataOut();
  writeByte_Flash(0x0, 0xff);

  dataIn();
  delay(500);
}

uint8_t statusFlash28FXXX() {
  dataOut();

  writeByte_Flash(0x0, 0x70);
  dataIn8();
  return readByte_Flash(0x0);
}

void eraseFlash28FXXX() {
  // only can erase block by block
  for (uint32_t ba = 0; ba < flashSize; ba += sectorSize) {
    dataOut();
    writeByte_Flash(ba, 0x20);
    writeByte_Flash(ba, 0xd0);

    dataIn8();
    while ((readByte_Flash(ba) & 0x80) == 0x00)
      ;

    // blink LED
    blinkLED();
  }
}

void writeFlash28FXXX() {
  if (openFlashFile()) {
    if ((flashid == 0xB088))
      writeFlashLH28F0XX();
    else if ((flashid == 0x8916) || (flashid == 0x8917) || (flashid == 0x8918)) {
      writeFlashE28FXXXJ3A();
    }

    myFile.close();
  }
}

void writeFlashE28FXXXJ3A() {
  uint32_t block_addr;
  uint32_t block_addr_mask = ~(sectorSize - 1);

  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = (uint32_t)fileSize;
  draw_progressbar(0, totalProgressBar);

  // Fill sdBuffer
  for (uint32_t currByte = 0; currByte < fileSize; currByte += 512) {
    myFile.read(sdBuffer, 512);

    // Blink led
    if (currByte % 2048 == 0)
      blinkLED();

    block_addr = currByte & block_addr_mask;

    for (uint32_t c = 0; c < 512; c += bufferSize) {
      // write to buffer start
      dataOut();
      writeByte_Flash(block_addr, 0xe8);

      // waiting for buffer available
      dataIn8();
      while ((readByte_Flash(block_addr) & 0x80) == 0x00)
        ;
      dataOut();

      // set write byte count
      writeByte_Flash(block_addr, bufferSize - 1);

      // filling buffer
      for (uint32_t d = 0; d < bufferSize; d++)
        writeByte_Flash(currByte + c + d, sdBuffer[c + d]);

      // start flashing page
      writeByte_Flash(block_addr, 0xd0);

      // waiting for finishing
      dataIn8();
      while ((readByte_Flash(block_addr) & 0x80) == 0x00)
        ;
    }
    // update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
  }

  dataIn8();
}

void writeFlashLH28F0XX() {

  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = (uint32_t)fileSize;
  draw_progressbar(0, totalProgressBar);

  // Fill sdBuffer
  for (uint32_t currByte = 0; currByte < fileSize; currByte += 512) {
    myFile.read(sdBuffer, 512);
    // Blink led
    if (currByte % 2048 == 0)
      blinkLED();

    for (uint32_t c = 0; c < 512; c += bufferSize) {
      // sequence load to page
      dataOut();
      writeByte_Flash(0x0, 0xe0);
      writeByte_Flash(0x0, bufferSize - 1);  // BCL
      writeByte_Flash(0x0, 0x00);            // BCH should be 0x00

      for (uint32_t d = 0; d < bufferSize; d++)
        writeByte_Flash(d, sdBuffer[c + d]);

      // start flashing page
      writeByte_Flash(0x0, 0x0c);
      writeByte_Flash(0x0, bufferSize - 1);  // BCL
      writeByte_Flash(currByte + c, 0x00);   // BCH should be 0x00

      // waiting for finishing
      dataIn8();
      while ((readByte_Flash(currByte + c) & 0x80) == 0x00)
        ;
    }
    // update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
  }

  dataIn8();
}

/******************************************
  Common flashrom functions
*****************************************/
void blankcheck_Flash() {

  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = flashSize;
  draw_progressbar(0, totalProgressBar);

  blank = 1;
  for (unsigned long currBuffer = 0; currBuffer < flashSize; currBuffer += 512) {
    // Fill buffer
    for (int c = 0; c < 512; c++) {
      // Read byte
      sdBuffer[c] = readByte_Flash(currBuffer + c);
    }
    // Check if all bytes are 0xFF
    for (uint32_t currByte = 0; currByte < 512; currByte++) {
      if (sdBuffer[currByte] != 0xFF) {
        currByte = 512;
        currBuffer = flashSize;
        blank = 0;
      }
    }
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currBuffer % 25600 == 0)
      blinkLED();
  }
  if (blank) {
    println_Msg(F("Flashrom is empty"));
    display_Update();
  } else {
    println_Msg(FS(FSTRING_EMPTY));
    print_Error(F("Error: Not blank"));
  }
}

void verifyFlash() {
  verifyFlash(1, 1, 0);
}

void verifyFlash(byte currChip, byte totalChips, boolean reversed) {
  if (openVerifyFlashFile()) {
    blank = 0;

    // Adjust filesize to fit flashchip
    adjustFileSizeOffset(currChip, totalChips, reversed);

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      if ((reversed) && (currChip == 1) && (totalChips == 1) && (fileSize == 8388608) && (currByte == 4194304)) {
        myFile.seekSet(0);
      }
      if ((reversed) && (currChip == 1) && (totalChips == 1) && (fileSize == 6291456) && (currByte == 2097152)) {
        myFile.seekSet(0);
        currByte = 4194304;
        fileSize = 8388608;
      }

      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 512; c++) {
        if (readByte_Flash(currByte + c) != sdBuffer[c]) {
          blank++;
        }
      }
      // Update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
      // Blink led
      if (currByte % 25600 == 0)
        blinkLED();
    }
    if (blank == 0) {
      println_Msg(F("Flashrom verified OK"));
      display_Update();
    } else {
      print_STR(error_STR, 0);
      print_Msg(blank);
      print_STR(_bytes_STR, 1);
      print_Error(did_not_verify_STR);
    }
    // Close the file:
    myFile.close();
  }
}

void readFlash() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("FLASH", NULL, "FL", "bin");

  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = flashSize;
  draw_progressbar(0, totalProgressBar);

  for (unsigned long currByte = 0; currByte < flashSize; currByte += 512) {
    for (int c = 0; c < 512; c++) {
      sdBuffer[c] = readByte_Flash(currByte + c);
    }
    myFile.write(sdBuffer, 512);
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currByte % 25600 == 0)
      blinkLED();
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading"));
  display_Update();
}

void printFlash(int numBytes) {
  char myBuffer[3];

  for (int currByte = 0; currByte < numBytes; currByte += 10) {
    for (int c = 0; c < 10; c++) {
      sprintf(myBuffer, "%.2x", readByte_Flash(currByte + c));
      // Now print the significant bits
      print_Msg(myBuffer);
    }
    println_Msg("");
  }
  display_Update();
}

void resetFlash8() {
  switch (flashromType) {
    case 1: resetFlash29F032(); break;
    case 2: resetFlash29F1610(); break;
    case 3: resetFlash28FXXX(); break;
  }
}

#ifdef ENABLE_FLASH16
/******************************************
  29L3211 16bit flashrom functions
*****************************************/
void resetFlash16() {
  // Set data pins to output
  dataOut16();

  // Reset command sequence
  writeWordCommand_Flash(0xf0);

  // Set data pins to input again
  dataIn16();

  delay(500);
}

void writeFlash16() {
  if (openFlashFile()) {

    // Set data pins to output
    dataOut16();

    // Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
    int d = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 64) {
      myFile.read(sdBuffer, 128);

      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();

      // Check if write is complete
      delayMicroseconds(100);
      busyCheck16();

      // Write command sequence
      writeWordCommand_Flash(0xa0);

      // Write one full page at a time
      for (byte c = 0; c < 64; c++) {
        word currWord = ((sdBuffer[d + 1] & 0xFF) << 8) | (sdBuffer[d] & 0xFF);
        writeWord_Flash(currByte + c, currWord);
        d += 2;
      }
      d = 0;
    }

    // Check if write is complete
    busyCheck16();

    // Set data pins to input again
    dataIn16();

    // Close the file:
    myFile.close();
  }
}

void writeFlash16_29F1601() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut16();

    // Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
    int d = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 64) {
      myFile.read(sdBuffer, 128);

      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();

      // Check if write is complete
      delayMicroseconds(100);
      busyCheck16();

      // Write command sequence
      writeWordCommand_Flash(0xa0);

      // Write one full page at a time
      for (byte c = 0; c < 64; c++) {
        word currWord = ((sdBuffer[d + 1] & 0xFF) << 8) | (sdBuffer[d] & 0xFF);
        writeWord_Flash(currByte + c, currWord);

        if (c == 63) {
          // Write the last byte twice or else it won't write at all
          writeWord_Flash(currByte + c, sdBuffer[d + 1]);
        }
        d += 2;
      }
      d = 0;
    }

    // Check if write is complete
    busyCheck16();

    // Set data pins to input again
    dataIn16();

    // Close the file:
    myFile.close();
  }
}

void idFlash16() {
  // Set data pins to output
  dataOut16();

  // ID command sequence
  writeWordCommand_Flash(0x90);

  // Set data pins to input again
  dataIn16();

  // Read the two id bytes into a string
  flashid = (readWord_Flash(0) & 0xFF) << 8;
  flashid |= readWord_Flash(1) & 0xFF;
  sprintf(flashid_str, "%04X", flashid);
}

byte readStatusReg16() {
  // Set data pins to output
  dataOut16();

  // Status reg command sequence
  writeWordCommand_Flash(0x70);

  // Set data pins to input again
  dataIn16();

  // Read the status register
  byte statusReg = readWord_Flash(0);
  return statusReg;
}

void eraseFlash16() {
  // Set data pins to output
  dataOut16();

  // Erase command sequence
  writeWordCommand_Flash(0x80);
  writeWordCommand_Flash(0x10);

  // Set data pins to input again
  dataIn16();

  busyCheck16();
}

void blankcheck16() {

  println_Msg(F("Please wait..."));
  display_Update();

  blank = 1;
  for (unsigned long currByte = 0; currByte < flashSize / 2; currByte++) {
    if (readWord_Flash(currByte) != 0xFFFF) {
      currByte = flashSize / 2;
      blank = 0;
    }
  }
  if (blank) {
    println_Msg(F("Flashrom is empty."));
    display_Update();
  } else {
    print_Error(F("Error: Not blank"));
  }
}

void verifyFlash16() {
  if (openVerifyFlashFile()) {
    blank = 0;
    word d = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 256) {
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 256; c++) {
        word currWord = ((sdBuffer[d + 1] << 8) | sdBuffer[d]);

        if (readWord_Flash(currByte + c) != currWord) {
          blank++;
        }
        d += 2;
      }
      d = 0;
    }
    if (blank == 0) {
      println_Msg(F("Flashrom verified OK"));
      display_Update();
    } else {
      println_Msg(F("Verification ERROR!"));
      print_Msg(blank);
      print_Error(F("B did not verify."));
      display_Update();
    }
    // Close the file:
    myFile.close();
  }
}

void readFlash16() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("FLASH", NULL, "FL", "bin");

  word d = 0;
  for (unsigned long currByte = 0; currByte < flashSize / 2; currByte += 256) {
    for (word c = 0; c < 256; c++) {
      word currWord = readWord_Flash(currByte + c);
      // Split word into two bytes
      // Right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Left
      sdBuffer[d] = (currWord & 0xFF);
      d += 2;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading."));
  display_Update();
}

void printFlash16(int numBytes) {
  /*
    right_byte = short_val & 0xFF;
    left_byte = ( short_val >> 8 ) & 0xFF
    short_val = ( ( left_byte & 0xFF ) << 8 ) | ( right_byte & 0xFF );
  */

  char buf[3];

  for (int currByte = 0; currByte < numBytes / 2; currByte += 5) {
    // 5 words per line
    for (int c = 0; c < 5; c++) {
      word currWord = readWord_Flash(currByte + c);

      // Split word into two bytes
      byte left_byte = currWord & 0xFF;
      byte right_byte = (currWord >> 8) & 0xFF;


      sprintf(buf, "%.2X", left_byte);
      // Now print the significant bits
      print_Msg(buf);

      sprintf(buf, "%.2X", right_byte);
      // Now print the significant bits
      print_Msg(buf);
    }
    println_Msg("");
  }
  display_Update();
}

// Delay between write operations based on status register
void busyCheck16() {
  // Set data pins to input
  dataIn16();

  // Read the status register
  word statusReg = readWord_Flash(0);

  while ((statusReg | 0xFF7F) != 0xFFFF) {
    statusReg = readWord_Flash(0);
  }

  // Set data pins to output
  dataOut16();
}

/******************************************
  MX29LV flashrom functions 16bit
*****************************************/
// Delay between write operations based on status register
void busyCheck16_29LV640(unsigned long myAddress, word myData) {
  // Set data pins to input
  dataIn16();

  // Read the status register
  word statusReg = readWord_Flash(myAddress);
  while ((statusReg & 0x80) != (myData & 0x80)) {
    statusReg = readWord_Flash(myAddress);
  }

  // Set data pins to output
  dataOut16();
}

void writeFlash16_29LV640() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut16();

    int d = 0;
    for (unsigned long currWord = 0; currWord < fileSize / 2; currWord += 256) {
      // Fill sdBuffer
      myFile.read(sdBuffer, 512);

      // Blink led
      if (currWord % 4096 == 0)
        blinkLED();

      for (int c = 0; c < 256; c++) {
        // Write command sequence
        writeWordCommand_Flash(0xa0);

        // Write current word
        word myWord = ((sdBuffer[d + 1] & 0xFF) << 8) | (sdBuffer[d] & 0xFF);
        writeWord_Flash(currWord + c, myWord);
        d += 2;
        // Check if write is complete
        busyCheck16_29LV640(currWord + c, myWord);
      }
      d = 0;
    }
    // Set data pins to input again
    dataIn16();

    // Close the file:
    myFile.close();
  }
}

/******************************************
  Eprom functions
*****************************************/
word writeWord_Eprom(unsigned long myAddress, word myData) {
  // Data out
  DDRC = 0xFF;
  DDRA = 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t");

  // Set address
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  // Set data
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

  __asm__("nop\n\t");

  // Switch VPP/OE(PH5) to HIGH
  PORTH |= (1 << 5);
  // Wait 1us for VPP High to Chip Enable Low
  delayMicroseconds(1);
  // Setting CE(PH6) LOW
  PORTH &= ~(1 << 6);

  // Leave VPP HIGH for 50us Chip Enable Program Pulse Width
  delayMicroseconds(55);

  // Setting CE(PH6) HIGH
  PORTH |= (1 << 6);
  // Wait 2us for Chip Enable High to VPP Transition
  delayMicroseconds(2);
  // Switch VPP/OE(PH5) to LOW
  PORTH &= ~(1 << 5);

  // Leave CE High for 1us for VPP Low to Chip Enable Low
  delayMicroseconds(1);

  // Data in
  DDRC = 0x00;
  DDRA = 0x00;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Setting CE(PH6) LOW
  PORTH &= ~(1 << 6);

  // Wait 1us for Chip Enable Low to Output Valid while program verify
  delayMicroseconds(3);

  // Read
  word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);

  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Setting CE(PH6) HIGH
  PORTH |= (1 << 6);

  // Delay 130ns for Chip Enable High to Output Hi-Z
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t");

  return tempWord;
}

word readWord_Eprom(unsigned long myAddress) {
  // Data in
  DDRC = 0x00;
  DDRA = 0x00;
  // Set address
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t");

  // Setting CE(PH6) LOW
  PORTH &= ~(1 << 6);

  // Delay for 100ns for Address Valid/Chip Enable Low to Output Valid
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  // Read
  word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);

  // Setting CE(PH6) HIGH
  PORTH |= (1 << 6);

  return tempWord;
}

void blankcheck_Eprom() {
  println_Msg(F("Please wait..."));
  display_Update();

  blank = 1;
  for (unsigned long currWord = 0; currWord < flashSize / 2; currWord++) {
    if (readWord_Eprom(currWord) != 0xFFFF) {
      currWord = flashSize / 2;
      blank = 0;
    }
  }
  if (blank) {
    println_Msg(F("Flashrom is empty."));
    display_Update();
  } else {
    print_Error(F("Error: Not blank"));
  }
}

void read_Eprom() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("FLASH", NULL, "FL", "bin");

  word d = 0;
  for (unsigned long currWord = 0; currWord < flashSize / 2; currWord += 256) {
    for (word c = 0; c < 256; c++) {
      word myWord = readWord_Eprom(currWord + c);
      // Split word into two bytes
      // Right
      sdBuffer[d + 1] = ((myWord >> 8) & 0xFF);
      // Left
      sdBuffer[d] = (myWord & 0xFF);
      d += 2;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading."));
  display_Update();
}

void write_Eprom() {
  if (openFlashFile()) {
    // Switch VPP/OE(PH5) to HIGH
    PORTH |= (1 << 5);
    delay(1000);

    for (unsigned long currWord = 0; currWord < fileSize / 2; currWord += 256) {
      // Fill SD buffer
      myFile.read(sdBuffer, 512);
      int d = 0;

      // Blink led
      if (currWord % 2048 == 0)
        blinkLED();

      // Work through SD buffer
      for (int c = 0; c < 256; c++) {
        word checkWord;
        word myWord = ((sdBuffer[d + 1] & 0xFF) << 8) | (sdBuffer[d] & 0xFF);

        // Error counter
        byte n = 0;

        // Presto III allows up to 25 rewrites per word
        do {
          // Write word
          checkWord = writeWord_Eprom(currWord + c, myWord);
          // Check for fail
          if (n == 25) {
            print_Msg(F("Program Error 0x"));
            println_Msg(currWord + c, HEX);
            print_Msg(F("0x"));
            print_Msg(readWord_Eprom(currWord + c), HEX);
            print_Msg(F(" != 0x"));
            println_Msg(myWord, HEX);
            print_FatalError(F("Press button to reset"));
          }
          n++;
        } while (checkWord != myWord);
        d += 2;
      }
    }
    // Close the file:
    myFile.close();
  }
}

void verify_Eprom() {
  if (openVerifyFlashFile()) {
    blank = 0;
    word d = 0;
    for (unsigned long currWord = 0; currWord < (fileSize / 2); currWord += 256) {
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 256; c++) {
        word myWord = (((sdBuffer[d + 1] & 0xFF) << 8) | (sdBuffer[d] & 0xFF));

        if (readWord_Eprom(currWord + c) != myWord) {
          blank++;
        }
        d += 2;
      }
      d = 0;
    }
    if (blank == 0) {
      println_Msg(F("Eprom verified OK"));
      display_Update();
    } else {
      println_Msg(F("Verification ERROR!"));
      print_Msg(blank);
      print_Error(F(" words did not verify."));
      display_Update();
    }
    // Close the file:
    myFile.close();
  }
}

void print_Eprom(int numBytes) {
  char buf[3];

  for (int currByte = 0; currByte < numBytes / 2; currByte += 5) {
    // 5 words per line
    for (int c = 0; c < 5; c++) {
      word currWord = readWord_Eprom(currByte + c);

      // Split word into two bytes
      byte left_byte = currWord & 0xFF;
      byte right_byte = (currWord >> 8) & 0xFF;


      sprintf(buf, "%.2x", left_byte);
      // Now print the significant bits
      print_Msg(buf);

      sprintf(buf, "%.2x", right_byte);
      // Now print the significant bits
      print_Msg(buf);
    }
    println_Msg("");
  }
  display_Update();
}

#endif

/******************************************
CFI flashrom functions (modified from GB.ino)
*****************************************/
void sendCFICommand_Flash(byte cmd) {
  writeByteCompensated_Flash(0xAAA, 0xaa);
  writeByteCompensated_Flash(0x555, 0x55);
  writeByteCompensated_Flash(0xAAA, cmd);
}

byte readByteCompensated_Flash(int address) {
  byte data = readByte_Flash(address >> (flashX16Mode ? 1 : 0));
  if (flashSwitchLastBits) {
    return (data & 0b11111100) | ((data << 1) & 0b10) | ((data >> 1) & 0b01);
  }
  return data;
}

void writeByteCompensated_Flash(int address, byte data) {
  if (flashSwitchLastBits) {
    data = (data & 0b11111100) | ((data << 1) & 0b10) | ((data >> 1) & 0b01);
  }
  writeByte_Flash(address >> (flashX16Mode ? 1 : 0), data);
}

void startCFIMode_Flash(boolean x16Mode) {
  if (x16Mode) {
    writeByte_Flash(0x555, 0xf0);  //x16 mode reset command
    delay(500);
    writeByte_Flash(0x555, 0xf0);  //Double reset to get out of possible Autoselect + CFI mode
    delay(500);
    writeByte_Flash(0x55, 0x98);  //x16 CFI Query command
  } else {
    writeByte_Flash(0xAAA, 0xf0);  //x8  mode reset command
    delay(100);
    writeByte_Flash(0xAAA, 0xf0);  //Double reset to get out of possible Autoselect + CFI mode
    delay(100);
    writeByte_Flash(0xAA, 0x98);  //x8 CFI Query command
  }
}

void identifyCFI_Flash() {
  display_Clear();

  // Reset flash
  dataOut();
  writeByteCompensated_Flash(0xAAA, 0xf0);
  delay(100);

  // Trying x8 mode first
  startCFIMode_Flash(false);
  dataIn8();

  char cfiQRYx8[7];
  char cfiQRYx16[7];
  sprintf(cfiQRYx8, "%02X%02X%02X", readByte_Flash(0x20), readByte_Flash(0x22), readByte_Flash(0x24));
  sprintf(cfiQRYx16, "%02X%02X%02X", readByte_Flash(0x10), readByte_Flash(0x11), readByte_Flash(0x12));

  if (strcmp(cfiQRYx8, "515259") == 0) {
    println_Msg(F("Normal CFI x8 Mode"));
    flashX16Mode = false;
    flashSwitchLastBits = false;
  } else if (strcmp(cfiQRYx8, "52515A") == 0) {  // QRY in x8 mode with switched last bit
    println_Msg(F("Switched CFI x8 Mode"));
    flashX16Mode = false;
    flashSwitchLastBits = true;
  } else if (strcmp(cfiQRYx16, "515259") == 0) {  // QRY in x16 mode
    println_Msg(F("Normal CFI x16 Mode"));
    flashX16Mode = true;
    flashSwitchLastBits = false;
  } else if (strcmp(cfiQRYx16, "52515A") == 0) {  // QRY in x16 mode with switched last bit
    println_Msg(F("Switched CFI x16 Mode"));
    flashX16Mode = true;
    flashSwitchLastBits = true;
  } else {
    // Try x16 mode next
    startCFIMode_Flash(true);
    sprintf(cfiQRYx16, "%02X%02X%02X", readByte_Flash(0x10), readByte_Flash(0x11), readByte_Flash(0x12));
    if (strcmp(cfiQRYx16, "515259") == 0) {  // QRY in x16 mode
      println_Msg(F("Normal CFI x16 Mode"));
      flashX16Mode = true;
      flashSwitchLastBits = false;
    } else if (strcmp(cfiQRYx16, "52515A") == 0) {  // QRY in x16 mode with switched last bit
      println_Msg(F("Switched CFI x16 Mode"));
      flashX16Mode = true;
      flashSwitchLastBits = true;
    } else {
      println_Msg(F("CFI Query failed!"));
      print_STR(press_button_STR, 0);
      display_Update();
      wait();
      resetArduino();
      return;
    }
  }
  flashBanks = 1 << (readByteCompensated_Flash(0x4E) - 14);  // - flashX16Mode);

  // Reset flash
  dataOut();
  writeByteCompensated_Flash(0xAAA, 0xf0);
  dataIn8();
  delay(100);
  display_Update();
}

// Adjust file size to fit flash chip and goto needed file offset
void adjustFileSizeOffset(byte currChip, byte totalChips, boolean reversed) {
  // 1*2MB, 1*4MB or 1*8MB
  if ((currChip == 1) && (totalChips == 1)) {
    if (reversed)
      myFile.seekSet(4194304);
  }

  // 2*2MB or 2*4MB
  else if ((currChip == 1) && (totalChips == 2)) {
    if (reversed) {
      if (fileSize > 4194304) {
        fileSize = fileSize - flashSize / 2;
        myFile.seekSet(4194304);
      } else
        fileSize = 0;
    } else if (fileSize > flashSize / 2)
      fileSize = flashSize / 2;

  } else if ((currChip == 2) && (totalChips == 2)) {
    if (reversed) {
      fileSize = flashSize / 2;
      myFile.seekSet(0);
    } else if (fileSize > flashSize / 2) {
      fileSize = fileSize - flashSize / 2;
      myFile.seekSet(flashSize / 2);
    } else
      fileSize = 0;
  }

  // 4*2MB
  else if ((currChip == 1) && (totalChips == 4)) {
    if (reversed) {
      if (fileSize > 4194304) {
        myFile.seekSet(4194304);
        fileSize = 2097152;
      } else
        fileSize = 0;
    } else if (fileSize > 2097152)
      fileSize = 2097152;

  } else if ((currChip == 2) && (totalChips == 4)) {
    if (reversed) {
      if (fileSize > 6291456) {
        myFile.seekSet(6291456);
        fileSize = 2097152;
      } else
        fileSize = 0;
    } else {
      if (fileSize > 2097152) {
        myFile.seekSet(2097152);
        fileSize = 2097152;
      } else
        fileSize = 0;
    }

  } else if ((currChip == 3) && (totalChips == 4)) {
    if (reversed) {
      myFile.seekSet(0);
      fileSize = 2097152;
    } else {
      if (fileSize > 4194304) {
        myFile.seekSet(4194304);
        fileSize = 2097152;
      } else
        fileSize = 0;
    }

  } else if ((currChip == 4) && (totalChips == 4)) {
    if (reversed) {
      if (fileSize > 2097152) {
        myFile.seekSet(2097152);
        fileSize = 2097152;
      } else
        fileSize = 0;
    } else {
      if (fileSize > 6291456) {
        myFile.seekSet(6291456);
        fileSize = 2097152;
      } else
        fileSize = 0;
    }
  }
  // skip write
  else
    fileSize = 0;
}

// Write flashrom
void writeCFI_Flash(byte currChip, byte totalChips, boolean reversed) {
  if (openFileOnSD()) {
    // Print filepath
    print_STR(flashing_file_STR, 0);
    print_Msg(filePath);
    println_Msg(F("..."));
    // Check size
    if ((flashSize == 8388608) && (fileSize < 6291456) && reversed) {
      println_Msg(FS(FSTRING_EMPTY));
      print_STR(error_STR, 0);
      print_FatalError(F("ROM file not ExROM"));
    }
    display_Update();

    // Reset flash
    dataOut();
    writeByteCompensated_Flash(0xAAA, 0xf0);
    dataIn8();
    delay(100);

    // Reset flash
    dataOut();
    writeByte_Flash(0x555, 0xf0);
    dataIn8();

    delay(100);
    println_Msg(F("Erasing..."));
    display_Update();

    // Erase flash
    dataOut();
    sendCFICommand_Flash(0x80);
    sendCFICommand_Flash(0x10);
    dataIn8();

    // Read the status register
    byte statusReg = readByte_Flash(0);

    // After a completed erase D7 will output 1
    while ((statusReg & 0x80) != 0x80) {
      // Blink led
      blinkLED();
      delay(100);
      // Update Status
      statusReg = readByte_Flash(0);
    }

    // Adjust filesize to fit flashchip
    adjustFileSizeOffset(currChip, totalChips, reversed);

    print_Msg(F("Writing flash"));

    // File offset indicator for SNES repros with multiple chips
    if ((totalChips > 1) || reversed) {
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(currChip);
      print_Msg(F("/"));
      print_Msg(totalChips);
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));
      print_Msg(FS(FSTRING_SPACE));

      switch (myFile.curPosition() / 1024 / 1024UL) {
        case 0:
          println_Msg(F("[A]BCD"));
          break;

        case 2:
          println_Msg(F("A[B]CD"));
          break;

        case 4:
          println_Msg(F("AB[C]D"));
          break;

        case 6:
          println_Msg(F("ABC[D]"));
          break;

        default:
          println_Msg(FS(FSTRING_SPACE));
          break;
      }
    } else
      println_Msg(F("..."));
    display_Update();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currAddr = 0; currAddr < fileSize; currAddr += 512) {
      if ((reversed) && (currChip == 1) && (totalChips == 1) && (fileSize == 8388608) && (currAddr == 4194304)) {
        myFile.seekSet(0);
      }
      if ((reversed) && (currChip == 1) && (totalChips == 1) && (fileSize == 6291456) && (currAddr == 2097152)) {
        myFile.seekSet(0);
        currAddr = 4194304;
        fileSize = 8388608;
      }

      myFile.read(sdBuffer, 512);

      // Blink led
      if (currAddr % 4096 == 0)
        blinkLED();

      for (int currByte = 0; currByte < 512; currByte++) {
        // Write command sequence
        dataOut();
        sendCFICommand_Flash(0xa0);

        // Write current byte
        writeByte_Flash(currAddr + currByte, sdBuffer[currByte]);

        dataIn8();

        // Read the status register
        byte statusReg = readByte_Flash(currAddr + currByte);
        while ((statusReg & 0x80) != (sdBuffer[currByte] & 0x80)) {
          statusReg = readByte_Flash(currAddr + currByte);
        }
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
    // Close the file:
    myFile.close();
  }
  // Reset flash
  dataOut();
  writeByteCompensated_Flash(0xAAA, 0xf0);
  delay(100);
  dataIn8();
}
#endif

//******************************************
// End of File
//******************************************
