Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ MK3MENU := m3nu.bin
FPGAPATH := verilog
MK2EXT := bit
MK3EXT := bi3
MK2CORES := base cx4 gsu obc1 sdd1 sa1 dsp sgb sgb_msu
MK3CORES := base cx4 gsu obc1 sdd1 sa1 dsp sgb
MK2CORES := base cx4 gsu obc1 sdd1 sa1 dsp sgb sgb_msu spc7110
MK3CORES := base cx4 gsu obc1 sdd1 sa1 dsp sgb spc7110

MK2FPGA := $(foreach C,$(MK2CORES),$(FPGAPATH)/sd2snes_$C/fpga_$C.$(MK2EXT))
MK3FPGA := $(foreach C,$(MK3CORES),$(FPGAPATH)/sd2snes_$C/fpga_$C.$(MK3EXT))
Expand Down
3 changes: 3 additions & 0 deletions src/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ SRC += printf.c fileops.c fpga.c fpga_spi.c snes.c smc.c
SRC += memory.c filetypes.c faulthandler.c sort.c crc32.c cic.c
SRC += cli.c ymodem.c rle.c msu1.c crc16.c sysinfo.c
SRC += cfg.c cheat.c yaml.c savestate.c sgb.c hwinfo.c
# SPC7110 RTC-4513 battery backup. mk3 only in practice: the call sites are
# gated on CONFIG_MK2, so --gc-sections drops the whole object on mk2.
SRC += spc7110rtc.c

SRC += usbdesc.c usbcore.c usbuser.c cdcuser.c usbinterface.c

Expand Down
64 changes: 64 additions & 0 deletions src/fpga_spi.c
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@
nibbles packed)
eg 0x20111210094816 is 2011-12-10, 9:48:16
E6 ssrr set/reset BS-X status register [7:0]
E6 -{8} read SPC7110 RTC-4513 battery backup
(SPC7110 core only: status byte + the seven
time bytes of E5)
E7 - reset SRTC state
E7 ff tt{7} restore SPC7110 RTC-4513 battery backup
(SPC7110 core only)
E8 - reset DSP program and data ROM write pointers
E9 hhmmllxxxx write+incr. DSP program ROM (xxxx=dummy writes)
EA hhllxxxx write+incr. DSP data ROM (xxxx=dummy writes)
Expand Down Expand Up @@ -222,6 +227,43 @@ void set_rom_mask(uint32_t mask) {
FPGA_DESELECT();
}

/* SPC7110: the data ROM window in PSRAM. The core computes
psram_addr = base + (offset & mask), so the mask must span only the DROM
(power of two); the firmware programs both before the SNES is released.
Every other core ignores $d7/$d8. */
void set_drom_base(uint32_t base) {
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SETDROMBASE);
FPGA_TX_BYTE((base >> 16) & 0xff);
FPGA_TX_BYTE((base >> 8) & 0xff);
FPGA_TX_BYTE((base) & 0xff);
FPGA_DESELECT();
}

void set_drom_mask(uint32_t mask) {
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SETDROMMASK);
FPGA_TX_BYTE((mask >> 16) & 0xff);
FPGA_TX_BYTE((mask >> 8) & 0xff);
FPGA_TX_BYTE((mask) & 0xff);
FPGA_DESELECT();
}

/* SPC7110: PSRAM base of the expansion ROM the Tengai Makyou Zero
translations add at banks $40-4f (flat, outside the $483x window logic).
1 MB-aligned by contract -- the core concatenates base[23:20] with the
bank offset -- and 0 means no expansion chip, so it is sent on every
SPC7110 load like $d7/$d8 and never carries a stale value over. Every
other core ignores $db. */
void set_exp_base(uint32_t base) {
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SETEXPBASE);
FPGA_TX_BYTE((base >> 16) & 0xff);
FPGA_TX_BYTE((base >> 8) & 0xff);
FPGA_TX_BYTE((base) & 0xff);
FPGA_DESELECT();
}

void set_mapper(uint8_t val) {
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SETMAPPER(val));
Expand Down Expand Up @@ -379,6 +421,28 @@ uint64_t get_fpga_time() {
return result;
}

