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