/*
This file is part of Flycast.
Flycast 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.
Flycast 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 Flycast. If not, see .
*/
#include "types.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/aica/aica_if.h"
#include "hw/hwreg.h"
#include "naomi.h"
#include "naomi_cart.h"
#include "naomi_regs.h"
#include "naomi_m3comm.h"
#include "serialize.h"
#include "network/output.h"
#include "hw/sh4/modules/modules.h"
#include "rend/gui.h"
#include "printer.h"
#include "hw/flashrom/x76f100.h"
#include
static NaomiM3Comm m3comm;
Multiboard *multiboard;
static X76F100SerialFlash mainSerialId;
static X76F100SerialFlash romSerialId;
static u8 midiTxBuf[4];
static u32 midiTxBufIndex;
static int dmaSchedId = -1;
void NaomiBoardIDWrite(const u16 data)
{
// bit 2: clock
// bit 3: data
// bit 4: reset (x76f100 only)
// bit 5: chip select
mainSerialId.writeCS(data & 0x20);
mainSerialId.writeRST(data & 0x10);
mainSerialId.writeSCL(data & 4);
mainSerialId.writeSDA(data & 8);
}
u16 NaomiBoardIDRead()
{
// bit 0 indicates the eeprom is a X76F100, otherwise the BIOS expects an AT93C46
// bit 3 is xf76f100 SDA
// bit 4 is at93c46 DO
return (mainSerialId.readSDA() << 3) | 1;
}
void NaomiGameIDWrite(const u16 data)
{
romSerialId.writeCS(data & 4);
romSerialId.writeRST(data & 8);
romSerialId.writeSCL(data & 2);
romSerialId.writeSDA(data & 1);
}
u16 NaomiGameIDRead()
{
return romSerialId.readSDA() << 15;
}
static bool aw_ram_test_skipped = false;
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);
}
static int naomiDmaSched(int tag, int sch_cycl, int jitter, void *arg)
{
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?");
for (u32 i = 0; i < len; i += 8, start += 8)
addrspace::write64(start, 0);
SB_GDLEND += len;
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;
SB_GDST = 0;
asic_RaiseInterrupt(holly_GDROM_DMA);
return 0;
}
//Dma Start
static void Naomi_DmaStart(u32 addr, u32 data)
{
if ((data & 1) == 0 || SB_GDST == 1)
return;
if (SB_GDEN == 0)
{
INFO_LOG(NAOMI, "Invalid NAOMI-DMA start, SB_GDEN=0. Ignoring it.");
return;
}
if (multiboard != nullptr && multiboard->dmaStart())
{
}
else if (!m3comm.DmaStart(addr, data) && CurrentCartridge != nullptr)
{
DEBUG_LOG(NAOMI, "NAOMI-DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
verify(1 == SB_GDDIR);
SB_GDST = 1;
// Max G1 bus rate: 50 MHz x 16 bits
// SH4_access990312_e.xls: 14.4 MB/s from GD-ROM to system RAM
// Here: 7 MB/s
sh4_sched_request(dmaSchedId, std::min(SB_GDLEN * 27, SH4_MAIN_CLOCK));
return;
}
else
{
SB_GDSTARD = SB_GDSTAR + SB_GDLEN;
SB_GDLEND = SB_GDLEN;
}
asic_RaiseInterrupt(holly_GDROM_DMA);
}
static void Naomi_DmaEnable(u32 addr, u32 data)
{
SB_GDEN = data & 1;
if (SB_GDEN == 0 && SB_GDST == 1)
{
INFO_LOG(NAOMI, "NAOMI-DMA aborted");
SB_GDST = 0;
sh4_sched_request(dmaSchedId, -1);
}
}
void naomi_reg_Init()
{
networkOutput.init();
static const u8 romSerialData[0x84] = {
0x19, 0x00, 0xaa, 0x55,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x69, 0x79, 0x68, 0x6b, 0x74, 0x6d, 0x68, 0x6d,
0xa1, 0x09, ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'
};
romSerialId.setData(romSerialData);
mainSerialId.setData(romSerialData);
if (dmaSchedId == -1)
dmaSchedId = sh4_sched_register(0, naomiDmaSched);
}
void setGameSerialId(const u8 *data)
{
romSerialId.setData(data);
}
void naomi_reg_Term()
{
if (multiboard != nullptr)
delete multiboard;
multiboard = nullptr;
m3comm.closeNetwork();
networkOutput.term();
if (dmaSchedId != -1)
sh4_sched_unregister(dmaSchedId);
dmaSchedId = -1;
}
void naomi_reg_Reset(bool hard)
{
hollyRegs.setWriteHandler(Naomi_DmaStart);
hollyRegs.setWriteHandler(Naomi_DmaEnable);
SB_GDST = 0;
SB_GDEN = 0;
sh4_sched_request(dmaSchedId, -1);
aw_ram_test_skipped = false;
m3comm.closeNetwork();
if (hard)
{
naomi_cart_Close();
if (multiboard != nullptr)
{
delete multiboard;
multiboard = nullptr;
}
if (settings.naomi.multiboard)
multiboard = new Multiboard();
networkOutput.reset();
mainSerialId.reset();
romSerialId.reset();
}
else if (multiboard != nullptr)
multiboard->reset();
}
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);
}
static bool ffbCalibrating;
void naomi_Serialize(Serializer& ser)
{
mainSerialId.serialize(ser);
romSerialId.serialize(ser);
ser << aw_maple_devs;
ser << coin_chute_time;
ser << aw_ram_test_skipped;
ser << midiTxBuf;
ser << midiTxBufIndex;
// TODO serialize m3comm?
ser << ffbCalibrating;
sh4_sched_serialize(ser, dmaSchedId);
}
void naomi_Deserialize(Deserializer& deser)
{
if (deser.version() < Deserializer::V40)
{
deser.skip(); // GSerialBuffer
deser.skip(); // BSerialBuffer
deser.skip(); // GBufPos
deser.skip(); // BBufPos
deser.skip(); // GState
deser.skip(); // BState
deser.skip(); // GOldClk
deser.skip(); // BOldClk
deser.skip(); // BControl
deser.skip(); // BCmd
deser.skip(); // BLastCmd
deser.skip(); // GControl
deser.skip(); // GCmd
deser.skip(); // GLastCmd
deser.skip(); // SerStep
deser.skip(); // SerStep2
deser.skip(69); // BSerial
deser.skip(69); // GSerial
}
else
{
mainSerialId.deserialize(deser);
romSerialId.deserialize(deser);
}
if (deser.version() < Deserializer::V36)
{
deser.skip(); // reg_dimm_command;
deser.skip(); // reg_dimm_offsetl;
deser.skip(); // reg_dimm_parameterl;
deser.skip(); // reg_dimm_parameterh;
deser.skip(); // reg_dimm_status;
}
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;
}
if (deser.version() >= Deserializer::V34)
deser >> ffbCalibrating;
else
ffbCalibrating = false;
if (deser.version() >= Deserializer::V45)
sh4_sched_deserialize(deser, dmaSchedId);
}
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)
ffbCalibrating = true;
else if (midiTxBuf[0] == 0xf0)
ffbCalibrating = false;
if (!ffbCalibrating)
{
int direction = -1;
if (NaomiGameInputs != nullptr)
direction = NaomiGameInputs->axes[0].inverted ? 1 : -1;
position = std::clamp(mapleInputState[0].fullAxes[0] * direction / 4.f + 8192.f, 0.f, 16383.f);
}
// 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)
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) % std::size(midiTxBuf);
}
void initMidiForceFeedback()
{
aica::setMidiReceiver(forceFeedbackMidiReceiver);
}
struct DriveSimPipe : public SerialPort::Pipe
{
void write(u8 data) override
{
if (buffer.empty() && data != 2)
return;
if (buffer.size() == 7)
{
u8 checksum = 0;
for (u8 b : buffer)
checksum += b;
if (checksum == data)
{
int newTacho = (buffer[2] - 1) * 100;
if (newTacho != tacho)
{
tacho = newTacho;
networkOutput.output("tachometer", tacho);
}
int newSpeed = buffer[3] - 1;
if (newSpeed != speed)
{
speed = newSpeed;
networkOutput.output("speedometer", speed);
}
if (!config::NetworkOutput)
{
char message[16];
sprintf(message, "Speed: %3d", speed);
gui_display_notification(message, 1000);
}
}
buffer.clear();
}
else
{
buffer.push_back(data);
}
}
void reset()
{
buffer.clear();
tacho = -1;
speed = -1;
}
private:
std::vector buffer;
int tacho = -1;
int speed = -1;
};
void initDriveSimSerialPipe()
{
static DriveSimPipe pipe;
pipe.reset();
SCIFSerialPort::Instance().setPipe(&pipe);
}
G2PrinterConnection g2PrinterConnection;
u32 G2PrinterConnection::read(u32 addr, u32 size)
{
if (addr == STATUS_REG_ADDR)
{
u32 ret = printerStat;
printerStat |= 1;
DEBUG_LOG(NAOMI, "Printer status == %x", ret);
return ret;
}
else
{
INFO_LOG(NAOMI, "Unhandled G2 Ext read<%d> at %x", size, addr);
return 0;
}
}
void G2PrinterConnection::write(u32 addr, u32 size, u32 data)
{
switch (addr)
{
case DATA_REG_ADDR:
for (u32 i = 0; i < size; i++)
printer::print((char)(data >> (i * 8)));
break;
case STATUS_REG_ADDR:
DEBUG_LOG(NAOMI, "Printer status = %x", data);
printerStat &= ~1;
break;
default:
INFO_LOG(NAOMI, "Unhandled G2 Ext write<%d> at %x: %x", size, addr, data);
break;
}
}