project64/Source/Project64-core/N64System/Mips/Disk.cpp

387 lines
12 KiB
C++
Raw Normal View History

// Based on MAME's N64DD driver code by Happy_
2016-01-19 18:53:18 +00:00
#include "stdafx.h"
2016-01-20 13:31:29 +00:00
#include "Disk.h"
2021-04-14 05:34:15 +00:00
#include <Project64-core/N64System/N64System.h>
#include <Project64-core/N64System/N64Disk.h>
2016-01-19 18:53:18 +00:00
#include <Project64-core/N64System/SystemGlobals.h>
2021-04-14 05:34:15 +00:00
#include <Project64-core/N64System/Mips/Register.h>
#include <Project64-core/N64System/Mips/SystemTiming.h>
2016-01-19 18:53:18 +00:00
2016-01-27 21:41:31 +00:00
#ifdef _WIN32
#include <Windows.h>
#else
#include <sys/time.h>
#endif
2016-05-25 02:06:10 +00:00
uint8_t dd_swapdelay;
2016-01-20 13:31:29 +00:00
bool dd_write;
bool dd_reset_hold;
uint32_t dd_track_offset, dd_zone;
2016-01-20 16:43:23 +00:00
uint32_t dd_start_block, dd_current;
2016-01-20 13:31:29 +00:00
2016-01-19 18:53:18 +00:00
void DiskCommand()
{
2016-01-20 13:31:29 +00:00
//ASIC_CMD_STATUS - Commands
uint32_t cmd = g_Reg->ASIC_CMD;
WriteTrace(TraceN64System, TraceDebug, "N64DD CMD %08X - DATA %08X", cmd, g_Reg->ASIC_DATA);
2016-01-19 18:53:18 +00:00
#ifdef _WIN32
2016-01-20 13:31:29 +00:00
SYSTEMTIME sysTime;
::GetLocalTime(&sysTime);
// BCD format needed for 64DD RTC
2016-08-27 21:29:11 +00:00
uint8_t year = (uint8_t)(((sysTime.wYear / 10 % 10) << 4) | (sysTime.wYear % 10));
2016-01-20 13:31:29 +00:00
uint8_t month = (uint8_t)(((sysTime.wMonth / 10) << 4) | (sysTime.wMonth % 10));
uint8_t day = (uint8_t)(((sysTime.wDay / 10) << 4) | (sysTime.wDay % 10));
uint8_t hour = (uint8_t)(((sysTime.wHour / 10) << 4) | (sysTime.wHour % 10));
uint8_t minute = (uint8_t)(((sysTime.wMinute / 10) << 4) | (sysTime.wMinute % 10));
uint8_t second = (uint8_t)(((sysTime.wSecond / 10) << 4) | (sysTime.wSecond % 10));
2016-01-19 18:53:18 +00:00
#else
2016-01-20 13:31:29 +00:00
time_t ltime;
ltime = time(&ltime);
2016-01-27 21:41:31 +00:00
2016-01-20 13:31:29 +00:00
struct tm result = { 0 };
localtime_r(&ltime, &result);
// BCD format needed for 64DD RTC
2016-08-27 21:29:11 +00:00
uint8_t year = (uint8_t)(((result.tm_year / 10 % 10) << 4) | (result.tm_year % 10));
2016-01-27 21:41:31 +00:00
uint8_t month = (uint8_t)((((result.tm_mon + 1) / 10) << 4) | ((result.tm_mon + 1) % 10));
2016-01-20 13:31:29 +00:00
uint8_t day = (uint8_t)(((result.tm_mday / 10) << 4) | (result.tm_mday % 10));
uint8_t hour = (uint8_t)(((result.tm_hour / 10) << 4) | (result.tm_hour % 10));
uint8_t minute = (uint8_t)(((result.tm_min / 10) << 4) | (result.tm_min % 10));
uint8_t second = (uint8_t)(((result.tm_sec / 10) << 4) | (result.tm_sec % 10));
2016-01-19 18:53:18 +00:00
#endif
// Used for seek times
bool isSeek = false;
2016-01-20 13:31:29 +00:00
switch (cmd & 0xFFFF0000)
{
case 0x00010000:
// Seek read
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_CUR_TK = g_Reg->ASIC_DATA | 0x60000000;
dd_write = false;
isSeek = true;
2016-01-20 13:31:29 +00:00
break;
case 0x00020000:
// Seek write
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_CUR_TK = g_Reg->ASIC_DATA | 0x60000000;
dd_write = true;
isSeek = true;
2016-01-20 13:31:29 +00:00
break;
case 0x00080000:
// Unset disk changed bit
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_STATUS &= ~DD_STATUS_DISK_CHNG; break;
case 0x00090000:
// Unset reset and disk changed bit bit
2016-05-25 02:06:10 +00:00
g_Reg->ASIC_STATUS &= ~DD_STATUS_RST_STATE;
g_Reg->ASIC_STATUS &= ~DD_STATUS_DISK_CHNG;
// F-Zero X + Expansion Kit fix so it doesn't enable "swapping" at boot
dd_swapdelay = 0;
2021-04-12 11:35:39 +00:00
if (g_Disk != nullptr)
g_Reg->ASIC_STATUS |= DD_STATUS_DISK_PRES;
2016-05-25 02:06:10 +00:00
break;
2016-01-20 13:31:29 +00:00
case 0x00120000:
// RTC get year and month
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_DATA = (year << 24) | (month << 16); break;
case 0x00130000:
// RTC get day and hour
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_DATA = (day << 24) | (hour << 16); break;
case 0x00140000:
// RTC get minute and second
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_DATA = (minute << 24) | (second << 16); break;
case 0x001B0000:
// Disk inquiry
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_DATA = 0x00000000; break;
}
if (isSeek)
{
if (g_System->DiskSeekTimingType() == DiskSeek_Turbo)
{
// Instant response for turbo
2021-01-29 22:37:27 +00:00
// Set timer for seek response
2021-01-29 22:37:27 +00:00
g_SystemTimer->SetTimer(g_SystemTimer->DDSeekTimer, 0, false);
// Set timer for motor
2021-01-29 22:37:27 +00:00
g_SystemTimer->SetTimer(g_SystemTimer->DDMotorTimer, 0, false);
}
else /* if (g_System->DiskSeekTimingType() == DiskSeek_Slow) */
{
// Emulate seek times, send interrupt later
2021-01-29 22:37:27 +00:00
uint32_t seektime = 0;
// Start motor, can take half a second, delay the response
2021-01-29 22:37:27 +00:00
if (g_Reg->ASIC_STATUS & DD_STATUS_MTR_N_SPIN)
{
2021-01-29 22:37:27 +00:00
seektime += (0x5A00000 / 2);
g_Reg->ASIC_STATUS &= ~DD_STATUS_MTR_N_SPIN;
}
// Get zone to calculate seek times
2021-01-29 22:37:27 +00:00
uint32_t track = g_Reg->ASIC_CUR_TK >> 16 & 0x0FFF;
uint32_t zone = 0;
uint32_t zonebound = 0;
for (uint8_t i = 0; i < 8; i++)
{
zonebound += ddZoneTrackSize[i];
if (track < zonebound)
{
zone = i;
if (g_Reg->ASIC_CUR_TK & 0x10000000)
zone++;
break;
}
}
2020-06-05 15:25:01 +00:00
// Add seek delay depending on the zone (this is inaccurate timing, but close enough)
2021-01-29 22:37:27 +00:00
seektime += 0x179200;
2021-01-29 22:37:27 +00:00
switch (zone)
{
case 0:
case 1:
default:
seektime += track * 38;
break;
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
seektime += 0x13C * 38 + (track - 0x13C) * 46;
break;
case 8:
seektime += 0x13C * 38 + 0x2E9 * 46 + (track - 0x425) * 58;
break;
}
// Set timer for seek response
2021-01-29 22:37:27 +00:00
g_SystemTimer->SetTimer(g_SystemTimer->DDSeekTimer, seektime, false);
2020-06-05 15:25:01 +00:00
// Set timer for motor to shutdown in 5 seconds, reset the timer if other seek commands were sent
2021-01-29 22:37:27 +00:00
g_SystemTimer->SetTimer(g_SystemTimer->DDMotorTimer, 0x5A00000 * 5, false);
}
}
else
{
// Other commands are basically instant
g_Reg->ASIC_STATUS |= DD_STATUS_MECHA_INT;
g_Reg->FAKE_CAUSE_REGISTER |= CAUSE_IP3;
g_Reg->CheckInterrupts();
}
2016-01-19 18:53:18 +00:00
}
void DiskReset(void)
{
2016-01-20 13:31:29 +00:00
//ASIC_HARD_RESET 0xAAAA0000
WriteTrace(TraceN64System, TraceDebug, "N64DD reset");
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_STATUS |= DD_STATUS_RST_STATE;
2016-05-25 02:06:10 +00:00
dd_swapdelay = 0;
2021-04-12 11:35:39 +00:00
if (g_Disk != nullptr)
2016-05-25 02:06:10 +00:00
g_Reg->ASIC_STATUS |= DD_STATUS_DISK_PRES;
2016-01-19 18:53:18 +00:00
}
void DiskBMControl(void)
{
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_CUR_SECTOR = g_Reg->ASIC_BM_CTL & 0x00FF0000;
2016-01-20 16:43:23 +00:00
2016-01-20 13:31:29 +00:00
if ((g_Reg->ASIC_CUR_SECTOR >> 16) == 0x00)
{
2016-01-20 16:43:23 +00:00
dd_start_block = 0;
dd_current = 0;
2016-01-20 13:31:29 +00:00
}
else if ((g_Reg->ASIC_CUR_SECTOR >> 16) == 0x5A)
{
2016-01-20 16:43:23 +00:00
dd_start_block = 1;
dd_current = 0;
2016-01-20 13:31:29 +00:00
}
2016-01-20 16:43:23 +00:00
2016-01-20 13:31:29 +00:00
if (g_Reg->ASIC_BM_CTL & DD_BM_CTL_BLK_TRANS)
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_BLOCK;
if (g_Reg->ASIC_BM_CTL & DD_BM_CTL_MECHA_RST)
g_Reg->ASIC_STATUS &= ~DD_STATUS_MECHA_INT;
if (g_Reg->ASIC_BM_CTL & DD_BM_CTL_RESET)
dd_reset_hold = true;
if (!(g_Reg->ASIC_BM_CTL & DD_BM_CTL_RESET) && dd_reset_hold)
{
dd_reset_hold = false;
g_Reg->ASIC_STATUS &= ~(DD_STATUS_BM_INT | DD_STATUS_BM_ERR | DD_STATUS_DATA_RQ | DD_STATUS_C2_XFER);
g_Reg->ASIC_BM_STATUS = 0;
g_Reg->ASIC_CUR_SECTOR = 0;
2016-01-20 16:43:23 +00:00
dd_start_block = 0;
dd_current = 0;
2016-01-20 13:31:29 +00:00
}
if (!(g_Reg->ASIC_STATUS & DD_STATUS_MECHA_INT) && !(g_Reg->ASIC_STATUS & DD_STATUS_BM_INT))
g_Reg->FAKE_CAUSE_REGISTER &= ~CAUSE_IP3;
if (g_Reg->ASIC_BM_CTL & DD_BM_CTL_START)
{
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_RUNNING;
DiskBMUpdate();
}
}
void DiskGapSectorCheck()
{
// On 64DD status register read
// Buffer manager interrupt, gap sector check
2016-01-20 13:31:29 +00:00
if (g_Reg->ASIC_STATUS & DD_STATUS_BM_INT)
{
2016-01-20 16:43:23 +00:00
if (SECTORS_PER_BLOCK < dd_current)
2016-01-20 13:31:29 +00:00
{
g_Reg->ASIC_STATUS &= ~DD_STATUS_BM_INT;
g_Reg->FAKE_CAUSE_REGISTER &= ~CAUSE_IP3;
g_Reg->CheckInterrupts();
2016-01-20 13:31:29 +00:00
DiskBMUpdate();
}
}
2016-05-25 02:06:10 +00:00
// Delay disk swapping by removing the disk for a certain amount of time, then insert the newly loaded disk (after 50 status register reads, here)
2021-04-12 11:35:39 +00:00
if (!(g_Reg->ASIC_STATUS & DD_STATUS_DISK_PRES) && g_Disk != nullptr && g_Settings->LoadBool(GameRunning_LoadingInProgress) == false)
2016-05-25 02:06:10 +00:00
{
dd_swapdelay++;
if (dd_swapdelay >= 50)
2016-05-25 02:06:10 +00:00
{
g_Reg->ASIC_STATUS |= (DD_STATUS_DISK_PRES | DD_STATUS_DISK_CHNG);
dd_swapdelay = 0;
WriteTrace(TraceN64System, TraceDebug, "N64DD swap done");
2016-05-25 02:06:10 +00:00
}
}
2016-01-20 13:31:29 +00:00
}
void DiskBMUpdate()
{
if (!(g_Reg->ASIC_BM_STATUS & DD_BM_STATUS_RUNNING))
return;
2016-01-20 13:31:29 +00:00
if (dd_write)
{
//Write Data
if (dd_current < SECTORS_PER_BLOCK)
2016-01-20 13:31:29 +00:00
{
//User Sector
2019-08-11 16:13:52 +00:00
if (!DiskBMReadWrite(true))
g_Reg->ASIC_STATUS |= DD_STATUS_DATA_RQ;
else
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_MICRO;
2016-01-20 16:43:23 +00:00
dd_current += 1;
2016-01-20 13:31:29 +00:00
}
2016-01-20 16:43:23 +00:00
else if (dd_current < SECTORS_PER_BLOCK + 1)
2016-01-20 13:31:29 +00:00
{
// C2 sector
2016-01-20 13:31:29 +00:00
if (g_Reg->ASIC_BM_STATUS & DD_BM_STATUS_BLOCK)
{
dd_start_block = 1 - dd_start_block;
dd_current = 0;
2019-08-11 16:13:52 +00:00
if (!DiskBMReadWrite(true))
g_Reg->ASIC_STATUS |= DD_STATUS_DATA_RQ;
else
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_MICRO;
2016-01-20 16:43:23 +00:00
dd_current += 1;
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_BM_STATUS &= ~DD_BM_STATUS_BLOCK;
}
else
{
2016-01-20 16:43:23 +00:00
dd_current += 1;
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_BM_STATUS &= ~DD_BM_STATUS_RUNNING;
}
}
g_Reg->ASIC_STATUS |= DD_STATUS_BM_INT;
g_Reg->FAKE_CAUSE_REGISTER |= CAUSE_IP3;
g_Reg->CheckInterrupts();
return;
}
else
{
// Read data
2020-10-12 04:31:28 +00:00
if (((g_Reg->ASIC_CUR_TK >> 16) & 0x1FFF) == 6 && g_Reg->ASIC_CUR_SECTOR == 0 && g_Disk->GetCountry() != Country_Unknown)
2016-01-20 13:31:29 +00:00
{
// Copy protection if retail disk
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_STATUS &= ~DD_STATUS_DATA_RQ;
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_MICRO;
}
else if (dd_current < SECTORS_PER_BLOCK)
2016-01-20 13:31:29 +00:00
{
// User sector
2019-08-11 16:13:52 +00:00
if (!DiskBMReadWrite(false))
g_Reg->ASIC_STATUS |= DD_STATUS_DATA_RQ;
else
g_Reg->ASIC_BM_STATUS |= DD_BM_STATUS_MICRO;
2016-01-20 16:43:23 +00:00
dd_current += 1;
2016-01-20 13:31:29 +00:00
}
2016-01-20 16:43:23 +00:00
else if (dd_current < SECTORS_PER_BLOCK + 4)
2016-01-20 13:31:29 +00:00
{
// C2 sectors (all 00s)
2016-01-20 16:43:23 +00:00
dd_current += 1;
if (dd_current == SECTORS_PER_BLOCK + 4)
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_STATUS |= DD_STATUS_C2_XFER;
}
2016-01-20 16:43:23 +00:00
else if (dd_current == SECTORS_PER_BLOCK + 4)
2016-01-20 13:31:29 +00:00
{
// Gap sector
2016-01-20 13:31:29 +00:00
if (g_Reg->ASIC_BM_STATUS & DD_BM_STATUS_BLOCK)
{
2016-01-20 16:43:23 +00:00
dd_start_block = 1 - dd_start_block;
dd_current = 0;
2016-01-20 13:31:29 +00:00
g_Reg->ASIC_BM_STATUS &= ~DD_BM_STATUS_BLOCK;
}
else
{
g_Reg->ASIC_BM_STATUS &= ~DD_BM_STATUS_RUNNING;
}
}
g_Reg->ASIC_STATUS |= DD_STATUS_BM_INT;
g_Reg->FAKE_CAUSE_REGISTER |= CAUSE_IP3;
g_Reg->CheckInterrupts();
}
}
2021-01-19 05:58:59 +00:00
bool DiskBMReadWrite(bool /*write*/)
2016-01-20 13:31:29 +00:00
{
// Returns true if error
uint16_t head = ((g_Reg->ASIC_CUR_TK >> 16) / 0x1000) & 1;
2016-01-20 13:31:29 +00:00
uint16_t track = (g_Reg->ASIC_CUR_TK >> 16) & 0xFFF;
2021-01-19 05:58:59 +00:00
uint16_t block = (uint16_t)dd_start_block;
uint16_t sector = (uint16_t)dd_current;
uint16_t sectorsize = (((g_Reg->ASIC_HOST_SECBYTE & 0x00FF0000) >> 16) + 1);
uint32_t addr = g_Disk->GetDiskAddressBlock(head, track, block, sector, sectorsize);
2019-08-11 16:13:52 +00:00
if (addr == 0xFFFFFFFF)
{
// Error
2019-08-11 16:13:52 +00:00
return true;
}
else
{
g_Disk->SetDiskAddressBuffer(addr);
return false;
}
}
void DiskDMACheck(void)
{
if (g_Reg->PI_CART_ADDR_REG == 0x05000000)
{
g_Reg->ASIC_STATUS &= ~(DD_STATUS_BM_INT | DD_STATUS_BM_ERR | DD_STATUS_C2_XFER);
g_Reg->FAKE_CAUSE_REGISTER &= ~CAUSE_IP3;
g_Reg->CheckInterrupts();
}
else if (g_Reg->PI_CART_ADDR_REG == 0x05000400)
{
g_Reg->ASIC_STATUS &= ~(DD_STATUS_BM_INT | DD_STATUS_BM_ERR | DD_STATUS_DATA_RQ);
g_Reg->FAKE_CAUSE_REGISTER &= ~CAUSE_IP3;
g_Reg->CheckInterrupts();
}
2016-01-19 18:53:18 +00:00
}