flycast/core/hw/naomi/naomi.cpp

812 lines
17 KiB
C++

/*
This file is a mix of my code, Zezu's, and duno wtf-else (most likely ElSemi's ?)
*/
#include "types.h"
#include "cfg/cfg.h"
#include "hw/holly/sb.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/holly/holly_intc.h"
#include "hw/maple/maple_cfg.h"
#include "hw/sh4/sh4_sched.h"
#include "hw/sh4/modules/dmac.h"
#include "hw/aica/aica_if.h"
#include "naomi.h"
#include "naomi_cart.h"
#include "naomi_regs.h"
#include "naomi_m3comm.h"
#include "serialize.h"
#include "network/output.h"
//#define NAOMI_COMM
static NaomiM3Comm m3comm;
static const u32 BoardID = 0x980055AA;
static u32 GSerialBuffer, BSerialBuffer;
static int GBufPos, BBufPos;
static int GState, BState;
static int GOldClk, BOldClk;
static int BControl, BCmd, BLastCmd;
static int GControl, GCmd, GLastCmd;
static int SerStep, SerStep2;
#ifdef NAOMI_COMM
u32 CommOffset;
u32* CommSharedMem;
HANDLE CommMapFile=INVALID_HANDLE_VALUE;
#endif
/*
El numero de serie solo puede contener:
0-9 (0x30-0x39)
A-H (0x41-0x48)
J-N (0x4A-0x4E)
P-Z (0x50-0x5A)
*/
static u8 BSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
static u8 GSerial[]="\xB7"/*CRC1*/"\x19"/*CRC2*/"0123234437897584372973927387463782196719782697849162342198671923649";
static u8 midiTxBuf[4];
static u32 midiTxBufIndex;
static unsigned int ShiftCRC(unsigned int CRC,unsigned int rounds)
{
const unsigned int Magic=0x10210000;
unsigned int i;
for(i=0;i<rounds;++i)
{
if(CRC&0x80000000)
CRC=(CRC<<1)+Magic;
else
CRC=(CRC<<1);
}
return CRC;
}
static unsigned short CRCSerial(const u8 *Serial,unsigned int len)
{
unsigned int CRC=0xDEBDEB00;
unsigned int i;
for(i=0;i<len;++i)
{
unsigned char c=Serial[i];
//CRC&=0xFFFFFF00;
CRC|=c;
CRC=ShiftCRC(CRC,8);
}
CRC=ShiftCRC(CRC,8);
return (u16)(CRC>>16);
}
void NaomiInit()
{
u16 CRC;
CRC=CRCSerial(BSerial+2,0x2E);
BSerial[0]=(u8)(CRC>>8);
BSerial[1]=(u8)(CRC);
CRC=CRCSerial(GSerial+2,0x2E);
GSerial[0]=(u8)(CRC>>8);
GSerial[1]=(u8)(CRC);
}
void NaomiBoardIDWrite(const u16 Data)
{
int Dat=Data&8;
int Clk=Data&4;
int Rst=Data&0x20;
int Sta=Data&0x10;
if(Rst)
{
BState=0;
BBufPos=0;
}
if(Clk!=BOldClk && !Clk) //Falling Edge clock
{
//State change
if(BState==0 && Sta)
BState=1;
if(BState==1 && !Sta)
BState=2;
if((BControl&0xfff)==0xFF0) //Command mode
{
BCmd<<=1;
if(Dat)
BCmd|=1;
else
BCmd&=0xfffffffe;
}
//State processing
if(BState==1) //LoadBoardID
{
BSerialBuffer=BoardID;
BBufPos=0; //??
}
if(BState==2) //ShiftBoardID
{
BBufPos++;
}
}
BOldClk=Clk;
}
u16 NaomiBoardIDRead()
{
if((BControl&0xff)==0xFE)
return 0xffff;
return (BSerialBuffer&(1<<(31-BBufPos)))?8:0;
}
static u32 AdaptByte(u8 val)
{
return val<<24;
}
void NaomiBoardIDWriteControl(const u16 Data)
{
if((Data&0xfff)==0xF30 && BCmd!=BLastCmd)
{
if((BCmd&0x81)==0x81)
{
SerStep2=(BCmd>>1)&0x3f;
BSerialBuffer=0x00000000; //First block contains CRC
BBufPos=0;
}
if((BCmd&0xff)==0x55) //Load Offset 0
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2])>>1;
}
if((BCmd&0xff)==0xAA) //Load Offset 1
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+1]);
}
if((BCmd&0xff)==0x54)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+2]);
}
if((BCmd&0xff)==0xA8)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+3]);
}
if((BCmd&0xff)==0x50)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+4]);
}
if((BCmd&0xff)==0xA0)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+5]);
}
if((BCmd&0xff)==0x40)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+6]);
}
if((BCmd&0xff)==0x80)
{
BState=2;
BBufPos=0;
BSerialBuffer=AdaptByte(BSerial[8*SerStep2+7]);
}
BLastCmd=BCmd;
}
BControl=Data;
}
static void NaomiGameIDProcessCmd()
{
if(GCmd!=GLastCmd)
{
if((GCmd&0x81)==0x81)
{
SerStep=(GCmd>>1)&0x3f;
GSerialBuffer=0x00000000; //First block contains CRC
GBufPos=0;
}
if((GCmd&0xff)==0x55) //Load Offset 0
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep])>>0;
}
if((GCmd&0xff)==0xAA) //Load Offset 1
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+1]);
}
if((GCmd&0xff)==0x54)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+2]);
}
if((GCmd&0xff)==0xA8)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+3]);
}
if((GCmd&0xff)==0x50)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+4]);
}
if((GCmd&0xff)==0xA0)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+5]);
}
if((GCmd&0xff)==0x40)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+6]);
}
if((GCmd&0xff)==0x80)
{
GState=2;
GBufPos=0;
GSerialBuffer=AdaptByte(GSerial[8*SerStep+7]);
}
GLastCmd=GCmd;
}
}
void NaomiGameIDWrite(const u16 Data)
{
int Dat=Data&0x01;
int Clk=Data&0x02;
int Rst=Data&0x04;
int Sta=Data&0x08;
int Cmd=Data&0x10;
if(Rst)
{
GState=0;
GBufPos=0;
}
if(Clk!=GOldClk && !Clk) //Falling Edge clock
{
//State change
if(GState==0 && Sta)
GState=1;
if(GState==1 && !Sta)
GState=2;
//State processing
if(GState==1) //LoadBoardID
{
GSerialBuffer=BoardID;
GBufPos=0; //??
}
if(GState==2) //ShiftBoardID
GBufPos++;
if(GControl!=Cmd && !Cmd)
{
NaomiGameIDProcessCmd();
}
GControl=Cmd;
}
if(Clk!=GOldClk && Clk) //Rising Edge clock
{
if(Cmd) //Command mode
{
GCmd<<=1;
if(Dat)
GCmd|=1;
else
GCmd&=0xfffffffe;
GControl=Cmd;
}
}
GOldClk=Clk;
}
u16 NaomiGameIDRead()
{
return (GSerialBuffer&(1<<(31-GBufPos)))?1:0;
}
//DIMM board
//Uses interrupt ext#3 (holly_EXT_PCI)
//status/flags ? 0x1 is some completion/init flag(?), 0x100 is the interrupt disable flag (?)
//n1 bios rev g (n2/epr-23605b has similar behavior of not same):
//3c=0x1E03
//40=0
//44=0
//48=0
//read 4c
//wait for 4c not 0
//4c=[4c]-1
//Naomi 2 bios epr-23609
//read 3c
//wait 4c to be non 0
//
//SO the writes to 3c/stuff are not relaced with 4c '1'
//If the dimm board has some internal cpu/pic logic
//4c '1' seems to be the init done bit (?)
//n1/n2 clears it after getting a non 0 value
//n1 bios writes the value -1, meaning it expects the bit 0 to be set
//.//
u32 reg_dimm_command; // command, written, 0x1E03 some flag ?
u32 reg_dimm_offsetl;
u32 reg_dimm_parameterl;
u32 reg_dimm_parameterh;
u32 reg_dimm_status = 0x11;
static bool aw_ram_test_skipped = false;
void naomi_process(u32 command, u32 offsetl, u32 parameterl, u32 parameterh)
{
DEBUG_LOG(NAOMI, "Naomi process 0x%04X 0x%04X 0x%04X 0x%04X", command, offsetl, parameterl, parameterh);
DEBUG_LOG(NAOMI, "Possible format 0 %d 0x%02X 0x%04X",command >> 15,(command & 0x7e00) >> 9, command & 0x1FF);
DEBUG_LOG(NAOMI, "Possible format 1 0x%02X 0x%02X", (command & 0xFF00) >> 8,command & 0xFF);
// command: param1 & 3f << 9 | param2
// offsetl, paraml, paramh: params 3 4 5
// HOLLY::SB_IML2EXT |= 8 when done
u32 param=(command&0xFF);
if (param==0xFF)
{
DEBUG_LOG(NAOMI, "invalid opcode or smth ?");
}
static int opcd=0;
//else if (param!=3)
if (opcd<255)
{
reg_dimm_command=0x8000 | (opcd%12<<9) | (0x0);
DEBUG_LOG(NAOMI, "new reg is 0x%X", reg_dimm_command);
asic_RaiseInterrupt(holly_EXP_PCI);
DEBUG_LOG(NAOMI, "Interrupt raised");
opcd++;
}
}
u32 ReadMem_naomi(u32 address, u32 size)
{
verify(size != 1);
if (unlikely(CurrentCartridge == NULL))
{
INFO_LOG(NAOMI, "called without cartridge");
return 0xFFFF;
}
if (address >= NAOMI_COMM2_CTRL_addr && address <= NAOMI_COMM2_STATUS1_addr)
return m3comm.ReadMem(address, size);
else
return CurrentCartridge->ReadMem(address, size);
}
void WriteMem_naomi(u32 address, u32 data, u32 size)
{
if (unlikely(CurrentCartridge == NULL))
{
INFO_LOG(NAOMI, "called without cartridge");
return;
}
if (address >= NAOMI_COMM2_CTRL_addr && address <= NAOMI_COMM2_STATUS1_addr
&& settings.platform.isNaomi())
m3comm.WriteMem(address, data, size);
else
CurrentCartridge->WriteMem(address, data, size);
}
//Dma Start
void Naomi_DmaStart(u32 addr, u32 data)
{
if (SB_GDEN==0)
{
INFO_LOG(NAOMI, "Invalid (NAOMI)GD-DMA start, SB_GDEN=0. Ignoring it.");
return;
}
SB_GDST |= data & 1;
if (SB_GDST == 0)
return;
if (!m3comm.DmaStart(addr, data) && CurrentCartridge != NULL)
{
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
u32 start = SB_GDSTAR & 0x1FFFFFE0;
u32 len = (SB_GDLEN + 31) & ~31;
SB_GDLEND = 0;
while (len > 0)
{
u32 block_len = len;
void* ptr = CurrentCartridge->GetDmaPtr(block_len);
if (block_len == 0)
{
INFO_LOG(NAOMI, "Aborted DMA transfer. Read past end of cart?");
break;
}
WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len);
CurrentCartridge->AdvancePtr(block_len);
len -= block_len;
start += block_len;
SB_GDLEND += block_len;
}
SB_GDSTARD = start;
}
else
{
SB_GDSTARD = SB_GDSTAR + SB_GDLEN;
SB_GDLEND = SB_GDLEN;
}
SB_GDST = 0;
asic_RaiseInterrupt(holly_GDROM_DMA);
}
void Naomi_DmaEnable(u32 addr, u32 data)
{
SB_GDEN=data&1;
if (SB_GDEN==0 && SB_GDST==1)
{
INFO_LOG(NAOMI, "(NAOMI)GD-DMA aborted");
SB_GDST=0;
}
}
void naomi_reg_Init()
{
#ifdef NAOMI_COMM
CommMapFile = CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // max. object size
0x1000*4, // buffer size
L"Global\\nullDC_103_naomi_comm"); // name of mapping object
if (CommMapFile == NULL || CommMapFile==INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Could not create file mapping object (%d).\nTrying to open existing one\n"), GetLastError());
CommMapFile=OpenFileMapping(
FILE_MAP_ALL_ACCESS, // read/write access
FALSE, // do not inherit the name
L"Global\\nullDC_103_naomi_comm"); // name of mapping object
}
if (CommMapFile == NULL || CommMapFile==INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("Could not open existing file either\n"), GetLastError());
CommMapFile=INVALID_HANDLE_VALUE;
}
else
{
printf("NAOMI: Created \"Global\\nullDC_103_naomi_comm\"\n");
CommSharedMem = (u32*) MapViewOfFile(CommMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
0x1000*4);
if (CommSharedMem == NULL)
{
_tprintf(TEXT("Could not map view of file (%d).\n"),
GetLastError());
CloseHandle(CommMapFile);
CommMapFile=INVALID_HANDLE_VALUE;
}
else
printf("NAOMI: Mapped CommSharedMem\n");
}
#endif
NaomiInit();
networkOutput.init();
}
void naomi_reg_Term()
{
#ifdef NAOMI_COMM
if (CommSharedMem)
{
UnmapViewOfFile(CommSharedMem);
}
if (CommMapFile!=INVALID_HANDLE_VALUE)
{
CloseHandle(CommMapFile);
}
#endif
m3comm.closeNetwork();
networkOutput.term();
}
void naomi_reg_Reset(bool hard)
{
sb_rio_register(SB_GDST_addr, RIO_WF, 0, &Naomi_DmaStart);
sb_rio_register(SB_GDEN_addr, RIO_WF, 0, &Naomi_DmaEnable);
SB_GDST = 0;
SB_GDEN = 0;
aw_ram_test_skipped = false;
GSerialBuffer = 0;
BSerialBuffer = 0;
GBufPos = 0;
BBufPos = 0;
GState = 0;
BState = 0;
GOldClk = 0;
BOldClk = 0;
BControl = 0;
BCmd = 0;
BLastCmd = 0;
GControl = 0;
GCmd = 0;
GLastCmd = 0;
SerStep = 0;
SerStep2 = 0;
reg_dimm_command = 0;
reg_dimm_offsetl = 0;
reg_dimm_parameterl = 0;
reg_dimm_parameterh = 0;
reg_dimm_status = 0x11;
m3comm.closeNetwork();
if (hard)
naomi_cart_Close();
}
static u8 aw_maple_devs;
static u64 coin_chute_time[4];
static u8 awDigitalOuput;
u32 libExtDevice_ReadMem_A0_006(u32 addr,u32 size) {
addr &= 0x7ff;
//printf("libExtDevice_ReadMem_A0_006 %d@%08x: %x\n", size, addr, mem600[addr]);
switch (addr)
{
// case 0:
// return 0;
// case 4:
// return 1;
case 0x280:
// 0x00600280 r 0000dcba
// a/b - 1P/2P coin inputs (JAMMA), active low
// c/d - 3P/4P coin inputs (EX. IO board), active low
//
// (ab == 0) -> BIOS skip RAM test
if (!aw_ram_test_skipped)
{
// Skip RAM test at startup
aw_ram_test_skipped = true;
return 0;
}
{
u8 coin_input = 0xF;
u64 now = sh4_sched_now64();
for (int slot = 0; slot < 4; slot++)
{
if (maple_atomiswave_coin_chute(slot))
{
// ggx15 needs 4 or 5 reads to register the coin but it needs to be limited to avoid coin errors
// 1 s of cpu time is too much, 1/2 s seems to work, let's use 100 ms
if (coin_chute_time[slot] == 0 || now - coin_chute_time[slot] < SH4_MAIN_CLOCK / 10)
{
if (coin_chute_time[slot] == 0)
coin_chute_time[slot] = now;
coin_input &= ~(1 << slot);
}
}
else
{
coin_chute_time[slot] = 0;
}
}
return coin_input;
}
case 0x284: // Atomiswave maple devices
// ddcc0000 where cc/dd are the types of devices on maple bus 2 and 3:
// 0: regular AtomisWave controller
// 1: light gun
// 2,3: mouse/trackball
//printf("NAOMI 600284 read %x\n", aw_maple_devs);
return aw_maple_devs;
case 0x288:
// ??? Dolphin Blue
return 0;
case 0x28c:
return awDigitalOuput;
}
INFO_LOG(NAOMI, "Unhandled read @ %x sz %d", addr, size);
return 0xFF;
}
void libExtDevice_WriteMem_A0_006(u32 addr,u32 data,u32 size) {
addr &= 0x7ff;
//printf("libExtDevice_WriteMem_A0_006 %d@%08x: %x\n", size, addr, data);
switch (addr)
{
case 0x284: // Atomiswave maple devices
DEBUG_LOG(NAOMI, "NAOMI 600284 write %x", data);
aw_maple_devs = data & 0xF0;
return;
case 0x288:
// ??? Dolphin Blue
return;
case 0x28C: // Digital output
if ((u8)data != awDigitalOuput)
{
if (atomiswaveForceFeedback)
// Wheel force feedback:
// bit 0 direction (0 pos, 1 neg)
// bit 1-4 strength
networkOutput.output("awffb", (u8)data);
else
{
u8 changes = data ^ awDigitalOuput;
for (int i = 0; i < 8; i++)
if (changes & (1 << i))
{
std::string name = "lamp" + std::to_string(i);
networkOutput.output(name.c_str(), (data >> i) & 1);
}
}
awDigitalOuput = data;
DEBUG_LOG(NAOMI, "AW output %02x", data);
}
return;
default:
break;
}
INFO_LOG(NAOMI, "Unhandled write @ %x (%d): %x", addr, size, data);
}
void naomi_Serialize(Serializer& ser)
{
ser << GSerialBuffer;
ser << BSerialBuffer;
ser << GBufPos;
ser << BBufPos;
ser << GState;
ser << BState;
ser << GOldClk;
ser << BOldClk;
ser << BControl;
ser << BCmd;
ser << BLastCmd;
ser << GControl;
ser << GCmd;
ser << GLastCmd;
ser << SerStep;
ser << SerStep2;
ser.serialize(BSerial, 69);
ser.serialize(GSerial, 69);
ser << reg_dimm_command;
ser << reg_dimm_offsetl;
ser << reg_dimm_parameterl;
ser << reg_dimm_parameterh;
ser << reg_dimm_status;
ser << aw_maple_devs;
ser << coin_chute_time;
ser << aw_ram_test_skipped;
ser << midiTxBuf;
ser << midiTxBufIndex;
// TODO serialize m3comm?
}
void naomi_Deserialize(Deserializer& deser)
{
if (deser.version() < Deserializer::V9_LIBRETRO)
{
deser.skip<u32>(); // naomi_updates
deser.skip<u32>(); // BoardID
}
deser >> GSerialBuffer;
deser >> BSerialBuffer;
deser >> GBufPos;
deser >> BBufPos;
deser >> GState;
deser >> BState;
deser >> GOldClk;
deser >> BOldClk;
deser >> BControl;
deser >> BCmd;
deser >> BLastCmd;
deser >> GControl;
deser >> GCmd;
deser >> GLastCmd;
deser >> SerStep;
deser >> SerStep2;
deser.deserialize(BSerial, 69);
deser.deserialize(GSerial, 69);
deser >> reg_dimm_command;
deser >> reg_dimm_offsetl;
deser >> reg_dimm_parameterl;
deser >> reg_dimm_parameterh;
deser >> reg_dimm_status;
if (deser.version() < Deserializer::V11)
deser.skip<u8>();
else if (deser.version() >= Deserializer::V14)
deser >> aw_maple_devs;
if (deser.version() >= Deserializer::V20)
{
deser >> coin_chute_time;
deser >> aw_ram_test_skipped;
}
if (deser.version() >= Deserializer::V27)
{
deser >> midiTxBuf;
deser >> midiTxBufIndex;
}
else
{
midiTxBufIndex = 0;
}
}
static void midiSend(u8 b1, u8 b2, u8 b3)
{
aica_midiSend(b1);
aica_midiSend(b2);
aica_midiSend(b3);
aica_midiSend((b1 ^ b2 ^ b3) & 0x7f);
}
static void forceFeedbackMidiReceiver(u8 data)
{
static float position = 8192.f;
static float torque;
position = std::min(16383.f, std::max(0.f, position + torque));
if (data & 0x80)
midiTxBufIndex = 0;
midiTxBuf[midiTxBufIndex] = data;
if (midiTxBufIndex == 3 && ((midiTxBuf[0] ^ midiTxBuf[1] ^ midiTxBuf[2]) & 0x7f) == midiTxBuf[3])
{
if (midiTxBuf[0] == 0x84)
torque = ((midiTxBuf[1] << 7) | midiTxBuf[2]) - 0x80;
else if (midiTxBuf[0] == 0xff)
{
torque = 0;
position = 8192;
}
// required: b1 & 0x1f == 0x10 && b1 & 0x40 == 0
midiSend(0x90, ((int)position >> 7) & 0x7f, (int)position & 0x7f);
// decoding from FFB Arcade Plugin (by Boomslangnz)
// https://github.com/Boomslangnz/FFBArcadePlugin/blob/master/Game%20Files/Demul.cpp
if (midiTxBuf[0] == 0x85 && midiTxBuf[1] == 0x3f)
MapleConfigMap::UpdateVibration(0, std::max(0.f, (float)(midiTxBuf[2] - 1) / 24.f), 0.f, 5);
if (midiTxBuf[0] != 0xfd)
networkOutput.output("midiffb", (midiTxBuf[0] << 16) | (midiTxBuf[1]) << 8 | midiTxBuf[2]);
}
midiTxBufIndex = (midiTxBufIndex + 1) % ARRAY_SIZE(midiTxBuf);
}
void initMidiForceFeedback()
{
aica_setMidiReceiver(forceFeedbackMidiReceiver);
}