aboutsummaryrefslogtreecommitdiffhomepage
path: root/Cart_Reader/GPC.ino
blob: 5c48d4c671b1e222ccb2861cd7c168fff1a3f144 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
//******************************************
// SNES Game Processor RAM Cassette code by LuigiBlood
// Revision 1.0.0 February 2024
//******************************************
#ifdef ENABLE_GPC

/******************************************
   Game Processor RAM Cassette
******************************************/
/******************************************
   Prototype Declarations
 *****************************************/
/* Hoping that sanni will use this progressbar function */
extern void draw_progressbar(uint32_t processedsize, uint32_t totalsize);

//void gpcMenu();
void readRAM_GPC();
//void setup_GPC();
void writeRAM_GPC(void);

/******************************************
   Variables
 *****************************************/
//No global variables

/******************************************
  Menu
*****************************************/
// GPC flash menu items
static const char gpcFlashMenuItem1[] PROGMEM = "Read RAM";
static const char gpcFlashMenuItem2[] PROGMEM = "Write RAM";
static const char* const menuOptionsGPCFlash[] PROGMEM = { gpcFlashMenuItem1, gpcFlashMenuItem2, FSTRING_RESET };


void gpcMenu() {
  // create menu with title and 3 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsGPCFlash, 3);
  mainMenu = question_box(F("Game Processor RAM"), menuOptions, 3, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    // Read ram
    case 0:
      // Change working dir to root
      sd.chdir("/");
      readRAM_GPC();
      break;

    // Write ram
    case 1:
      // Change working dir to root
      sd.chdir("/");
      writeRAM_GPC();
      unsigned long wrErrors;
      wrErrors = verifyRAM_GPC();
      if (wrErrors == 0) {
        println_Msg(F("Verified OK"));
        display_Update();
      } else {
        print_STR(error_STR, 0);
        print_Msg(wrErrors);
        print_STR(_bytes_STR, 1);
        print_Error(did_not_verify_STR);
      }
      wait();
      break;

    // Reset
    case 2:
      resetArduino();
      break;
  }
}

/******************************************
   Setup
 *****************************************/
void setup_GPC() {
  // Request 5V
  setVoltage(VOLTS_SET_5V);

  // Set cicrstPin(PG1) to Output
  DDRG |= (1 << 1);
  // Output a high signal until we're ready to start
  PORTG |= (1 << 1);
  // Set cichstPin(PG0) to Input
  DDRG &= ~(1 << 0);

  // Adafruit Clock Generator
  i2c_found = clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0, 0);
  if (i2c_found) {
    clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
    clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
    clockgen.set_freq(2147727200ULL, SI5351_CLK0);
    clockgen.set_freq(307200000ULL, SI5351_CLK2);
    clockgen.output_enable(SI5351_CLK0, 1);
    clockgen.output_enable(SI5351_CLK1, 0);
    clockgen.output_enable(SI5351_CLK2, 1);
  }
#ifdef ENABLE_CLOCKGEN
  else {
    display_Clear();
    print_FatalError(F("Clock Generator not found"));
  }
#endif

  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //BA0-BA7
  DDRL = 0xFF;
  //PA0-PA7
  DDRA = 0xFF;

  // Set Control Pins to Output RST(PH0) CS(PH3) WR(PH5) RD(PH6)
  DDRH |= (1 << 0) | (1 << 3) | (1 << 5) | (1 << 6);
  // Switch RST(PH0) and WR(PH5) to HIGH
  PORTH |= (1 << 0) | (1 << 5);
  // Switch CS(PH3) and RD(PH6) to LOW
  PORTH &= ~((1 << 3) | (1 << 6));

  // Set Refresh(PE5) to Output
  DDRE |= (1 << 5);
  // Switch Refresh(PE5) to LOW (needed for SA-1)
  PORTE &= ~(1 << 5);

  // Set CPU Clock(PH1) to Output
  DDRH |= (1 << 1);
  //PORTH &= ~(1 << 1);

  // Set IRQ(PH4) to Input
  DDRH &= ~(1 << 4);
  // Activate Internal Pullup Resistors
  //PORTH |= (1 << 4);

  // Set expand(PG5) to output
  DDRG |= (1 << 5);
  // Output High
  PORTG |= (1 << 5);

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

  // Unused pins
  // Set wram(PE4) to Output
  DDRE |= (1 << 4);
  //PORTE &= ~(1 << 4);
  // Set pawr(PJ1) to Output
  DDRJ |= (1 << 1);
  //PORTJ &= ~(1 << 1);
  // Set pard(PJ0) to Output
  DDRJ |= (1 << 0);
  //PORTJ &= ~(1 << 0);

  // Start CIC by outputting a low signal to cicrstPin(PG1)
  PORTG &= ~(1 << 1);

  // Wait for CIC reset
  delay(1000);
}