/* SPC7110 RTC-4513 battery backup, FPGA_SPC7110_RTC_LEN bytes: the status byte
followed by the seven packed BCD time bytes. Only the SPC7110 core answers;
the shadow the core hands out is taken in one clock, so the eight bytes can
never straddle a tick. */
void get_spc7110_rtc(uint8_t *state) {
int i;
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SPC7110RTCGET);
FPGA_TX_BYTE(0x00); /* dummy (latch the current state into the shadow) */
for(i = 0; i < FPGA_SPC7110_RTC_LEN; i++) state[i] = FPGA_RX_BYTE();
FPGA_DESELECT();
}

void set_spc7110_rtc(const uint8_t *state) {
int i;
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_SPC7110RTCSET);
for(i = 0; i < FPGA_SPC7110_RTC_LEN; i++) FPGA_TX_BYTE(state[i]);
FPGA_TX_BYTE(0x00); /* latch */
FPGA_DESELECT();
}

void set_fpga_time(uint64_t time) {
FPGA_SELECT();
FPGA_TX_BYTE(FPGA_CMD_RTCSET);
Expand Down
18 changes: 17 additions & 1 deletion src/fpga_spi.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,26 @@
#define FPGA_CMD_SNESCMD_READ (0xd1)
#define FPGA_CMD_SNESCMD_WRITE (0xd2)
#define FPGA_CMD_CHEAT_WRITE (0xd3)
#define FPGA_CMD_SETDROMBASE (0xd7) /* SPC7110 core only: 3 param bytes MSB first -> PSRAM base of the data ROM (every other core drops the bytes) */
#define FPGA_CMD_SETDROMMASK (0xd8) /* SPC7110 core only: 3 param bytes MSB first -> power-of-two size mask of the data ROM */
#define FPGA_CMD_SETEXPBASE (0xdb) /* SPC7110 core only: 3 param bytes MSB first -> PSRAM base of the expansion ROM decoded flat at banks $40-4f (a third chip only the Tengai Makyou Zero translations populate). 1 MB-aligned by contract -- the core concatenates base[23:20] with the bank offset instead of adding. 0 = chip absent; sent on every SPC7110 load so a stale base never lingers; every other core drops the bytes. */
#define FPGA_CMD_MSUSETBITS (0xe0)
#define FPGA_CMD_DACPAUSE (0xe1)
#define FPGA_CMD_DACPLAY (0xe2)
#define FPGA_CMD_DACSETPTR (0xe3)
#define FPGA_CMD_MSUSETPTR (0xe4)
#define FPGA_CMD_RTCSET (0xe5)
#define FPGA_CMD_RTCGET (0xe6) /* TODO remap - SGB only */
#define FPGA_CMD_RTCGET (0xe6) /* TODO remap - SGB, and the SPC7110 core (see below) */
#define FPGA_CMD_BSXSETBITS (0xe6)
#define FPGA_CMD_SRTCRESET (0xe7)
/* SPC7110 core only: RTC-4513 battery backup, eight bytes in both directions -
one status byte {stopped, weekday written, weekday} followed by the seven
packed BCD time bytes of $e5. $e6 reads the state the cartridge is showing,
$e7 puts it back; every other core drops the bytes ($e7 there is the S-RTC
reset, which the SPC7110 core does not have). */
#define FPGA_CMD_SPC7110RTCGET (0xe6)
#define FPGA_CMD_SPC7110RTCSET (0xe7)
#define FPGA_SPC7110_RTC_LEN (8)
#define FPGA_CMD_DSPRESETPTR (0xe8)
#define FPGA_CMD_DSPWRITEPGM (0xe9)
#define FPGA_CMD_DSPWRITEDAT (0xea)
Expand Down Expand Up @@ -124,6 +135,9 @@ void set_msu_status(uint16_t status);
void set_saveram_base(uint8_t);
void set_saveram_mask(uint32_t);
void set_rom_mask(uint32_t);
void set_drom_base(uint32_t);
void set_drom_mask(uint32_t);
void set_exp_base(uint32_t);
void set_mapper(uint8_t val);
void fpga_sddma(uint8_t tgt, uint8_t partial);
void fpga_set_sddma_range(uint16_t start, uint16_t end);
Expand All @@ -133,6 +147,8 @@ uint32_t get_msu_offset(void);
uint32_t get_snes_sysclk(void);
void set_fpga_time(uint64_t time);
uint64_t get_fpga_time(void);
void get_spc7110_rtc(uint8_t *state);
void set_spc7110_rtc(const uint8_t *state);
void set_bsx_regs(uint8_t set, uint8_t reset);
void fpga_reset_srtc_state(void);
void fpga_reset_dspx_addr(void);
Expand Down
60 changes: 60 additions & 0 deletions src/memory.c
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ memory.c: RAM operations
#include "rtc.h"
#include "savestate.h"
#include "sgb.h"
#include "spc7110rtc.h"

#include <string.h>
char* hex = "0123456789ABCDEF";
Expand Down Expand Up @@ -434,6 +435,43 @@ uint32_t load_rom(uint8_t* filename, uint32_t base_addr, uint8_t flags) {
set_saveram_base(ramslot);
}
set_rom_mask(rommask);
if(romprops.has_spc7110) {
/* SPC7110: the ROM image is the program ROM followed by the data ROM.
Hand the core the DROM window over PSRAM as base + power-of-two mask
(the core masks the linear offset first, then adds the base). The
power-up defaults are neutral, so this MUST run before the SNES is
released.
Image-shape premise, deliberate: a headerless .sfc does not carry the
PROM size (emulators take it from an external manifest), so the PROM is
assumed 1 MB and the DROM a power of two -- true for every released
SPC7110 cart (Tengai Makyou Zero 1+4 MB, Momotarou Dentetsu Happy
1+2 MB, Super Power League 4 1+1 MB). A hypothetical 2 MB-PROM cart
(the r4834 bit2 mode) or a non-power-of-two data ROM is out of scope
and would map incorrectly. */
uint32_t drombase = 0x100000;
uint32_t dromspan = (romprops.romsize_bytes > drombase)
? (romprops.romsize_bytes - drombase) : drombase;
uint32_t dromsize = 1;
while(dromsize < dromspan) dromsize <<= 1;
set_drom_base(drombase);
set_drom_mask(dromsize - 1);
/* Expansion ROM for SPC7110 translations.
7 MB ROMs use the extra 1 MB at PSRAM 0x600000.
Set its base when present, or 0 otherwise. */
set_exp_base(romprops.romsize_bytes > 0x600000 ? 0x600000 : 0);
/* The RTC-4513 powers up with an invalid BCD calendar (month/day 00) and
games retry forever on it, so the clock is always programmed before boot.
With a .rtc sidecar next to the save this restores what the cartridge
battery would have kept - including a clock the game stopped - and
without one it falls back to the console's own time. */
#ifndef CONFIG_MK2
spc7110_rtc_load(filename);
#else
/* the Mk.II core carries no virtual battery (see verilog `ifndef MK2);
wall clock only */
set_fpga_time(get_bcdtime());
#endif
}
readled(0);

printf("gsu=%x sa1=%x srambase=%lx sramsize=%lx\n", romprops.has_gsu, romprops.has_sa1, romprops.srambase, romprops.sramsize_bytes);
Expand All @@ -444,6 +482,28 @@ uint32_t load_rom(uint8_t* filename, uint32_t base_addr, uint8_t flags) {
if (romprops.sramsize_bytes) migrate_and_load_srm(filename, SRAM_SAVE_ADDR);
/* file not found error is ok (SRM file might not exist yet) */
if(file_res == FR_NO_FILE) file_res = 0;
#ifdef CONFIG_MK2
/* SPC7110 on Mk.II has no virtual battery, so the factory check
program's RTC BACKUP test can never pass there. Plant the retail
signature that the check program itself writes on success ("SPC7110
CHECK OK", last 16 bytes of SRAM) whenever it is absent, so a fresh
cart boots retail instead of looping the factory ritual. Only the RTC
cart (carttype 0xf9, Tengai Makyou Zero) carries the check program --
on the plain SPC7110 carts those 16 bytes are ordinary save data and
must not be touched. A save that already carries the signature is
untouched; the seed lands in the initial CRC, so it reaches the card
only together with a real save. */
if(romprops.has_spc7110_rtc && romprops.sramsize_bytes >= 16) {
static const uint8_t spc7110_sig[16] = { 'S','P','C','7','1','1','0',' ',
'C','H','E','C','K',' ','O','K' };
uint8_t sigcur[16];
uint32_t sigaddr = SRAM_SAVE_ADDR + romprops.sramsize_bytes - 16;
sram_readblock(sigcur, sigaddr, 16);
if(memcmp(sigcur, spc7110_sig, 16)) {
sram_writeblock((void*)spc7110_sig, sigaddr, 16);
}
}
#endif
saveram_crc_old = calc_sram_crc(SRAM_SAVE_ADDR + romprops.srambase, romprops.sramsize_bytes, 0);
saveram_crc = 0;
saveram_offset = 0;
Expand Down
10 changes: 10 additions & 0 deletions src/msu1.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "usbinterface.h"
#include "savestate.h"
#include "cfg.h"
#include "spc7110rtc.h"

FIL msudata;
FIL msuaudio;
Expand Down Expand Up @@ -71,6 +72,15 @@ int is_msu_free_to_save() {
*/
void msu_savecheck(int immediate) {
uint32_t currentcrc;
#ifndef CONFIG_MK2
/* Keep the SPC7110 RTC-4513 backup in step here too. msu1_loop is the second
game loop and the switch(cmd) of the normal one never runs for an MSU-1
title, so anything that only lives there is silently dead in these games.
Deliberately ABOVE the autosave gate: the RTC backup is not SRAM autosave
and must not be switched off with it. Costs one branch when the cartridge
is not an SPC7110, and writes the card only on a real change. */
spc7110_rtc_save(file_lfn);
#endif
if(!cfg_is_msu1_autosave_enabled()) {
return;
}
Expand Down
4 changes: 2 additions & 2 deletions src/smc.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ void smc_id(snes_romprops_t* props, uint32_t file_offset) {
props->has_st0018 = 0;
props->has_msu1 = 0;
props->has_spc7110 = 0;
props->has_spc7110_rtc = 0;
props->has_cx4 = 0;
props->has_obc1 = 0;
props->has_gsu = 0;
Expand Down Expand Up @@ -263,8 +264,7 @@ void smc_id(snes_romprops_t* props, uint32_t file_offset) {
case 0x2a: /* SPC7110 */
if(header->carttype == 0xf5 || header->carttype == 0xf9) {
props->has_spc7110 = 1;
props->error = MENU_ERR_NOIMPL;
props->error_param = (uint8_t*)"SPC7110";
props->has_spc7110_rtc = (header->carttype == 0xf9);
props->fpga_conf = FPGA_SPC7110;
props->mapper_id = 5;
}
Expand Down
1 change: 1 addition & 0 deletions src/smc.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ typedef struct __attribute__ ((__packed__)) {
uint8_t has_sa1; /* SA-1 presence flag */
uint8_t has_sdd1; /* S-DD1 presence flag */
uint8_t has_spc7110; /* SPC7110 presence flag */
uint8_t has_spc7110_rtc; /* SPC7110 + RTC-4513 (carttype 0xf9) */
uint8_t has_combo; /* Multi game presence flag */
uint32_t srambase; /* saveram base address */
uint32_t sramsize_bytes; /* saveram size in bytes */
Expand Down
8 changes: 8 additions & 0 deletions src/snes.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#include "cfg.h"
#include "usbinterface.h"
#include "sgb.h"
#include "spc7110rtc.h"
#include "version.h"
#include "hwinfo.h"

Expand Down Expand Up @@ -289,6 +290,13 @@ uint8_t snes_main_loop() {
/* save the GB RTC if enabled */
sgb_gtc_save(file_lfn);

#ifndef CONFIG_MK2
/* keep the SPC7110 RTC-4513 backup in step; writes the card only on a change
(the virtual battery is not built into the Mk.II core, and this gate is
what lets --gc-sections drop the whole object there) */
spc7110_rtc_save(file_lfn);
#endif

if(romprops.sramsize_bytes && CFG.enable_autosave) {
uint32_t crc_bytes = min(romprops.sramsize_bytes - saveram_offset, SRAM_REGION_SIZE);
saveram_crc = calc_sram_crc(SRAM_SAVE_ADDR + romprops.srambase + saveram_offset, crc_bytes, saveram_crc);
Expand Down
Loading