BizHawk/waterbox/ss/smpc.cpp

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" },
};*/
}