/******************************************
   Low level functions
 *****************************************/
// Write one byte of data to a location specified by bank and address, 00:0000
void writeBank_GPC(byte myBank, word myAddress, byte myData) {
  PORTL = myBank;
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  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"
          "nop\n\t"
          "nop\n\t");

  // Switch WR(PH5) to LOW
  PORTH &= ~(1 << 5);

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

  // Switch WR(PH5) to HIGH
  PORTH |= (1 << 5);

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

// Read one byte of data from a location specified by bank and address, 00:0000
byte readBank_GPC(byte myBank, word myAddress) {
  PORTL = myBank;
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;

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

  // Read
  byte tempByte = PINC;
  return tempByte;
}

/******************************************
   Game Processor RAM Cassette functions
 *****************************************/
// Read RAM cassette to SD card
void readRAM_GPC() {
  // Set control
  dataIn();
  controlIn_SNES();

  // Get name, add extension and convert to char array for sd lib
  createFolderAndOpenFile("SNES", "ROM", "GPC4M", "sfc");

  // Read Banks
  for (int currBank = 0xC0; currBank < 0xC8; currBank++) {
    // Dump the bytes to SD 512B at a time
    for (long currByte = 0; currByte < 65536; currByte += 512) {
      draw_progressbar((currBank - 0xC0) * 0x10000 + currByte, 0x80000);
      for (int c = 0; c < 512; c++) {
        sdBuffer[c] = readBank_GPC(currBank, currByte + c);
      }
      myFile.write(sdBuffer, 512);
    }
  }
  draw_progressbar(0x80000, 0x80000);  //Finish drawing progress bar

  // Close the file:
  myFile.close();
  println_Msg(F("Read ram completed"));
  display_Update();
  wait();
}

void writeRAM_GPC(void) {
  //Display file Browser and wait user to select a file. Size must be 512KB.
  filePath[0] = '\0';
  sd.chdir("/");
  fileBrowser(F("Select SFC file"));
  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  display_Clear();

  //open file on sd card
  if (myFile.open(filePath, O_READ)) {

    fileSize = myFile.fileSize();
    if (fileSize != 0x80000) {
      println_Msg(F("File must be 512KB"));
      display_Update();
      myFile.close();
      wait();
      return;
    }

    //Disable ram cassette write protection
    dataOut();
    controlOut_SNES();
    for (int countProtect = 0; countProtect < 15; countProtect++) {
      writeBank_GPC(0x20, 0x6000, 0x00);
    }

    //Write ram
    dataOut();
    controlOut_SNES();
    println_Msg(F("Writing ram..."));
    display_Update();
    for (int currBank = 0xC0; currBank < 0xC8; currBank++) {
      //startAddr = 0x0000
      for (long currByte = 0x0000; currByte < 0x10000; currByte += 512) {
        myFile.read(sdBuffer, 512);
        for (unsigned long c = 0; c < 512; c++) {
          //startBank = 0x10; CS low
          writeBank_GPC(currBank, currByte + c, sdBuffer[c]);
        }
      }
      draw_progressbar(((currBank - 0xC0) * 0x10000), 0x80000);
    }

    //reenable write protection
    dataIn();
    controlIn_SNES();
    byte keepByte = readBank_GPC(0x20, 0x6000);
    delay(100);
    dataOut();
    controlOut_SNES();    
    writeBank_GPC(0x20, 0x6000, keepByte);

    draw_progressbar(0x80000, 0x80000);
    delay(100);
    // Set pins to input
    dataIn();

    // Close the file:
    myFile.close();
    println_Msg("");
    println_Msg(F("RAM writing finished"));
    display_Update();
  } else {
    print_Error(FS(FSTRING_FILE_DOESNT_EXIST));
  }
}

// Check if the RAM was written without any error
unsigned long verifyRAM_GPC() {
  //open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Variable for errors
    writeErrors = 0;

    // Set control
    controlIn_SNES();

    //startBank = 0xC0; endBank = 0xC7; CS low
    for (byte currBank = 0xC0; currBank < 0xC8; currBank++) {
      //startAddr = 0x0000
      for (long currByte = 0x0000; currByte < 0x10000; currByte += 512) {
        //fill sdBuffer
        myFile.read(sdBuffer, 512);
        for (unsigned long c = 0; c < 512; c++) {
          if ((readBank_GPC(currBank, currByte + c)) != sdBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    // Close the file:
    myFile.close();
    return writeErrors;
  } else {
    print_Error(open_file_STR);
    return 1;
  }
}

#endif

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