/* 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/sh4/sh4_sched.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 "oslib/oslib.h" #include "printer.h" #include "hw/flashrom/x76f100.h" #include "midiffb.h" #include "atomiswave.h" #include static NaomiM3Comm m3comm; Multiboard *multiboard; static X76F100SerialFlash mainSerialId; static X76F100SerialFlash romSerialId; static int dmaSchedId = -1; static int dmaXferDelay = 10; // cart dma xfer speed, in cycles/byte (default 20 MB/s) 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; } 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_GDSTARD; u32 len = std::min(((SB_GDLEN + 31) & ~31) - SB_GDLEND, 1024); SB_GDLEND += len; 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); break; } WriteMemBlock_nommu_ptr(start, (u32*)ptr, block_len); CurrentCartridge->AdvancePtr(block_len); len -= block_len; start += block_len; } SB_GDSTARD = start; if (SB_GDLEN <= SB_GDLEND) { SB_GDST = 0; asic_RaiseInterrupt(holly_GDROM_DMA); return 0; } else { return std::min(SB_GDLEN - SB_GDLEND, 1024) * dmaXferDelay; } } //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 %x", SB_GDSTAR, SB_GDLEN); verify(1 == SB_GDDIR); SB_GDST = 1; SB_GDSTARD = SB_GDSTAR & 0x1FFFFFE0; SB_GDLEND = 0; // Max G1 bus rate: 50 MHz x 16 bits // SH4_access990312_e.xls: 14.4 MB/s from GD-ROM to system RAM // Here: 20 MB/s sh4_sched_request(dmaSchedId, std::min(SB_GDLEN, 1024) * dmaXferDelay); return; } else { SB_GDSTARD = SB_GDSTAR + SB_GDLEN; SB_GDLEND = SB_GDLEN; } asic_RaiseInterrupt(holly_GDROM_DMA); } void Naomi_setDmaDelay() { if (settings.platform.isAtomiswave() || settings.content.gameId == "FORCE FIVE" || settings.content.gameId == "KENJU") // 7 MB/s for Atomiwave games and conversions dmaXferDelay = 27; else dmaXferDelay = 10; } 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() { 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); } // Sets the full content of the rom board serial eeprom (132 bytes) // including response to reset and read/write passwords. void setGameSerialId(const u8 *data) { romSerialId.setData(data); } // Return the protected data from the rom board serial eeprom (112 bytes) // excluding response to reset and passwords. const u8 *getGameSerialId() { return romSerialId.getProtectedData(); } void naomi_reg_Term() { if (multiboard != nullptr) delete multiboard; multiboard = nullptr; m3comm.closeNetwork(); networkOutput.term(); if (dmaSchedId != -1) sh4_sched_unregister(dmaSchedId); dmaSchedId = -1; midiffb::term(); } 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); atomiswave::reset(); 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(); midiffb::reset(); } void naomi_Serialize(Serializer& ser) { mainSerialId.serialize(ser); romSerialId.serialize(ser); atomiswave::serialize(ser); // TODO serialize m3comm? midiffb::serialize(ser); 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; } atomiswave::deserialize(deser); midiffb::deserialize(deser); if (deser.version() >= Deserializer::V45) sh4_sched_deserialize(deser, dmaSchedId); } 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]; snprintf(message, sizeof(message), "Speed: %3d", speed); os_notify(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; } }