cartreader/Cart_Reader/GBS.ino
Ancyker 2cf7f5dbe7 Cleanup voltage requests
The `setVoltage()` function should be called even when `ENABLE_VSELECT` is disabled because `ENABLE_3V3FIX` also uses it. There is no resource cost to do this as when both options are disabled the compiler will optimize this function out. This just "future proofs" the code so if that function ever does more it doesn't need updated everywhere. This applies to `setup_FlashVoltage()` as well.

The changes to OSCR.cpp are just for code formatting and additional comments to clarify this.
2023-06-26 15:25:54 -04:00

738 lines
18 KiB
C++

//******************************************
// GB SMART MODULE
// Supports 32M cart with LH28F016SUT flash
//******************************************
#ifdef enable_GBX
#define GB_SMART_GAMES_PER_PAGE 6
/******************************************
Menu
*****************************************/
// GB Smart menu items
static const char gbSmartMenuItem1[] PROGMEM = "Game Menu";
static const char gbSmartMenuItem2[] PROGMEM = "Flash Menu";
//static const char gbSmartMenuItem3[] PROGMEM = "Reset"; (stored in common strings array)
static const char *const menuOptionsGBSmart[] PROGMEM = { gbSmartMenuItem1, gbSmartMenuItem2, string_reset2 };
static const char gbSmartFlashMenuItem1[] PROGMEM = "Read Flash";
static const char gbSmartFlashMenuItem2[] PROGMEM = "Write Flash";
static const char gbSmartFlashMenuItem3[] PROGMEM = "Back";
static const char *const menuOptionsGBSmartFlash[] PROGMEM = { gbSmartFlashMenuItem1, gbSmartFlashMenuItem2, gbSmartFlashMenuItem3 };
static const char gbSmartGameMenuItem1[] PROGMEM = "Read Game";
static const char gbSmartGameMenuItem2[] PROGMEM = "Read SRAM";
static const char gbSmartGameMenuItem3[] PROGMEM = "Write SRAM";
static const char gbSmartGameMenuItem4[] PROGMEM = "Switch Game";
//static const char gbSmartGameMenuItem5[] PROGMEM = "Reset"; (stored in common strings array)
static const char *const menuOptionsGBSmartGame[] PROGMEM = { gbSmartGameMenuItem1, gbSmartGameMenuItem2, gbSmartGameMenuItem3, gbSmartGameMenuItem4, string_reset2 };
struct GBSmartGameInfo {
uint8_t start_bank;
uint8_t rom_type;
uint8_t rom_size;
uint8_t sram_size;
char title[16];
};
uint32_t gbSmartSize = 32 * 131072;
uint16_t gbSmartBanks = 256;
uint8_t gbSmartBanksPerFlashChip = 128;
uint8_t gbSmartBanksPerFlashBlock = 4;
uint32_t gbSmartFlashBlockSize = (gbSmartBanksPerFlashBlock << 14);
uint8_t gbSmartRomSizeGB = 0x07;
uint8_t gbSmartSramSizeGB = 0x04;
uint8_t gbSmartFlashSizeGB = 0x06;
byte signature[48];
uint16_t gameMenuStartBank;
// Compare checksum
boolean compare_checksum_GBS() {
println_Msg(F("Calculating Checksum"));
display_Update();
strcpy(fileName, romName);
strcat(fileName, ".GB");
// last used rom folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "GB/ROM/%s/%d", romName, foldern - 1);
char calcsumStr[5];
sprintf(calcsumStr, "%04X", calc_checksum_GB(fileName));
if (strcmp(calcsumStr, checksumStr) == 0) {
print_Msg(F("Result: "));
println_Msg(calcsumStr);
println_Msg(F("Checksum matches"));
display_Update();
return 1;
} else {
print_Msg(F("Result: "));
println_Msg(calcsumStr);
print_Error(F("Checksum Error"));
return 0;
}
}
byte readByte_GBS(word myAddress) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Switch CS(PH3) and RD(PH6) to LOW
PORTH &= ~((1 << 3) | (1 << 6));
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Read
byte tempByte = PINC;
// Switch CS(PH3) and RD(PH6) to HIGH
PORTH |= (1 << 3) | (1 << 6);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
return tempByte;
}
void setup_GBSmart() {
// Request 5V
setVoltage(VOLTS_SET_5V);
// take from setup_GB
// Set RST(PH0) to Input
DDRH &= ~(1 << 0);
// Activate Internal Pullup Resistors
PORTH |= (1 << 0);
// Set Address Pins to Output
//A0-A7
DDRF = 0xFF;
//A8-A15
DDRK = 0xFF;
// Set Control Pins to Output CS(PH3) WR(PH5) RD(PH6) AUDIOIN(PH4) RESET(PH0)
DDRH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Output a high signal on all pins, pins are active low therefore everything is disabled now
PORTH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Set Data Pins (D0-D7) to Input
DDRC = 0x00;
delay(400);
gbSmartRemapStartBank(0x00, 0x00, 0x00);
getCartInfo_GB();
for (byte i = 0; i < 0x30; i++)
signature[i] = readByte_GBS(0x0104 + i);
gameMenuStartBank = 0x02;
display_Clear();
display_Update();
}
void gbSmartMenu() {
uint8_t mainMenu;
// Copy menuOptions out of progmem
convertPgm(menuOptionsGBSmart, 3);
mainMenu = question_box(F("GB Smart"), menuOptions, 3, 0);
// wait for user choice to come back from the question box menu
switch (mainMenu) {
case 0:
{
gbSmartGameMenu();
break;
}
case 1:
{
mode = mode_GB_GBSmart_Flash;
break;
}
default:
{
asm volatile(" jmp 0");
break;
}
}
}
void gbSmartGameOptions() {
uint8_t gameSubMenu;
convertPgm(menuOptionsGBSmartGame, 5);
gameSubMenu = question_box(F("GB Smart Game Menu"), menuOptions, 5, 0);
switch (gameSubMenu) {
case 0: // Read Game
{
display_Clear();
sd.chdir("/");
readROM_GB();
compare_checksum_GBS();
break;
}
case 1: // Read SRAM
{
display_Clear();
sd.chdir("/");
readSRAM_GB();
break;
}
case 2: // Write SRAM
{
display_Clear();
sd.chdir("/");
writeSRAM_GB();
uint32_t wrErrors = verifySRAM_GB();
if (wrErrors == 0) {
println_Msg(F("Verified OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(wrErrors);
println_Msg(F(" bytes"));
print_Error(did_not_verify_STR);
}
break;
}
case 3: // Switch Game
{
gameMenuStartBank = 0x02;
gbSmartGameMenu();
break;
}
default:
{
asm volatile(" jmp 0");
break;
}
}
if (gameSubMenu != 3) {
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
}
void gbSmartGameMenu() {
boolean hasMenu;
byte numGames;
struct GBSmartGameInfo gbSmartGames[GB_SMART_GAMES_PER_PAGE];
uint8_t gameSubMenu = 0;
gb_smart_load_more_games:
if (gameMenuStartBank > 0xfe)
gameMenuStartBank = 0x02;
gbSmartGetGames(gbSmartGames, &hasMenu, &numGames);
if (hasMenu) {
char menuOptionsGBSmartGames[7][20];
int i = 0;
for (; i < numGames; i++)
strncpy(menuOptionsGBSmartGames[i], gbSmartGames[i].title, 16);
strncpy(menuOptionsGBSmartGames[i], "...", 16);
gameSubMenu = question_box(F("Select Game"), menuOptionsGBSmartGames, i + 1, 0);
if (gameSubMenu >= i)
goto gb_smart_load_more_games;
} else {
gameSubMenu = 0;
}
// copy romname
strcpy(romName, gbSmartGames[gameSubMenu].title);
// select a game
gbSmartRemapStartBank(gbSmartGames[gameSubMenu].start_bank, gbSmartGames[gameSubMenu].rom_size, gbSmartGames[gameSubMenu].sram_size);
getCartInfo_GB();
showCartInfo_GB();
mode = mode_GB_GBSmart_Game;
}
void gbSmartFlashMenu() {
uint8_t flashSubMenu;
convertPgm(menuOptionsGBSmartFlash, 3);
flashSubMenu = question_box(F("GB Smart Flash Menu"), menuOptions, 3, 0);
switch (flashSubMenu) {
case 0:
{
// read flash
display_Clear();
sd.chdir("/");
EEPROM_readAnything(0, foldern);
sprintf(fileName, "GBS%d.bin", foldern);
sd.mkdir("GB/GBS", true);
sd.chdir("GB/GBS");
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
gbSmartReadFlash();
break;
}
case 1:
{
// write flash
display_Clear();
println_Msg(F("Attention"));
println_Msg(F("This will erase your"));
println_Msg(F("GB Smart Cartridge."));
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
display_Clear();
filePath[0] = '\0';
sd.chdir("/");
fileBrowser(F("Select 4MB file"));
sprintf(filePath, "%s/%s", filePath, fileName);
gbSmartWriteFlash();
break;
}
default:
{
mode = mode_GB_GBSmart;
return;
}
}
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
void gbSmartGetOneGame(struct GBSmartGameInfo *gbSmartGames, byte bank, word base) {
uint8_t myByte, myLength = 0;
word title_address = base + 0x0134;
for (uint8_t j = 0; j < 15; j++) {
myByte = readByte_GBS(title_address++);
if (((myByte >= '0' && myByte <= '9') || (myByte >= 'A' && myByte <= 'z')))
gbSmartGames->title[myLength++] = myByte;
}
gbSmartGames->title[myLength] = 0x00;
gbSmartGames->start_bank = bank;
gbSmartGames->rom_type = readByte_GBS(base + 0x0147);
gbSmartGames->rom_size = readByte_GBS(base + 0x0148);
gbSmartGames->sram_size = readByte_GBS(base + 0x0149);
}
void gbSmartGetGames(struct GBSmartGameInfo *gbSmartGames, boolean *hasMenu, byte *numGames) {
static const byte menu_title[] = { 0x47, 0x42, 0x31, 0x36, 0x4d };
// reset remap setting
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
uint16_t i;
// check if contain menu
*hasMenu = true;
dataIn();
for (i = 0; i < 5; i++) {
if (readByte_GBS(0x0134 + i) != menu_title[i]) {
*hasMenu = false;
break;
}
}
if (*hasMenu) {
for (i = gameMenuStartBank, *numGames = 0; i < gbSmartBanks && *numGames < GB_SMART_GAMES_PER_PAGE;) {
// switch bank
dataOut();
writeByte_GB(0x2100, i);
dataIn();
// read signature
for (uint8_t j = 0x00; j < 0x30; j++) {
if (readByte_GBS(0x4104 + j) != signature[j]) {
i += 0x02;
continue;
}
}
gbSmartGetOneGame(&gbSmartGames[*numGames], i, 0x4000);
i += (2 << gbSmartGames[(*numGames)++].rom_size);
}
gameMenuStartBank = i;
} else {
dataIn();
gbSmartGetOneGame(&gbSmartGames[0], 0, 0);
*numGames = 1;
gameMenuStartBank = 0xfe;
}
}
void gbSmartReadFlash() {
print_Msg(F("Saving as GB/GBS/"));
print_Msg(fileName);
println_Msg(F("..."));
display_Update();
if (!myFile.open(fileName, O_RDWR | O_CREAT))
print_FatalError(create_file_STR);
// reset flash to read array state
for (uint16_t i = 0x00; i < gbSmartBanks; i += gbSmartBanksPerFlashChip)
gbSmartResetFlash(i);
// remaps mmc to full access
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
// dump fixed bank 0x00
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr += 512) {
for (uint16_t c = 0; c < 512; c++)
sdBuffer[c] = readByte_GBS(addr + c);
myFile.write(sdBuffer, 512);
}
// read rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanks; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr += 512) {
for (uint16_t c = 0; c < 512; c++)
sdBuffer[c] = readByte_GBS(addr + c);
myFile.write(sdBuffer, 512);
}
}
// back to initial state
writeByte_GB(0x2100, 0x01);
myFile.close();
println_Msg("");
println_Msg(F("Finished reading"));
display_Update();
}
void gbSmartWriteFlash() {
for (uint16_t bank = 0x00; bank < gbSmartBanks; bank += gbSmartBanksPerFlashChip) {
display_Clear();
print_Msg(F("Erasing..."));
display_Update();
gbSmartEraseFlash(bank);
gbSmartResetFlash(bank);
print_STR(done_STR, 1);
print_Msg(F("Blankcheck..."));
display_Update();
if (!gbSmartBlankCheckingFlash(bank))
print_FatalError(F("Could not erase flash"));
println_Msg(F("Passed"));
display_Update();
// write full chip
gbSmartWriteFlash(bank);
// reset chip
gbSmartWriteFlashByte(0x0000, 0xff);
}
print_STR(verifying_STR, 0);
display_Update();
writeErrors = gbSmartVerifyFlash();
if (writeErrors == 0) {
println_Msg(F("OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(writeErrors);
print_STR(_bytes_STR, 1);
print_FatalError(did_not_verify_STR);
}
}
void gbSmartWriteFlash(uint32_t start_bank) {
if (!myFile.open(filePath, O_READ))
print_FatalError(open_file_STR);
// switch to flash base bank
gbSmartRemapStartBank(start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
myFile.seekCur((start_bank << 14));
print_Msg(F("Writing Bank 0x"));
print_Msg(start_bank, HEX);
print_Msg(F("..."));
display_Update();
// handle bank 0x00 on 0x0000
gbSmartWriteFlashFromMyFile(0x0000);
// handle rest banks on 0x4000
for (uint8_t bank = 0x01; bank < gbSmartBanksPerFlashChip; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
gbSmartWriteFlashFromMyFile(0x4000);
}
myFile.close();
println_Msg("");
}
void gbSmartWriteFlashFromMyFile(uint32_t addr) {
for (uint16_t i = 0; i < 16384; i += 256) {
myFile.read(sdBuffer, 256);
// sequence load to page
dataOut();
gbSmartWriteFlashByte(addr, 0xe0);
gbSmartWriteFlashByte(addr, 0xff);
gbSmartWriteFlashByte(addr, 0x00); // BCH should be 0x00
// fill page buffer
for (int d = 0; d < 256; d++)
gbSmartWriteFlashByte(d, sdBuffer[d]);
// start flashing page
gbSmartWriteFlashByte(addr, 0x0c);
gbSmartWriteFlashByte(addr, 0xff);
gbSmartWriteFlashByte(addr + i, 0x00); // BCH should be 0x00
// waiting for finishing
dataIn();
while ((readByte_GBS(addr + i) & 0x80) == 0x00)
;
}
// blink LED
blinkLED();
}
uint32_t gbSmartVerifyFlash() {
uint32_t verified = 0;
if (!myFile.open(filePath, O_READ)) {
verified = 0xffffffff;
print_Error(F("Can't open file on SD"));
} else {
// remaps mmc to full access
gbSmartRemapStartBank(0x00, gbSmartRomSizeGB, gbSmartSramSizeGB);
// verify bank 0x00
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr += 512) {
myFile.read(sdBuffer, 512);
for (uint16_t c = 0; c < 512; c++) {
if (readByte_GBS(addr + c) != sdBuffer[c])
verified++;
}
}
// verify rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanks; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr += 512) {
myFile.read(sdBuffer, 512);
for (uint16_t c = 0; c < 512; c++) {
if (readByte_GBS(addr + c) != sdBuffer[c])
verified++;
}
}
}
// back to initial state
writeByte_GB(0x2100, 0x01);
myFile.close();
}
return verified;
}
byte gbSmartBlankCheckingFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
// check first bank
dataIn();
for (uint16_t addr = 0x0000; addr <= 0x3fff; addr++) {
if (readByte_GBS(addr) != 0xff)
return 0;
}
// check rest banks
for (uint16_t bank = 0x01; bank < gbSmartBanksPerFlashChip; bank++) {
dataOut();
writeByte_GB(0x2100, bank);
dataIn();
for (uint16_t addr = 0x4000; addr <= 0x7fff; addr++) {
if (readByte_GBS(addr) != 0xff)
return 0;
}
}
return 1;
}
void gbSmartResetFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
dataOut();
gbSmartWriteFlashByte(0x0, 0xff);
}
void gbSmartEraseFlash(uint8_t flash_start_bank) {
gbSmartRemapStartBank(flash_start_bank, gbSmartFlashSizeGB, gbSmartSramSizeGB);
// handling first flash block
dataOut();
gbSmartWriteFlashByte(0x0000, 0x20);
gbSmartWriteFlashByte(0x0000, 0xd0);
dataIn();
while ((readByte_GBS(0x0000) & 0x80) == 0x00)
;
// blink LED
blinkLED();
// rest of flash block
for (uint32_t ba = gbSmartBanksPerFlashBlock; ba < gbSmartBanksPerFlashChip; ba += gbSmartBanksPerFlashBlock) {
dataOut();
writeByte_GB(0x2100, ba);
gbSmartWriteFlashByte(0x4000, 0x20);
gbSmartWriteFlashByte(0x4000, 0xd0);
dataIn();
while ((readByte_GBS(0x4000) & 0x80) == 0x00)
;
// blink LED
blinkLED();
}
}
void gbSmartWriteFlashByte(uint32_t myAddress, uint8_t myData) {
PORTF = myAddress & 0xff;
PORTK = (myAddress >> 8) & 0xff;
PORTC = myData;
// wait for 62.5 x 4 = 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
// Pull FLASH_WE (PH4) low
PORTH &= ~(1 << 4);
// pull low for another 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
// Pull FLASH_WE (PH4) high
PORTH |= (1 << 4);
// pull high for another 250ns
__asm__("nop\n\tnop\n\tnop\n\tnop\n\t");
}
// rom_start_bank = 0x00 means back to original state
void gbSmartRemapStartBank(uint8_t rom_start_bank, uint8_t rom_size, uint8_t sram_size) {
rom_start_bank &= 0xfe;
dataOut();
// clear base bank setting
writeByte_GB(0x1000, 0xa5);
writeByte_GB(0x7000, 0x00);
writeByte_GB(0x1000, 0x98);
writeByte_GB(0x2000, rom_start_bank);
if (rom_start_bank > 1) {
// start set new base bank
writeByte_GB(0x1000, 0xa5);
dataIn();
rom_start_bank = gbSmartGetResizeParam(rom_size, sram_size);
dataOut();
writeByte_GB(0x7000, rom_start_bank);
writeByte_GB(0x1000, 0x98);
writeByte_GB(0x2100, 0x01);
}
dataIn();
}
// Get magic number for 0x7000 register.
// Use for setting correct rom and sram size
// Code logic is take from SmartCard32M V1.3 menu code,
// see 0x2db2 to 0x2e51 (0xa0 bytes)
uint8_t gbSmartGetResizeParam(uint8_t rom_size, uint8_t sram_size) {
if (rom_size < 0x0f) {
rom_size &= 0x07;
rom_size ^= 0x07;
} else {
rom_size = 0x01;
}
if (sram_size > 0) {
if (sram_size > 1) {
sram_size--;
sram_size ^= 0x03;
sram_size <<= 4;
sram_size &= 0x30;
} else {
sram_size = 0x20; // 2KiB treat as 8KiB
}
} else {
sram_size = 0x30; // no sram
}
return (sram_size | rom_size);
}
#endif
//******************************************
// End of File
//******************************************