1442 lines
32 KiB
C++
1442 lines
32 KiB
C++
/******************************************************************************/
|
|
/* Mednafen Sega Saturn Emulation Module */
|
|
/******************************************************************************/
|
|
/* smpc.cpp - SMPC Emulation
|
|
** Copyright (C) 2015-2017 Mednafen Team
|
|
**
|
|
** This program is free software; you can redistribute it and/or
|
|
** modify it under the terms of the GNU General Public License
|
|
** as published by the Free Software Foundation; either version 2
|
|
** of the License, or (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software Foundation, Inc.,
|
|
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/*
|
|
TODO:
|
|
CD On/Off
|
|
*/
|
|
|
|
#include "ss.h"
|
|
#include "cdrom/CDUtility.h"
|
|
using namespace CDUtility;
|
|
|
|
#include "smpc.h"
|
|
#include "sound.h"
|
|
#include "vdp1.h"
|
|
#include "vdp2.h"
|
|
#include "cdb.h"
|
|
#include "scu.h"
|
|
|
|
#include "input/gamepad.h"
|
|
#include "input/3dpad.h"
|
|
#include "input/mouse.h"
|
|
#include "input/wheel.h"
|
|
#include "input/mission.h"
|
|
#include "input/keyboard.h"
|
|
//#include "input/jpkeyboard.h"
|
|
|
|
#include "input/multitap.h"
|
|
|
|
namespace MDFN_IEN_SS
|
|
{
|
|
#include "sh7095.h"
|
|
|
|
enum
|
|
{
|
|
CLOCK_DIVISOR_26M = 65,
|
|
CLOCK_DIVISOR_28M = 61
|
|
};
|
|
|
|
enum
|
|
{
|
|
CMD_MSHON = 0x00,
|
|
CMD_SSHON = 0x02,
|
|
CMD_SSHOFF = 0x03,
|
|
|
|
CMD_SNDON = 0x06,
|
|
CMD_SNDOFF = 0x07,
|
|
|
|
CMD_CDON = 0x08,
|
|
CMD_CDOFF = 0x09,
|
|
|
|
// A, B, C do something...
|
|
|
|
CMD_SYSRES = 0x0D,
|
|
|
|
CMD_CKCHG352 = 0x0E,
|
|
CMD_CKCHG320 = 0x0F,
|
|
|
|
CMD_INTBACK = 0x10,
|
|
CMD_SETTIME = 0x16,
|
|
CMD_SETSMEM = 0x17,
|
|
|
|
CMD_NMIREQ = 0x18,
|
|
CMD_RESENAB = 0x19,
|
|
CMD_RESDISA = 0x1A
|
|
};
|
|
|
|
static uint8 AreaCode;
|
|
static int32 MasterClock;
|
|
|
|
static struct
|
|
{
|
|
uint64 ClockAccum;
|
|
|
|
bool Valid;
|
|
|
|
union
|
|
{
|
|
uint8 raw[7];
|
|
struct
|
|
{
|
|
uint8 year[2]; // BCD; [0] = xx00, [1] = 00xx
|
|
uint8 wday_mon; // 0x0-0x6(upper; 6=Saturday), 0x1-0xC(lower)
|
|
uint8 mday; // BCD; 0x01-0x31
|
|
uint8 hour; // BCD; 0x00-0x23
|
|
uint8 minute; // BCD; 0x00-0x59
|
|
uint8 second; // BCD; 0x00-0x59
|
|
};
|
|
};
|
|
} RTC;
|
|
|
|
static uint8 SaveMem[4];
|
|
|
|
static uint8 IREG[7];
|
|
static uint8 OREG[0x20];
|
|
static uint8 SR;
|
|
static bool SF;
|
|
|
|
enum
|
|
{
|
|
PMODE_15BYTE = 0,
|
|
PMODE_255BYTE = 1,
|
|
PMODE_ILL = 2,
|
|
PMODE_0BYTE = 3
|
|
};
|
|
|
|
enum
|
|
{
|
|
SR_RESB = 0x10,
|
|
SR_NPE = 0x20,
|
|
SR_PDL = 0x40,
|
|
};
|
|
|
|
static bool ResetNMIEnable;
|
|
|
|
static bool ResetButtonPhysStatus;
|
|
static int32 ResetButtonCount;
|
|
static bool ResetPending;
|
|
static int32 PendingCommand;
|
|
static int32 ExecutingCommand;
|
|
static int32 PendingClockDivisor;
|
|
static int32 CurrentClockDivisor;
|
|
|
|
static bool PendingVB;
|
|
|
|
static int32 SubPhase;
|
|
static int64 ClockCounter;
|
|
static uint32 SMPC_ClockRatio;
|
|
|
|
static bool SoundCPUOn;
|
|
static bool SlaveSH2On;
|
|
static bool CDOn;
|
|
|
|
static uint8 BusBuffer;
|
|
//
|
|
//
|
|
static struct
|
|
{
|
|
int64 TimeCounter;
|
|
int32 StartTime;
|
|
int32 OptWaitUntilTime;
|
|
int32 OptEatTime;
|
|
|
|
int32 OptReadTime;
|
|
|
|
uint8 Mode[2];
|
|
bool TimeOptEn;
|
|
bool NextContBit;
|
|
|
|
uint8 CurPort;
|
|
uint8 ID1;
|
|
uint8 ID2;
|
|
uint8 IDTap;
|
|
|
|
uint8 CommMode;
|
|
|
|
uint8 OWP;
|
|
|
|
uint8 work[8];
|
|
//
|
|
//
|
|
uint8 TapCounter;
|
|
uint8 TapCount;
|
|
uint8 ReadCounter;
|
|
uint8 ReadCount;
|
|
uint8 ReadBuffer[255]; //16];
|
|
uint8 WriteCounter;
|
|
uint8 PDCounter;
|
|
} JRS;
|
|
//
|
|
//
|
|
static uint8 DataOut[2][2];
|
|
static uint8 DataDir[2][2];
|
|
static bool DirectModeEn[2];
|
|
static bool ExLatchEn[2];
|
|
|
|
static uint8 IOBusState[2];
|
|
static IODevice* IOPorts[2];
|
|
|
|
static struct PossibleDevice
|
|
{
|
|
IODevice none;
|
|
IODevice_Gamepad gamepad;
|
|
IODevice_3DPad threedpad;
|
|
IODevice_Mouse mouse;
|
|
IODevice_Wheel wheel;
|
|
IODevice_Mission mission;//{false};
|
|
IODevice_Mission dualmission;//{true};
|
|
IODevice_Keyboard keyboard;
|
|
// IODevice_Keyboard jpkeyboard;
|
|
PossibleDevice()
|
|
:mission(false), dualmission(true)
|
|
{
|
|
}
|
|
} PossibleDevices[12];
|
|
|
|
static IODevice_Multitap PossibleMultitaps[2];
|
|
|
|
static IODevice_Multitap* SPorts[2];
|
|
static IODevice* VirtualPorts[12];
|
|
static uint8* VirtualPortsDPtr[12];
|
|
static uint8* MiscInputPtr;
|
|
|
|
IODevice::IODevice() { }
|
|
IODevice::~IODevice() { }
|
|
void IODevice::Power(void) { }
|
|
void IODevice::UpdateInput(const uint8* data, const int32 time_elapsed) { }
|
|
void IODevice::UpdateOutput(uint8* data) { }
|
|
uint8 IODevice::UpdateBus(const uint8 smpc_out, const uint8 smpc_out_asserted) { return smpc_out; }
|
|
|
|
//
|
|
//
|
|
|
|
static bool vb;
|
|
static sscpu_timestamp_t lastts;
|
|
|
|
static void UpdateIOBus(unsigned port)
|
|
{
|
|
IOBusState[port] = IOPorts[port]->UpdateBus((DataOut[port][DirectModeEn[port]] | ~DataDir[port][DirectModeEn[port]]) & 0x7F, DataDir[port][DirectModeEn[port]]);
|
|
assert(!(IOBusState[port] & 0x80));
|
|
}
|
|
|
|
static void MapPorts(void)
|
|
{
|
|
for(unsigned sp = 0, vp = 0; sp < 2; sp++)
|
|
{
|
|
IODevice* nd;
|
|
|
|
if(SPorts[sp])
|
|
{
|
|
for(unsigned i = 0; i < 6; i++)
|
|
{
|
|
IODevice* const tsd = VirtualPorts[vp++];
|
|
|
|
if(SPorts[sp]->GetSubDevice(i) != tsd)
|
|
tsd->Power();
|
|
|
|
SPorts[sp]->SetSubDevice(i, tsd);
|
|
}
|
|
|
|
nd = SPorts[sp];
|
|
}
|
|
else
|
|
nd = VirtualPorts[vp++];
|
|
|
|
if(IOPorts[sp] != nd)
|
|
nd->Power();
|
|
|
|
IOPorts[sp] = nd;
|
|
}
|
|
}
|
|
|
|
void SMPC_SetMultitap(unsigned sport, bool enabled)
|
|
{
|
|
assert(sport < 2);
|
|
|
|
SPorts[sport] = (enabled ? &PossibleMultitaps[sport] : nullptr);
|
|
MapPorts();
|
|
}
|
|
|
|
void SMPC_SetInput(unsigned port, const char* type, uint8* ptr)
|
|
{
|
|
assert(port < 13);
|
|
|
|
if(port == 12)
|
|
{
|
|
MiscInputPtr = ptr;
|
|
return;
|
|
}
|
|
//
|
|
//
|
|
//
|
|
IODevice* nd = nullptr;
|
|
|
|
if(!strcmp(type, "none"))
|
|
nd = &PossibleDevices[port].none;
|
|
else if(!strcmp(type, "gamepad"))
|
|
nd = &PossibleDevices[port].gamepad;
|
|
else if(!strcmp(type, "3dpad"))
|
|
nd = &PossibleDevices[port].threedpad;
|
|
else if(!strcmp(type, "mouse"))
|
|
nd = &PossibleDevices[port].mouse;
|
|
else if(!strcmp(type, "wheel"))
|
|
nd = &PossibleDevices[port].wheel;
|
|
else if(!strcmp(type, "mission") || !strcmp(type, "missionwoa"))
|
|
nd = &PossibleDevices[port].mission;
|
|
else if(!strcmp(type, "dmission") || !strcmp(type, "dmissionwoa"))
|
|
nd = &PossibleDevices[port].dualmission;
|
|
else if(!strcmp(type, "keyboard"))
|
|
nd = &PossibleDevices[port].keyboard;
|
|
// else if(!strcmp(type, "jpkeyboard"))
|
|
// nd = &PossibleDevices[port].jpkeyboard;
|
|
else
|
|
abort();
|
|
|
|
VirtualPorts[port] = nd;
|
|
VirtualPortsDPtr[port] = ptr;
|
|
|
|
MapPorts();
|
|
}
|
|
|
|
#if 0
|
|
static void RTC_Reset(void)
|
|
{
|
|
|
|
|
|
}
|
|
#endif
|
|
|
|
/*void SMPC_LoadNV(Stream* s)
|
|
{
|
|
RTC.Valid = s->get_u8();
|
|
s->read(RTC.raw, sizeof(RTC.raw));
|
|
s->read(SaveMem, sizeof(SaveMem));
|
|
}
|
|
|
|
void SMPC_SaveNV(Stream* s)
|
|
{
|
|
s->put_u8(RTC.Valid);
|
|
s->write(RTC.raw, sizeof(RTC.raw));
|
|
s->write(SaveMem, sizeof(SaveMem));
|
|
}*/
|
|
|
|
void SMPC_SetRTC(const struct tm* ht, const uint8 lang)
|
|
{
|
|
if(!ht)
|
|
{
|
|
RTC.Valid = false;
|
|
RTC.year[0] = 0x19;
|
|
RTC.year[1] = 0x93;
|
|
RTC.wday_mon = 0x5C;
|
|
RTC.mday = 0x31;
|
|
RTC.hour = 0x23;
|
|
RTC.minute = 0x59;
|
|
RTC.second = 0x59;
|
|
|
|
for(unsigned i = 0; i < 4; i++)
|
|
SaveMem[i] = 0x00;
|
|
}
|
|
else
|
|
{
|
|
int year_adj = ht->tm_year;
|
|
//if(year_adj >= 100)
|
|
// year_adj = 100 + ((year_adj - 100) % 28);
|
|
|
|
RTC.Valid = true; //false;
|
|
RTC.year[0] = U8_to_BCD(19 + year_adj / 100);
|
|
RTC.year[1] = U8_to_BCD(year_adj % 100);
|
|
RTC.wday_mon = (std::min<unsigned>(6, ht->tm_wday) << 4) | ((std::min<unsigned>(11, ht->tm_mon) + 1) << 0);
|
|
RTC.mday = U8_to_BCD(std::min<unsigned>(31, ht->tm_mday));
|
|
RTC.hour = U8_to_BCD(std::min<unsigned>(23, ht->tm_hour));
|
|
RTC.minute = U8_to_BCD(std::min<unsigned>(59, ht->tm_min));
|
|
RTC.second = U8_to_BCD(std::min<unsigned>(59, ht->tm_sec));
|
|
|
|
//if((SaveMem[3] & 0x0F) <= 0x05 || (SaveMem[3] & 0x0F) == 0xF)
|
|
SaveMem[3] = (SaveMem[3] & 0xF0) | lang;
|
|
}
|
|
}
|
|
|
|
void SMPC_Init(const uint8 area_code_arg, const int32 master_clock_arg)
|
|
{
|
|
AreaCode = area_code_arg;
|
|
MasterClock = master_clock_arg;
|
|
|
|
ResetPending = false;
|
|
vb = false;
|
|
lastts = 0;
|
|
|
|
for(unsigned sp = 0; sp < 2; sp++)
|
|
SPorts[sp] = nullptr;
|
|
|
|
for(unsigned i = 0; i < 12; i++)
|
|
{
|
|
VirtualPorts[i] = nullptr;
|
|
SMPC_SetInput(i, "none", NULL);
|
|
}
|
|
|
|
SMPC_SetRTC(NULL, 0);
|
|
}
|
|
|
|
bool SMPC_IsSlaveOn(void)
|
|
{
|
|
return SlaveSH2On;
|
|
}
|
|
|
|
static void SlaveOn(void)
|
|
{
|
|
SlaveSH2On = true;
|
|
CPU[1].AdjustTS(SH7095_mem_timestamp, true);
|
|
CPU[1].Reset(true);
|
|
SS_SetEventNT(&events[SS_EVENT_SH2_S_DMA], SH7095_mem_timestamp + 1);
|
|
}
|
|
|
|
static void SlaveOff(void)
|
|
{
|
|
SlaveSH2On = false;
|
|
CPU[1].Reset(true);
|
|
CPU[1].AdjustTS(0x7FFFFFFF, true);
|
|
SS_SetEventNT(&events[SS_EVENT_SH2_S_DMA], SS_EVENT_DISABLED_TS);
|
|
}
|
|
|
|
static void TurnSoundCPUOn(void)
|
|
{
|
|
SOUND_Reset68K();
|
|
SoundCPUOn = true;
|
|
SOUND_Set68KActive(true);
|
|
}
|
|
|
|
static void TurnSoundCPUOff(void)
|
|
{
|
|
SOUND_Reset68K();
|
|
SoundCPUOn = false;
|
|
SOUND_Set68KActive(false);
|
|
}
|
|
|
|
void SMPC_Reset(bool powering_up)
|
|
{
|
|
SlaveOff();
|
|
TurnSoundCPUOff();
|
|
CDOn = true; // ? false;
|
|
|
|
ResetButtonCount = 0;
|
|
ResetNMIEnable = false; // or only on powering_up?
|
|
|
|
CPU[0].SetNMI(true);
|
|
|
|
memset(IREG, 0, sizeof(IREG));
|
|
memset(OREG, 0, sizeof(OREG));
|
|
PendingCommand = -1;
|
|
ExecutingCommand = -1;
|
|
SF = 0;
|
|
|
|
BusBuffer = 0x00;
|
|
|
|
for(unsigned port = 0; port < 2; port++)
|
|
{
|
|
for(unsigned sel = 0; sel < 2; sel++)
|
|
{
|
|
DataOut[port][sel] = 0;
|
|
DataDir[port][sel] = 0;
|
|
}
|
|
DirectModeEn[port] = false;
|
|
ExLatchEn[port] = false;
|
|
UpdateIOBus(port);
|
|
}
|
|
|
|
ResetPending = false;
|
|
|
|
PendingClockDivisor = 0;
|
|
CurrentClockDivisor = CLOCK_DIVISOR_26M;
|
|
|
|
SubPhase = 0;
|
|
PendingVB = false;
|
|
ClockCounter = 0;
|
|
//
|
|
memset(&JRS, 0, sizeof(JRS));
|
|
}
|
|
|
|
int32 SMPC_StartFrame(EmulateSpecStruct* espec)
|
|
{
|
|
if(ResetPending)
|
|
SS_Reset(false);
|
|
|
|
if(PendingClockDivisor > 0)
|
|
{
|
|
CurrentClockDivisor = PendingClockDivisor;
|
|
PendingClockDivisor = 0;
|
|
}
|
|
|
|
if(!SlaveSH2On)
|
|
CPU[1].AdjustTS(0x7FFFFFFF, true);
|
|
|
|
SMPC_ClockRatio = (1ULL << 32) * 4000000 * CurrentClockDivisor / MasterClock;
|
|
SOUND_SetClockRatio((1ULL << 32) * 11289600 * CurrentClockDivisor / MasterClock);
|
|
CDB_SetClockRatio((1ULL << 32) * 11289600 * CurrentClockDivisor / MasterClock);
|
|
|
|
return CurrentClockDivisor;
|
|
}
|
|
|
|
void SMPC_UpdateOutput(void)
|
|
{
|
|
for(unsigned vp = 0; vp < 12; vp++)
|
|
{
|
|
VirtualPorts[vp]->UpdateOutput(VirtualPortsDPtr[vp]);
|
|
}
|
|
}
|
|
|
|
void SMPC_UpdateInput(const int32 time_elapsed)
|
|
{
|
|
//printf("%8d\n", time_elapsed);
|
|
|
|
ResetButtonPhysStatus = (bool)(*MiscInputPtr & 0x1);
|
|
for(unsigned vp = 0; vp < 12; vp++)
|
|
{
|
|
VirtualPorts[vp]->UpdateInput(VirtualPortsDPtr[vp], time_elapsed);
|
|
}
|
|
}
|
|
|
|
|
|
void SMPC_Write(const sscpu_timestamp_t timestamp, uint8 A, uint8 V)
|
|
{
|
|
BusBuffer = V;
|
|
A &= 0x3F;
|
|
|
|
SS_DBGTI(SS_DBG_SMPC_REGW, "[SMPC] Write to 0x%02x:0x%02x", A, V);
|
|
|
|
//
|
|
// Call VDP2::Update() to prevent out-of-temporal-order calls to SMPC_Update() from here and the event system.
|
|
//
|
|
SS_SetEventNT(&events[SS_EVENT_VDP2], VDP2::Update(timestamp)); // TODO: conditionalize so we don't consume so much CPU time if a game writes continuously to SMPC ports
|
|
sscpu_timestamp_t nt = SMPC_Update(timestamp);
|
|
switch(A)
|
|
{
|
|
case 0x00:
|
|
case 0x01:
|
|
case 0x02:
|
|
case 0x03:
|
|
case 0x04:
|
|
case 0x05:
|
|
case 0x06:
|
|
if(MDFN_UNLIKELY(ExecutingCommand >= 0))
|
|
{
|
|
SS_DBGTI(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Input register %u port written with 0x%02x while command 0x%02x is executing.", A, V, ExecutingCommand);
|
|
}
|
|
|
|
IREG[A] = V;
|
|
break;
|
|
|
|
case 0x0F:
|
|
if(MDFN_UNLIKELY(ExecutingCommand >= 0))
|
|
{
|
|
SS_DBGTI(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Command port written with 0x%02x while command 0x%02x is still executing.", V, ExecutingCommand);
|
|
}
|
|
|
|
if(MDFN_UNLIKELY(PendingCommand >= 0))
|
|
{
|
|
SS_DBGTI(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Command port written with 0x%02x while command 0x%02x is still pending.", V, PendingCommand);
|
|
}
|
|
|
|
PendingCommand = V;
|
|
break;
|
|
|
|
case 0x31:
|
|
if(MDFN_UNLIKELY(SF))
|
|
{
|
|
SS_DBGTI(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] SF port written while SF is 1.");
|
|
}
|
|
|
|
SF = true;
|
|
break;
|
|
|
|
//
|
|
//
|
|
//
|
|
case 0x3A:
|
|
DataOut[0][1] = V & 0x7F;
|
|
UpdateIOBus(0);
|
|
break;
|
|
|
|
case 0x3B:
|
|
DataOut[1][1] = V & 0x7F;
|
|
UpdateIOBus(1);
|
|
break;
|
|
|
|
case 0x3C:
|
|
DataDir[0][1] = V & 0x7F;
|
|
UpdateIOBus(0);
|
|
break;
|
|
|
|
case 0x3D:
|
|
DataDir[1][1] = V & 0x7F;
|
|
UpdateIOBus(1);
|
|
break;
|
|
|
|
case 0x3E:
|
|
DirectModeEn[0] = (bool)(V & 0x1);
|
|
UpdateIOBus(0);
|
|
|
|
DirectModeEn[1] = (bool)(V & 0x2);
|
|
UpdateIOBus(1);
|
|
break;
|
|
|
|
case 0x3F:
|
|
ExLatchEn[0] = (bool)(V & 0x1);
|
|
ExLatchEn[1] = (bool)(V & 0x2);
|
|
break;
|
|
|
|
default:
|
|
SS_DBG(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Unknown write of 0x%02x to 0x%02x\n", V, A);
|
|
break;
|
|
|
|
}
|
|
|
|
if(PendingCommand >= 0)
|
|
nt = timestamp + 1;
|
|
|
|
SS_SetEventNT(&events[SS_EVENT_SMPC], nt);
|
|
}
|
|
|
|
uint8 SMPC_Read(const sscpu_timestamp_t timestamp, uint8 A)
|
|
{
|
|
uint8 ret = BusBuffer;
|
|
|
|
A &= 0x3F;
|
|
|
|
switch(A)
|
|
{
|
|
default:
|
|
SS_DBG(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Unknown read from 0x%02x\n", A);
|
|
break;
|
|
|
|
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
|
|
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
|
|
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: case 0x26: case 0x27:
|
|
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
|
|
if(MDFN_UNLIKELY(ExecutingCommand >= 0))
|
|
{
|
|
//SS_DBG(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] Output register %u port read while command 0x%02x is executing.\n", A - 0x10, ExecutingCommand);
|
|
}
|
|
|
|
ret = (OREG - 0x10)[A];
|
|
break;
|
|
|
|
case 0x30:
|
|
if(MDFN_UNLIKELY(ExecutingCommand >= 0))
|
|
{
|
|
//SS_DBG(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] SR port read while command 0x%02x is executing.\n", ExecutingCommand);
|
|
}
|
|
|
|
ret = SR;
|
|
break;
|
|
|
|
case 0x31:
|
|
ret &= ~0x01;
|
|
ret |= SF;
|
|
break;
|
|
|
|
case 0x3A:
|
|
ret = (ret & 0x80) | IOBusState[0];
|
|
break;
|
|
|
|
case 0x3B:
|
|
ret = (ret & 0x80) | IOBusState[1];
|
|
break;
|
|
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void SMPC_ResetTS(void)
|
|
{
|
|
lastts = 0;
|
|
}
|
|
|
|
#define SMPC_WAIT_UNTIL_COND(cond) { \
|
|
case __COUNTER__: \
|
|
ClockCounter = 0; /* before if(), not after, otherwise the variable will overflow eventually. */ \
|
|
if(!(cond)) \
|
|
{ \
|
|
SubPhase = __COUNTER__ - SubPhaseBias - 1; \
|
|
return timestamp + 1000; \
|
|
} \
|
|
}
|
|
|
|
#define SMPC_WAIT_UNTIL_COND_TIMEOUT(cond, n) \
|
|
{ \
|
|
ClockCounter -= (int64)(n) << 32; \
|
|
case __COUNTER__: \
|
|
if(!(cond) && ClockCounter < 0) \
|
|
{ \
|
|
SubPhase = __COUNTER__ - SubPhaseBias - 1; \
|
|
return timestamp + (-ClockCounter + SMPC_ClockRatio - 1) / SMPC_ClockRatio; \
|
|
} \
|
|
ClockCounter = 0; \
|
|
}
|
|
|
|
#define SMPC_EAT_CLOCKS(n) \
|
|
{ \
|
|
ClockCounter -= (int64)(n) << 32; \
|
|
case __COUNTER__: \
|
|
if(ClockCounter < 0) \
|
|
{ \
|
|
SubPhase = __COUNTER__ - SubPhaseBias - 1; \
|
|
return timestamp + (-ClockCounter + SMPC_ClockRatio - 1) / SMPC_ClockRatio; \
|
|
} \
|
|
/*printf("%f\n", (double)ClockCounter / (1LL << 32));*/ \
|
|
} \
|
|
|
|
|
|
static unsigned RTC_BCDInc(uint8 v)
|
|
{
|
|
unsigned tmp = v & 0xF;
|
|
|
|
tmp++;
|
|
|
|
if(tmp >= 0xA)
|
|
tmp += 0x06;
|
|
|
|
tmp += v & 0xF0;
|
|
|
|
if(tmp >= 0xA0)
|
|
tmp += 0x60;
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static void RTC_IncTime(void)
|
|
{
|
|
// Seconds
|
|
if(RTC.second == 0x59)
|
|
{
|
|
RTC.second = 0x00;
|
|
|
|
// Minutes
|
|
if(RTC.minute == 0x59)
|
|
{
|
|
RTC.minute = 0x00;
|
|
|
|
// Hours
|
|
if(RTC.hour == 0x23)
|
|
{
|
|
RTC.hour = 0x00;
|
|
|
|
// Day of week
|
|
if(RTC.wday_mon >= 0x60)
|
|
RTC.wday_mon &= 0x0F;
|
|
else
|
|
RTC.wday_mon += 0x10;
|
|
|
|
//
|
|
static const uint8 mdtab[0x10] = {
|
|
// Jan, Feb, Mar, Apr, May, June, July, Aug, Sept, Oct, Nov, Dec
|
|
0x10, 0x31, 0x28, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31, 0xC1, 0xF5, 0xFF
|
|
};
|
|
const uint8 day_compare = mdtab[RTC.wday_mon & 0x0F] + ((RTC.wday_mon & 0x0F) == 0x02 && ((RTC.year[1] & 0x1F) < 0x1A) && !((RTC.year[1] + ((RTC.year[1] & 0x10) >> 3)) & 0x3));
|
|
|
|
// Day of month
|
|
if(RTC.mday >= day_compare)
|
|
{
|
|
RTC.mday = 0x01;
|
|
|
|
// Month of year
|
|
if((RTC.wday_mon & 0x0F) == 0x0C)
|
|
{
|
|
RTC.wday_mon &= 0xF0;
|
|
RTC.wday_mon |= 0x01;
|
|
|
|
// Year
|
|
unsigned tmp = RTC_BCDInc(RTC.year[1]);
|
|
RTC.year[1] = tmp;
|
|
|
|
if(tmp >= 0x100)
|
|
RTC.year[0] = RTC_BCDInc(RTC.year[0]);
|
|
}
|
|
else
|
|
RTC.wday_mon++;
|
|
}
|
|
else
|
|
RTC.mday = RTC_BCDInc(RTC.mday);
|
|
}
|
|
else
|
|
RTC.hour = RTC_BCDInc(RTC.hour);
|
|
}
|
|
else
|
|
RTC.minute = RTC_BCDInc(RTC.minute);
|
|
}
|
|
else
|
|
RTC.second = RTC_BCDInc(RTC.second);
|
|
}
|
|
|
|
enum { SubPhaseBias = __COUNTER__ + 1 };
|
|
sscpu_timestamp_t SMPC_Update(sscpu_timestamp_t timestamp)
|
|
{
|
|
int64 clocks;
|
|
|
|
if(MDFN_UNLIKELY(timestamp < lastts))
|
|
{
|
|
SS_DBG(SS_DBG_WARNING | SS_DBG_SMPC, "[SMPC] [BUG] timestamp(%d) < lastts(%d)\n", timestamp, lastts);
|
|
clocks = 0;
|
|
}
|
|
else
|
|
{
|
|
clocks = (int64)(timestamp - lastts) * SMPC_ClockRatio;
|
|
lastts = timestamp;
|
|
}
|
|
|
|
ClockCounter += clocks;
|
|
RTC.ClockAccum += clocks;
|
|
JRS.TimeCounter += clocks;
|
|
|
|
switch(SubPhase + SubPhaseBias)
|
|
{
|
|
for(;;)
|
|
{
|
|
default:
|
|
case __COUNTER__:
|
|
|
|
SMPC_WAIT_UNTIL_COND(PendingCommand >= 0 || PendingVB);
|
|
|
|
if(PendingVB && PendingCommand < 0)
|
|
{
|
|
PendingVB = false;
|
|
|
|
if(JRS.OptReadTime)
|
|
JRS.OptWaitUntilTime = std::max<int32>(0, (JRS.TimeCounter >> 32) - JRS.OptReadTime - 5000);
|
|
else
|
|
JRS.OptWaitUntilTime = 0;
|
|
JRS.TimeCounter = 0;
|
|
SMPC_EAT_CLOCKS(234);
|
|
|
|
SR &= ~SR_RESB;
|
|
if(ResetButtonPhysStatus) // FIXME: Semantics may not be right in regards to CMD_RESENAB timing.
|
|
{
|
|
SR |= SR_RESB;
|
|
if(ResetButtonCount >= 0)
|
|
{
|
|
ResetButtonCount++;
|
|
|
|
if(ResetButtonCount >= 3)
|
|
{
|
|
ResetButtonCount = 3;
|
|
|
|
if(ResetNMIEnable)
|
|
{
|
|
CPU[0].SetNMI(false);
|
|
CPU[0].SetNMI(true);
|
|
|
|
ResetButtonCount = -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
ResetButtonCount = 0;
|
|
|
|
//
|
|
// Do RTC increment here
|
|
//
|
|
while(MDFN_UNLIKELY(RTC.ClockAccum >= (4000000ULL << 32)))
|
|
{
|
|
RTC_IncTime();
|
|
RTC.ClockAccum -= (4000000ULL << 32);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
ExecutingCommand = PendingCommand;
|
|
PendingCommand = -1;
|
|
|
|
SMPC_EAT_CLOCKS(92);
|
|
if(ExecutingCommand < 0x20)
|
|
{
|
|
OREG[0x1F] = ExecutingCommand;
|
|
|
|
SS_DBGTI(SS_DBG_SMPC, "[SMPC] Command 0x%02x --- 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x", ExecutingCommand, IREG[0], IREG[1], IREG[2], IREG[3], IREG[4], IREG[5], IREG[6]);
|
|
|
|
if(ExecutingCommand == CMD_MSHON)
|
|
{
|
|
|
|
}
|
|
else if(ExecutingCommand == CMD_SSHON)
|
|
{
|
|
if(!SlaveSH2On)
|
|
SlaveOn();
|
|
}
|
|
else if(ExecutingCommand == CMD_SSHOFF)
|
|
{
|
|
if(SlaveSH2On)
|
|
SlaveOff();
|
|
}
|
|
else if(ExecutingCommand == CMD_SNDON)
|
|
{
|
|
if(!SoundCPUOn)
|
|
TurnSoundCPUOn();
|
|
}
|
|
else if(ExecutingCommand == CMD_SNDOFF)
|
|
{
|
|
if(SoundCPUOn)
|
|
TurnSoundCPUOff();
|
|
}
|
|
else if(ExecutingCommand == CMD_CDON)
|
|
{
|
|
CDOn = true;
|
|
}
|
|
else if(ExecutingCommand == CMD_CDOFF)
|
|
{
|
|
CDOn = false;
|
|
}
|
|
else if(ExecutingCommand == CMD_SYSRES)
|
|
{
|
|
ResetPending = true;
|
|
SMPC_WAIT_UNTIL_COND(!ResetPending);
|
|
|
|
// TODO/FIXME(unreachable currently?):
|
|
}
|
|
else if(ExecutingCommand == CMD_CKCHG352 || ExecutingCommand == CMD_CKCHG320)
|
|
{
|
|
// Devour some time
|
|
|
|
if(SlaveSH2On)
|
|
SlaveOff();
|
|
|
|
if(SoundCPUOn)
|
|
TurnSoundCPUOff();
|
|
|
|
SOUND_Reset(false);
|
|
VDP1::Reset(false);
|
|
VDP2::Reset(false);
|
|
SCU_Reset(false);
|
|
|
|
// Change clock
|
|
PendingClockDivisor = (ExecutingCommand == CMD_CKCHG352) ? CLOCK_DIVISOR_28M : CLOCK_DIVISOR_26M;
|
|
|
|
// Wait for a few vblanks
|
|
SMPC_WAIT_UNTIL_COND(!vb);
|
|
SMPC_WAIT_UNTIL_COND(vb);
|
|
SMPC_WAIT_UNTIL_COND(!vb);
|
|
SMPC_WAIT_UNTIL_COND(vb);
|
|
SMPC_WAIT_UNTIL_COND(!vb);
|
|
SMPC_WAIT_UNTIL_COND(vb);
|
|
|
|
|
|
//
|
|
SMPC_WAIT_UNTIL_COND(!PendingClockDivisor);
|
|
|
|
// Send NMI to master SH-2
|
|
CPU[0].SetNMI(false);
|
|
CPU[0].SetNMI(true);
|
|
}
|
|
else if(ExecutingCommand == CMD_INTBACK)
|
|
{
|
|
//SS_DBGTI(SS_DBG_SMPC, "[SMPC] INTBACK IREG0=0x%02x, IREG1=0x%02x, IREG2=0x%02x, %d", IREG[0], IREG[1], IREG[2], vb);
|
|
|
|
SR &= ~SR_NPE;
|
|
if(IREG[0] & 0xF)
|
|
{
|
|
SMPC_EAT_CLOCKS(952);
|
|
|
|
OREG[0] = (RTC.Valid << 7) | (!ResetNMIEnable << 6);
|
|
|
|
for(unsigned i = 0; i < 7; i++)
|
|
OREG[1 + i] = RTC.raw[i];
|
|
|
|
OREG[0x8] = 0; // TODO FIXME: Cartridge code?
|
|
OREG[0x9] = AreaCode;
|
|
OREG[0xA] = 0x24 |
|
|
((CurrentClockDivisor == CLOCK_DIVISOR_28M) << 6) |
|
|
(SlaveSH2On << 4) |
|
|
(true << 3) | // TODO?: Master NMI
|
|
(true << 1) | // TODO?: sysres
|
|
(SoundCPUOn << 0); // sndres
|
|
|
|
OREG[0xB] = (CDOn << 6) | (1 << 1); // cdres, TODO?: bit1
|
|
|
|
for(unsigned i = 0; i < 4; i++)
|
|
OREG[0xC + i] = SaveMem[i];
|
|
|
|
if(IREG[1] & 0x8)
|
|
SR |= SR_NPE;
|
|
|
|
SR &= ~0x80;
|
|
SR |= 0x0F;
|
|
|
|
SCU_SetInt(SCU_INT_SMPC, true);
|
|
SCU_SetInt(SCU_INT_SMPC, false);
|
|
}
|
|
|
|
// Wait for !vb, wait until (IREG[0] & 0x80), time-optimization wait.
|
|
|
|
if(IREG[1] & 0x8)
|
|
{
|
|
InputLagged = false;
|
|
if (InputCallback)
|
|
InputCallback();
|
|
|
|
|
|
#define JR_WAIT(cond) { SMPC_WAIT_UNTIL_COND((cond) || PendingVB); if(PendingVB) { SS_DBGTI(SS_DBG_SMPC, "[SMPC] abortjr wait"); goto AbortJR; } }
|
|
#define JR_EAT(n) { SMPC_EAT_CLOCKS(n); if(PendingVB) { SS_DBGTI(SS_DBG_SMPC, "[SMPC] abortjr eat"); goto AbortJR; } }
|
|
#define JR_WRNYB(val) \
|
|
{ \
|
|
if(!JRS.OWP) \
|
|
{ \
|
|
if(JRS.PDCounter > 0) \
|
|
{ \
|
|
SR = (SR & ~SR_PDL) | ((JRS.PDCounter < 0x2) ? SR_PDL : 0); \
|
|
SR = (SR & ~0xF) | (JRS.Mode[0] << 0) | (JRS.Mode[1] << 2); \
|
|
SR |= SR_NPE; \
|
|
SR |= 0x80; \
|
|
SCU_SetInt(SCU_INT_SMPC, true); \
|
|
SCU_SetInt(SCU_INT_SMPC, false); \
|
|
JR_WAIT((bool)(IREG[0] & 0x80) == JRS.NextContBit || (IREG[0] & 0x40)); \
|
|
if(IREG[0] & 0x40) \
|
|
{ \
|
|
SS_DBGTI(SS_DBG_SMPC, "[SMPC] Big Read Break"); \
|
|
goto AbortJR; \
|
|
} \
|
|
JRS.NextContBit = !JRS.NextContBit; \
|
|
} \
|
|
if(JRS.PDCounter < 0xFF) \
|
|
JRS.PDCounter++; \
|
|
} \
|
|
\
|
|
OREG[(JRS.OWP >> 1)] &= 0x0F << ((JRS.OWP & 1) << 2); \
|
|
OREG[(JRS.OWP >> 1)] |= ((val) & 0xF) << (((JRS.OWP & 1) ^ 1) << 2); \
|
|
JRS.OWP = (JRS.OWP + 1) & 0x3F; \
|
|
}
|
|
|
|
#define JR_BS IOBusState[JRS.CurPort]
|
|
|
|
#define JR_TH_TR(th, tr) \
|
|
{ \
|
|
DataDir[JRS.CurPort][0] = ((th >= 0) << 6) | ((tr >= 0) << 5); \
|
|
DataOut[JRS.CurPort][0] = (DataOut[JRS.CurPort][0] & 0x1F) | (((th) > 0) << 6) | (((tr) > 0) << 5); \
|
|
UpdateIOBus(JRS.CurPort); \
|
|
}
|
|
|
|
JR_WAIT(!vb);
|
|
JRS.NextContBit = true;
|
|
if(SR & SR_NPE)
|
|
{
|
|
JR_WAIT((bool)(IREG[0] & 0x80) == JRS.NextContBit || (IREG[0] & 0x40));
|
|
if(IREG[0] & 0x40)
|
|
{
|
|
SS_DBGTI(SS_DBG_SMPC, "[SMPC] Break");
|
|
goto AbortJR;
|
|
}
|
|
JRS.NextContBit = !JRS.NextContBit;
|
|
}
|
|
|
|
JRS.PDCounter = 0;
|
|
JRS.TimeOptEn = !(IREG[1] & 0x2);
|
|
JRS.Mode[0] = (IREG[1] >> 4) & 0x3;
|
|
JRS.Mode[1] = (IREG[1] >> 6) & 0x3;
|
|
|
|
JRS.OptReadTime = 0;
|
|
JRS.OptEatTime = std::max<int32>(0, (JRS.OptWaitUntilTime - (JRS.TimeCounter >> 32)));
|
|
JRS.OptWaitUntilTime = 0;
|
|
|
|
if(JRS.TimeOptEn)
|
|
{
|
|
SMPC_WAIT_UNTIL_COND_TIMEOUT(PendingVB, JRS.OptEatTime);
|
|
if(PendingVB)
|
|
{
|
|
SS_DBGTI(SS_DBG_SMPC, "[SMPC] abortjr timeopt");
|
|
goto AbortJR;
|
|
}
|
|
SS_SetEventNT(&events[SS_EVENT_MIDSYNC], timestamp + 1);
|
|
}
|
|
|
|
JRS.StartTime = JRS.TimeCounter >> 32;
|
|
JR_EAT(120);
|
|
JRS.OWP = 0;
|
|
for(JRS.CurPort = 0; JRS.CurPort < 2; JRS.CurPort++)
|
|
{
|
|
JR_EAT(380);
|
|
|
|
if(JRS.Mode[JRS.CurPort] & 0x2)
|
|
continue;
|
|
|
|
// TODO: 255-byte read size mode.
|
|
|
|
JRS.ID1 = 0;
|
|
JR_TH_TR(1, 1);
|
|
JR_EAT(50);
|
|
JRS.work[0] = JR_BS;
|
|
JRS.ID1 |= ((((JRS.work[0] >> 3) | (JRS.work[0] >> 2)) & 1) << 3) | ((((JRS.work[0] >> 1) | (JRS.work[0] >> 0)) & 1) << 2);
|
|
|
|
JR_TH_TR(0, 1);
|
|
JR_EAT(50);
|
|
JRS.work[1] = JR_BS;
|
|
JRS.ID1 |= ((((JRS.work[1] >> 3) | (JRS.work[1] >> 2)) & 1) << 1) | ((((JRS.work[1] >> 1) | (JRS.work[1] >> 0)) & 1) << 0);
|
|
|
|
//printf("%02x, %02x\n", JRS.work[0], JRS.work[1]);
|
|
|
|
if(JRS.ID1 == 0xB)
|
|
{
|
|
// Saturn digital pad.
|
|
JR_TH_TR(1, 0)
|
|
JR_EAT(50);
|
|
JRS.work[2] = JR_BS;
|
|
|
|
JR_TH_TR(0, 0)
|
|
JR_EAT(50);
|
|
JRS.work[3] = JR_BS;
|
|
|
|
JR_EAT(30);
|
|
|
|
JR_WRNYB(0xF); // Multitap ID
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(0x1); // Number of connected devices behind multitap
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(0x0); // Peripheral ID-2.
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(0x2); // Data size.
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.work[1] & 0xF);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.work[2] & 0xF);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.work[3] & 0xF);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB((JRS.work[0] & 0xF) | 0x7);
|
|
JR_EAT(21);
|
|
|
|
//JR_EAT();
|
|
|
|
//
|
|
//
|
|
//
|
|
}
|
|
else if(JRS.ID1 == 0x3 || JRS.ID1 == 0x5)
|
|
{
|
|
JR_TH_TR(0, 0)
|
|
JR_EAT(50);
|
|
JR_WAIT(!(JR_BS & 0x10));
|
|
JRS.ID2 = ((JR_BS & 0xF) << 4);
|
|
|
|
JR_TH_TR(0, 1)
|
|
JR_EAT(50);
|
|
JR_WAIT(JR_BS & 0x10);
|
|
JRS.ID2 |= ((JR_BS & 0xF) << 0);
|
|
|
|
//printf("%d, %02x %02x\n", JRS.CurPort, JRS.ID1, JRS.ID2);
|
|
|
|
if(JRS.ID1 == 0x3)
|
|
JRS.ID2 = 0xE3;
|
|
|
|
if((JRS.ID2 & 0xF0) == 0x40) // Multitap
|
|
{
|
|
JR_TH_TR(0, 0)
|
|
JR_EAT(50);
|
|
JR_WAIT(!(JR_BS & 0x10));
|
|
JRS.IDTap = ((JRS.ID2 & 0xF) << 4) | (JR_BS & 0xF);
|
|
|
|
JR_TH_TR(0, 1)
|
|
JR_EAT(50);
|
|
JR_WAIT(JR_BS & 0x10);
|
|
}
|
|
else
|
|
JRS.IDTap = 0xF1;
|
|
|
|
JRS.TapCounter = 0;
|
|
JRS.TapCount = (JRS.IDTap & 0xF);
|
|
while(JRS.TapCounter < JRS.TapCount)
|
|
{
|
|
if(JRS.TapCount > 1)
|
|
{
|
|
JR_TH_TR(0, 0)
|
|
JR_EAT(50);
|
|
JR_WAIT(!(JR_BS & 0x10));
|
|
JRS.ID2 = ((JR_BS & 0xF) << 4);
|
|
|
|
JR_TH_TR(0, 1)
|
|
JR_EAT(50);
|
|
JR_WAIT(JR_BS & 0x10);
|
|
JRS.ID2 |= ((JR_BS & 0xF) << 0);
|
|
}
|
|
JRS.ReadCounter = 0;
|
|
JRS.ReadCount = ((JRS.ID2 & 0xF0) == 0xF0) ? 0 : (JRS.ID2 & 0xF);
|
|
while(JRS.ReadCounter < JRS.ReadCount)
|
|
{
|
|
JR_TH_TR(0, 0)
|
|
JR_EAT(50);
|
|
JR_WAIT(!(JR_BS & 0x10));
|
|
JRS.ReadBuffer[JRS.ReadCounter] = ((JR_BS & 0xF) << 4);
|
|
|
|
JR_TH_TR(0, 1)
|
|
JR_EAT(50);
|
|
JR_WAIT(JR_BS & 0x10);
|
|
JRS.ReadBuffer[JRS.ReadCounter] |= ((JR_BS & 0xF) << 0);
|
|
JRS.ReadCounter++;
|
|
}
|
|
|
|
if(!JRS.TapCounter)
|
|
{
|
|
JR_WRNYB(JRS.IDTap >> 4);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.IDTap >> 0);
|
|
JR_EAT(21);
|
|
}
|
|
|
|
//printf("What: %d, %02x\n", JRS.TapCounter, JRS.ID2);
|
|
|
|
JR_WRNYB(JRS.ID2 >> 4);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.ID2 >> 0);
|
|
JR_EAT(21);
|
|
|
|
JRS.WriteCounter = 0;
|
|
while(JRS.WriteCounter < JRS.ReadCounter)
|
|
{
|
|
JR_WRNYB(JRS.ReadBuffer[JRS.WriteCounter] >> 4);
|
|
JR_EAT(21);
|
|
|
|
JR_WRNYB(JRS.ReadBuffer[JRS.WriteCounter] >> 0);
|
|
JR_EAT(21);
|
|
|
|
JRS.WriteCounter++;
|
|
}
|
|
JRS.TapCounter++;
|
|
}
|
|
// Saturn analog joystick, keyboard, multitap
|
|
// OREG[0x0] = 0xF1; // Upper nybble, multitap ID. Lower nybble, number of connected devices behind multitap.
|
|
// OREG[0x1] = 0x02; // Upper nybble, peripheral ID 2. Lower nybble, data size.
|
|
}
|
|
else
|
|
{
|
|
JR_WRNYB(0xF);
|
|
JR_WRNYB(0x0);
|
|
}
|
|
JR_EAT(26);
|
|
JR_TH_TR(-1, -1);
|
|
}
|
|
|
|
SR = (SR & ~SR_NPE);
|
|
SR = (SR & ~0xF) | (JRS.Mode[0] << 0) | (JRS.Mode[1] << 2);
|
|
SR = (SR & ~SR_PDL) | ((JRS.PDCounter < 0x2) ? SR_PDL : 0);
|
|
SR |= 0x80;
|
|
SCU_SetInt(SCU_INT_SMPC, true);
|
|
SCU_SetInt(SCU_INT_SMPC, false);
|
|
|
|
if(JRS.TimeOptEn)
|
|
JRS.OptReadTime = std::max<int32>(0, (JRS.TimeCounter >> 32) - JRS.StartTime);
|
|
}
|
|
AbortJR:;
|
|
// TODO: Set TH TR to inputs on abort.
|
|
}
|
|
else if(ExecutingCommand == CMD_SETTIME) // Warning: Execute RTC setting atomically(all values or none) in regards to emulator exit/power toggle.
|
|
{
|
|
SMPC_EAT_CLOCKS(380);
|
|
|
|
RTC.ClockAccum = 0; // settime resets sub-second count.
|
|
RTC.Valid = true;
|
|
|
|
for(unsigned i = 0; i < 7; i++)
|
|
RTC.raw[i] = IREG[i];
|
|
}
|
|
else if(ExecutingCommand == CMD_SETSMEM) // Warning: Execute save mem setting(all values or none) atomically in regards to emulator exit/power toggle.
|
|
{
|
|
SMPC_EAT_CLOCKS(234);
|
|
|
|
for(unsigned i = 0; i < 4; i++)
|
|
SaveMem[i] = IREG[i];
|
|
}
|
|
else if(ExecutingCommand == CMD_NMIREQ)
|
|
{
|
|
CPU[0].SetNMI(false);
|
|
CPU[0].SetNMI(true);
|
|
}
|
|
else if(ExecutingCommand == CMD_RESENAB)
|
|
{
|
|
ResetNMIEnable = true;
|
|
}
|
|
else if(ExecutingCommand == CMD_RESDISA)
|
|
{
|
|
ResetNMIEnable = false;
|
|
}
|
|
}
|
|
|
|
ExecutingCommand = -1;
|
|
SF = false;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SMPC_SetVB(sscpu_timestamp_t event_timestamp, bool vb_status)
|
|
{
|
|
if(vb ^ vb_status)
|
|
{
|
|
if(vb_status) // Going into vblank
|
|
PendingVB = true;
|
|
|
|
SS_SetEventNT(&events[SS_EVENT_SMPC], event_timestamp + 1);
|
|
}
|
|
|
|
vb = vb_status;
|
|
}
|
|
|
|
/*static const std::vector<InputDeviceInfoStruct> InputDeviceInfoSSVPort =
|
|
{
|
|
// None
|
|
{
|
|
"none",
|
|
"none",
|
|
NULL,
|
|
IDII_Empty
|
|
},
|
|
|
|
// Digital Gamepad
|
|
{
|
|
"gamepad",
|
|
"Digital Gamepad",
|
|
"Standard Saturn digital gamepad.",
|
|
IODevice_Gamepad_IDII
|
|
},
|
|
|
|
// 3D Gamepad
|
|
{
|
|
"3dpad",
|
|
"3D Control Pad",
|
|
"3D Control Pad",
|
|
IODevice_3DPad_IDII
|
|
},
|
|
|
|
// Mouse
|
|
{
|
|
"mouse",
|
|
"Mouse",
|
|
"Mouse",
|
|
IODevice_Mouse_IDII
|
|
},
|
|
|
|
// Steering Wheel
|
|
{
|
|
"wheel",
|
|
"Steering Wheel",
|
|
"Arcade Racer/Racing Controller",
|
|
IODevice_Wheel_IDII
|
|
},
|
|
|
|
// Mission Stick
|
|
{
|
|
"mission",
|
|
"Mission Stick",
|
|
"Mission Stick",
|
|
IODevice_Mission_IDII
|
|
},
|
|
#if 0
|
|
// Mission Stick (No Autofire)
|
|
{
|
|
"missionwoa",
|
|
"Mission (No AF)",
|
|
"Mission Stick, without autofire functionality(for less things to map).",
|
|
IODevice_MissionNoAF_IDII
|
|
},
|
|
#endif
|
|
// Dual Mission Stick
|
|
{
|
|
"dmission",
|
|
"Dual Mission",
|
|
"Dual Mission Sticks, useful for \"Panzer Dragoon Zwei\". With 30 inputs to map, don't get distracted by..LOOK A LOBSTER!",
|
|
IODevice_DualMission_IDII
|
|
},
|
|
|
|
#if 0
|
|
// Dual Mission Stick (No Autofire)
|
|
{
|
|
"dmissionwoa",
|
|
"Dual Mission (No AF)",
|
|
"Dual Mission Sticks (No Autofire)",
|
|
IODevice_DualMissionNoAF_IDII
|
|
},
|
|
#endif
|
|
|
|
// Keyboard (101-key US)
|
|
{
|
|
"keyboard",
|
|
"Keyboard (US)",
|
|
"101-key US keyboard.",
|
|
IODevice_Keyboard_US101_IDII,
|
|
InputDeviceInfoStruct::FLAG_KEYBOARD
|
|
},
|
|
|
|
#if 0
|
|
// Keyboard (Japanese)
|
|
{
|
|
"jpkeyboard",
|
|
"Keyboard (JP)",
|
|
"89-key Japanese keyboard.",
|
|
IODevice_JPKeyboard_IDII,
|
|
InputDeviceInfoStruct::FLAG_KEYBOARD
|
|
},
|
|
#endif
|
|
};
|
|
|
|
static IDIISG IDII_Builtin =
|
|
{
|
|
{ "reset", "Reset", -1, IDIT_RESET_BUTTON },
|
|
{ "smpc_reset", "SMPC Reset", -1, IDIT_BUTTON },
|
|
};
|
|
|
|
static const std::vector<InputDeviceInfoStruct> InputDeviceInfoBuiltin =
|
|
{
|
|
{
|
|
"builtin",
|
|
"builtin",
|
|
NULL,
|
|
IDII_Builtin
|
|
}
|
|
};
|
|
|
|
const std::vector<InputPortInfoStruct> SMPC_PortInfo =
|
|
{
|
|
{ "port1", "Virtual Port 1", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port2", "Virtual Port 2", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port3", "Virtual Port 3", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port4", "Virtual Port 4", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port5", "Virtual Port 5", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port6", "Virtual Port 6", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port7", "Virtual Port 7", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port8", "Virtual Port 8", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port9", "Virtual Port 9", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port10", "Virtual Port 10", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port11", "Virtual Port 11", InputDeviceInfoSSVPort, "gamepad" },
|
|
{ "port12", "Virtual Port 12", InputDeviceInfoSSVPort, "gamepad" },
|
|
|
|
{ "builtin", "Builtin", InputDeviceInfoBuiltin, "builtin" },
|
|
};*/
|
|
|
|
|
|
}
|