506 lines
12 KiB
C++
506 lines
12 KiB
C++
/*
|
|
aica interface
|
|
Handles RTC, Display mode reg && arm reset reg !
|
|
arm7 is handled on a separate arm plugin now
|
|
*/
|
|
|
|
#include "aica_if.h"
|
|
#include "aica_mem.h"
|
|
#include "hw/holly/sb.h"
|
|
#include "hw/holly/holly_intc.h"
|
|
#include "hw/sh4/sh4_mem.h"
|
|
#include "hw/sh4/sh4_sched.h"
|
|
#include "profiler/profiler.h"
|
|
#include "hw/sh4/dyna/blockmanager.h"
|
|
#include "hw/arm7/arm7.h"
|
|
|
|
#include <ctime>
|
|
|
|
VArray2 aica_ram;
|
|
u32 VREG;
|
|
u32 ARMRST;
|
|
u32 rtc_EN;
|
|
int dma_sched_id = -1;
|
|
u32 RealTimeClock;
|
|
int rtc_schid = -1;
|
|
u32 SB_ADST;
|
|
|
|
u32 GetRTC_now()
|
|
{
|
|
// rtc kept static for netplay when savestate is not loaded
|
|
if (config::GGPOEnable)
|
|
// 1/1/70 00:00:00
|
|
return (20 * 365 + 5) * 24 * 60 * 60;
|
|
|
|
// The Dreamcast Epoch time is 1/1/50 00:00 but without support for time zone or DST.
|
|
// We compute the TZ/DST current time offset and add it to the result
|
|
// as if we were in the UTC time zone (as well as the DC Epoch)
|
|
time_t rawtime = time(NULL);
|
|
struct tm localtm, gmtm;
|
|
localtm = *localtime(&rawtime);
|
|
gmtm = *gmtime(&rawtime);
|
|
gmtm.tm_isdst = -1;
|
|
time_t time_offset = mktime(&localtm) - mktime(&gmtm);
|
|
// 1/1/50 to 1/1/70 is 20 years and 5 leap days
|
|
return (20 * 365 + 5) * 24 * 60 * 60 + rawtime + time_offset;
|
|
}
|
|
|
|
template<typename T>
|
|
T ReadMem_aica_rtc(u32 addr)
|
|
{
|
|
switch (addr & 0xFF)
|
|
{
|
|
case 0:
|
|
return (T)(RealTimeClock >> 16);
|
|
case 4:
|
|
return (T)(RealTimeClock & 0xFFFF);
|
|
case 8:
|
|
return 0;
|
|
}
|
|
|
|
WARN_LOG(AICA, "ReadMem_aica_rtc: invalid address %x sz %d", addr, (int)sizeof(T));
|
|
return 0;
|
|
}
|
|
template u8 ReadMem_aica_rtc<>(u32 addr);
|
|
template u16 ReadMem_aica_rtc<>(u32 addr);
|
|
template u32 ReadMem_aica_rtc<>(u32 addr);
|
|
|
|
template<typename T>
|
|
void WriteMem_aica_rtc(u32 addr, T data)
|
|
{
|
|
switch (addr & 0xFF)
|
|
{
|
|
case 0:
|
|
if (rtc_EN)
|
|
{
|
|
RealTimeClock &= 0xFFFF;
|
|
RealTimeClock |= (data & 0xFFFF) << 16;
|
|
rtc_EN = 0;
|
|
}
|
|
break;
|
|
case 4:
|
|
if (rtc_EN)
|
|
{
|
|
RealTimeClock &= 0xFFFF0000;
|
|
RealTimeClock |= data & 0xFFFF;
|
|
//TODO: Clean the internal timer ?
|
|
}
|
|
break;
|
|
case 8:
|
|
rtc_EN = data & 1;
|
|
break;
|
|
|
|
default:
|
|
WARN_LOG(AICA, "WriteMem_aica_rtc: invalid address %x sz %d data %x", addr, (int)sizeof(T), data);
|
|
break;
|
|
}
|
|
}
|
|
template void WriteMem_aica_rtc<>(u32 addr, u8 data);
|
|
template void WriteMem_aica_rtc<>(u32 addr, u16 data);
|
|
template void WriteMem_aica_rtc<>(u32 addr, u32 data);
|
|
|
|
template<typename T>
|
|
T ReadMem_aica_reg(u32 addr)
|
|
{
|
|
addr &= 0x7FFF;
|
|
if (sizeof(T) == 1)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case 0x2C00:
|
|
return (T)ARMRST;
|
|
case 0x2C01:
|
|
return (T)VREG;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (addr == 0x2C00)
|
|
return (T)((VREG << 8) | ARMRST);
|
|
|
|
if (sizeof(T) == 4)
|
|
return aicaReadReg<u16>(addr);
|
|
else
|
|
return aicaReadReg<T>(addr);
|
|
}
|
|
template u8 ReadMem_aica_reg<>(u32 addr);
|
|
template u16 ReadMem_aica_reg<>(u32 addr);
|
|
template u32 ReadMem_aica_reg<>(u32 addr);
|
|
|
|
static void ArmSetRST()
|
|
{
|
|
ARMRST &= 1;
|
|
aicaarm::enable(ARMRST == 0);
|
|
}
|
|
|
|
template<typename T>
|
|
void WriteMem_aica_reg(u32 addr, T data)
|
|
{
|
|
addr &= 0x7FFF;
|
|
|
|
if (sizeof(T) == 1)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case 0x2C00:
|
|
ARMRST = data;
|
|
INFO_LOG(AICA_ARM, "ARMRST = %02X", ARMRST);
|
|
ArmSetRST();
|
|
return;
|
|
case 0x2C01:
|
|
VREG = data;
|
|
INFO_LOG(AICA_ARM, "VREG = %02X", VREG);
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else if (addr == 0x2C00)
|
|
{
|
|
VREG = (data >> 8) & 0xFF;
|
|
ARMRST = data & 0xFF;
|
|
INFO_LOG(AICA_ARM, "VREG = %02X ARMRST %02X", VREG, ARMRST);
|
|
ArmSetRST();
|
|
return;
|
|
}
|
|
if (sizeof(T) == 4)
|
|
aicaWriteReg(addr, (u16)data);
|
|
else
|
|
aicaWriteReg(addr, data);
|
|
}
|
|
template void WriteMem_aica_reg<>(u32 addr, u8 data);
|
|
template void WriteMem_aica_reg<>(u32 addr, u16 data);
|
|
template void WriteMem_aica_reg<>(u32 addr, u32 data);
|
|
|
|
static int DreamcastSecond(int tag, int c, int j)
|
|
{
|
|
RealTimeClock++;
|
|
|
|
prof_periodical();
|
|
|
|
#if FEAT_SHREC != DYNAREC_NONE
|
|
bm_Periodical_1s();
|
|
#endif
|
|
|
|
return SH4_MAIN_CLOCK;
|
|
}
|
|
|
|
//Init/res/term
|
|
void aica_Init()
|
|
{
|
|
RealTimeClock = GetRTC_now();
|
|
if (rtc_schid == -1)
|
|
rtc_schid = sh4_sched_register(0, &DreamcastSecond);
|
|
}
|
|
|
|
void aica_Reset(bool hard)
|
|
{
|
|
if (hard)
|
|
{
|
|
aica_Init();
|
|
sh4_sched_request(rtc_schid, SH4_MAIN_CLOCK);
|
|
}
|
|
VREG = 0;
|
|
ARMRST = 0;
|
|
}
|
|
|
|
void aica_Term()
|
|
{
|
|
sh4_sched_unregister(rtc_schid);
|
|
rtc_schid = -1;
|
|
}
|
|
|
|
static int dma_end_sched(int tag, int cycl, int jitt)
|
|
{
|
|
u32 len = SB_ADLEN & 0x7FFFFFFF;
|
|
|
|
if (SB_ADLEN & 0x80000000)
|
|
SB_ADEN = 0;
|
|
else
|
|
SB_ADEN = 1;
|
|
|
|
SB_ADSTAR += len;
|
|
SB_ADSTAG += len;
|
|
SB_ADST = 0; // dma done
|
|
SB_ADLEN = 0;
|
|
|
|
// indicate that dma is not happening, or has been paused
|
|
SB_ADSUSP |= 0x10;
|
|
|
|
asic_RaiseInterrupt(holly_SPU_DMA);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static bool check_STAG(u32 addr)
|
|
{
|
|
#ifdef STRICT_MODE
|
|
const u32 area = (addr >> 26) & 7;
|
|
// Aica and G2 Ext dev #1 and #2 on area 0
|
|
// G2 Ext dev #3 on area 5
|
|
return area == 0 || area == 5;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static bool check_STAR(u32 addr)
|
|
{
|
|
#ifdef STRICT_MODE
|
|
const u32 area = (addr >> 26) & 7;
|
|
// System RAM and VRAM
|
|
return area == 3 || area == 1;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
static bool check_STAR_overrun(u32 addr)
|
|
{
|
|
#ifdef STRICT_MODE
|
|
u32 bottom = (((SB_G2APRO >> 8) & 0x7f) << 20) | 0x08000000;
|
|
u32 top = ((SB_G2APRO & 0x7f) << 20) | 0x080fffe0;
|
|
|
|
return addr >= bottom && addr <= top;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
template<u32 ENABLE, u32 START, u32 SRC, u32 DEST, u32 LEN, u32 DIR,
|
|
HollyInterruptID interrupt, HollyInterruptID iainterrupt, HollyInterruptID ovinterrupt,
|
|
const char *LogTag>
|
|
void Write_DmaStart(u32 addr, u32 data)
|
|
{
|
|
u32& enableReg = SB_REGN_32(ENABLE);
|
|
u32& startReg = SB_REGN_32(START);
|
|
u32& sourceReg = SB_REGN_32(SRC);
|
|
u32& destReg = SB_REGN_32(DEST);
|
|
u32& lenReg = SB_REGN_32(LEN);
|
|
const u32 dirReg = SB_REGN_32(DIR);
|
|
|
|
if (!(data & 1) || enableReg == 0)
|
|
return;
|
|
u32 src = sourceReg;
|
|
u32 dst = destReg;
|
|
u32 len = lenReg & 0x7FFFFFFF;
|
|
|
|
// STAR
|
|
if (!check_STAR(src))
|
|
{
|
|
INFO_LOG(AICA, "%s: Invalid src address %08x", LogTag, src);
|
|
startReg = 0;
|
|
enableReg = 0;
|
|
asic_RaiseInterrupt(iainterrupt);
|
|
return;
|
|
}
|
|
// STAG
|
|
if (!check_STAG(dst))
|
|
{
|
|
INFO_LOG(AICA, "%s: Invalid dst address %08x", LogTag, dst);
|
|
startReg = 0;
|
|
enableReg = 0;
|
|
asic_RaiseInterrupt(iainterrupt);
|
|
return;
|
|
}
|
|
// Overrun
|
|
if (!check_STAR_overrun(src) || !check_STAR_overrun(src + len - 1))
|
|
{
|
|
INFO_LOG(AICA, "%s: Overrun address %08x len %x", LogTag, src, len);
|
|
startReg = 0;
|
|
enableReg = 0;
|
|
asic_RaiseInterrupt(ovinterrupt);
|
|
return;
|
|
}
|
|
|
|
if (dirReg == 1)
|
|
std::swap(src, dst);
|
|
DEBUG_LOG(AICA, "%s: DMA Write to %X from %X %d bytes", LogTag, dst, src, len);
|
|
|
|
WriteMemBlock_nommu_dma(dst, src, len);
|
|
|
|
if (lenReg & 0x80000000)
|
|
enableReg = 0;
|
|
else
|
|
enableReg = 1;
|
|
|
|
sourceReg += len;
|
|
destReg += len;
|
|
startReg = 0; // dma done
|
|
lenReg = 0;
|
|
|
|
asic_RaiseInterrupt(interrupt);
|
|
}
|
|
|
|
static void Write_SB_ADST(u32 addr, u32 data)
|
|
{
|
|
//0x005F7800 SB_ADSTAG RW AICA:G2-DMA G2 start address
|
|
//0x005F7804 SB_ADSTAR RW AICA:G2-DMA system memory start address
|
|
//0x005F7808 SB_ADLEN RW AICA:G2-DMA length
|
|
//0x005F780C SB_ADDIR RW AICA:G2-DMA direction
|
|
//0x005F7810 SB_ADTSEL RW AICA:G2-DMA trigger select
|
|
//0x005F7814 SB_ADEN RW AICA:G2-DMA enable
|
|
//0x005F7818 SB_ADST RW AICA:G2-DMA start
|
|
//0x005F781C SB_ADSUSP RW AICA:G2-DMA suspend
|
|
|
|
if ((data & 1) == 1 && (SB_ADST & 1) == 0)
|
|
{
|
|
if (SB_ADEN == 1)
|
|
{
|
|
u32 src = SB_ADSTAR;
|
|
u32 dst = SB_ADSTAG;
|
|
u32 len = SB_ADLEN & 0x7FFFFFFF;
|
|
|
|
if (!check_STAR(src))
|
|
{
|
|
INFO_LOG(AICA, "AICA-DMA : Invalid src address %08x", src);
|
|
SB_ADST = 0;
|
|
SB_ADEN = 0;
|
|
asic_RaiseInterrupt(holly_AICA_ILLADDR);
|
|
return;
|
|
}
|
|
if (!check_STAG(dst))
|
|
{
|
|
INFO_LOG(AICA, "AICA-DMA : Invalid dst address %08x", dst);
|
|
SB_ADST = 0;
|
|
SB_ADEN = 0;
|
|
asic_RaiseInterrupt(holly_AICA_ILLADDR);
|
|
return;
|
|
}
|
|
// Overrun
|
|
if (!check_STAR_overrun(src) || !check_STAR_overrun(src + len - 1))
|
|
{
|
|
INFO_LOG(AICA, "AICA-DMA : Overrun address %08x len %x", src, len);
|
|
SB_ADST = 0;
|
|
SB_ADEN = 0;
|
|
asic_RaiseInterrupt(holly_AICA_OVERRUN);
|
|
return;
|
|
}
|
|
|
|
if (SB_ADDIR == 1)
|
|
{
|
|
//swap direction
|
|
std::swap(src, dst);
|
|
DEBUG_LOG(AICA, "AICA-DMA : SB_ADDIR==1 DMA Read to 0x%X from 0x%X %x bytes", dst, src, SB_ADLEN);
|
|
}
|
|
else
|
|
DEBUG_LOG(AICA, "AICA-DMA : SB_ADDIR==0:DMA Write to 0x%X from 0x%X %x bytes", dst, src, SB_ADLEN);
|
|
|
|
WriteMemBlock_nommu_dma(dst, src, len);
|
|
|
|
// indicate that dma is in progress
|
|
SB_ADST = 1;
|
|
SB_ADSUSP &= ~0x10;
|
|
|
|
// Schedule the end of DMA transfer interrupt
|
|
int cycles = len * (SH4_MAIN_CLOCK / 2 / G2_BUS_CLOCK); // 16 bits @ 25 MHz
|
|
if (cycles < 4096)
|
|
dma_end_sched(0, 0, 0);
|
|
else
|
|
sh4_sched_request(dma_sched_id, cycles);
|
|
}
|
|
}
|
|
}
|
|
|
|
u32 Read_SB_ADST(u32 addr)
|
|
{
|
|
// Le Mans and Looney Tunes sometimes send the same dma transfer twice after checking SB_ADST == 0.
|
|
// To avoid this, we pretend SB_ADST is still set when there is a pending aica-dma interrupt.
|
|
// This is only done once.
|
|
if ((SB_ISTNRM & (1 << (u8)holly_SPU_DMA)) && !(SB_ADST & 2))
|
|
{
|
|
SB_ADST |= 2;
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
SB_ADST &= ~2;
|
|
return SB_ADST;
|
|
}
|
|
}
|
|
|
|
template<u32 STAG, HollyInterruptID iainterrupt, const char *LogTag>
|
|
void Write_SB_STAG(u32 addr, u32 data)
|
|
{
|
|
u32& stagReg = sb_regs[(STAG - SB_BASE) / 4].data32;
|
|
stagReg = data & 0x1FFFFFE0;
|
|
|
|
if (!check_STAG(data))
|
|
{
|
|
INFO_LOG(AICA, "%s Write_SB_STAG: Invalid address %08x", LogTag, data);
|
|
asic_RaiseInterrupt(iainterrupt);
|
|
}
|
|
}
|
|
|
|
template<u32 STAR, HollyInterruptID iainterrupt, const char *LogTag>
|
|
void Write_SB_STAR(u32 addr, u32 data)
|
|
{
|
|
u32& starReg = sb_regs[(STAR - SB_BASE) / 4].data32;
|
|
starReg = data & 0x1FFFFFE0;
|
|
|
|
if (!check_STAR(data))
|
|
{
|
|
INFO_LOG(AICA, "%s Write_SB_STAR: Invalid address %08x", LogTag, data);
|
|
asic_RaiseInterrupt(iainterrupt);
|
|
}
|
|
}
|
|
|
|
void Write_SB_G2APRO(u32 addr, u32 data)
|
|
{
|
|
if ((data >> 16) == 0x4659)
|
|
SB_G2APRO = data & 0x00007f7f;
|
|
}
|
|
|
|
extern const char AICA_TAG[] = "G2-AICA DMA";
|
|
extern const char EXT1_TAG[] = "G2-EXT1 DMA";
|
|
extern const char EXT2_TAG[] = "G2-EXT2 DMA";
|
|
extern const char DDEV_TAG[] = "G2-DDev DMA";
|
|
|
|
void aica_sb_Init()
|
|
{
|
|
// G2-DMA registers
|
|
|
|
// AICA
|
|
sb_rio_register(SB_ADST_addr, RIO_FUNC, &Read_SB_ADST, &Write_SB_ADST);
|
|
#ifdef STRICT_MODE
|
|
sb_rio_register(SB_ADSTAR_addr, RIO_WF, nullptr, &Write_SB_STAR<SB_ADSTAR_addr, holly_AICA_ILLADDR, AICA_TAG>);
|
|
sb_rio_register(SB_ADSTAG_addr, RIO_WF, nullptr, &Write_SB_STAG<SB_ADSTAG_addr, holly_AICA_ILLADDR, AICA_TAG>);
|
|
#endif
|
|
|
|
// G2 Ext device #1
|
|
sb_rio_register(SB_E1ST_addr, RIO_WF, nullptr, &Write_DmaStart<SB_E1EN_addr, SB_E1ST_addr, SB_E1STAR_addr, SB_E1STAG_addr, SB_E1LEN_addr,
|
|
SB_E1DIR_addr, holly_EXT_DMA1, holly_EXT1_ILLADDR, holly_EXT1_OVERRUN, EXT1_TAG>);
|
|
sb_rio_register(SB_E1STAR_addr, RIO_WF, nullptr, &Write_SB_STAR<SB_E1STAR_addr, holly_EXT1_ILLADDR, EXT1_TAG>);
|
|
sb_rio_register(SB_E1STAG_addr, RIO_WF, nullptr, &Write_SB_STAG<SB_E1STAG_addr, holly_EXT1_ILLADDR, EXT1_TAG>);
|
|
|
|
// G2 Ext device #2
|
|
sb_rio_register(SB_E2ST_addr, RIO_WF, nullptr, &Write_DmaStart<SB_E2EN_addr, SB_E2ST_addr, SB_E2STAR_addr, SB_E2STAG_addr, SB_E2LEN_addr,
|
|
SB_E2DIR_addr, holly_EXT_DMA2, holly_EXT2_ILLADDR, holly_EXT2_OVERRUN, EXT2_TAG>);
|
|
sb_rio_register(SB_E2STAR_addr, RIO_WF, nullptr, &Write_SB_STAR<SB_E2STAR_addr, holly_EXT2_ILLADDR, EXT2_TAG>);
|
|
sb_rio_register(SB_E2STAG_addr, RIO_WF, nullptr, &Write_SB_STAG<SB_E2STAG_addr, holly_EXT2_ILLADDR, EXT2_TAG>);
|
|
|
|
// G2 Ext device #3
|
|
sb_rio_register(SB_DDST_addr, RIO_WF, nullptr, &Write_DmaStart<SB_DDEN_addr, SB_DDST_addr, SB_DDSTAR_addr, SB_DDSTAG_addr, SB_DDLEN_addr,
|
|
SB_DDDIR_addr, holly_DEV_DMA, holly_DEV_ILLADDR, holly_DEV_OVERRUN, DDEV_TAG>);
|
|
sb_rio_register(SB_DDSTAR_addr, RIO_WF, nullptr, &Write_SB_STAR<SB_DDSTAR_addr, holly_DEV_ILLADDR, DDEV_TAG>);
|
|
sb_rio_register(SB_DDSTAG_addr, RIO_WF, nullptr, &Write_SB_STAG<SB_DDSTAG_addr, holly_DEV_ILLADDR, DDEV_TAG>);
|
|
|
|
sb_rio_register(SB_G2APRO_addr, RIO_WO_FUNC, nullptr, &Write_SB_G2APRO);
|
|
|
|
dma_sched_id = sh4_sched_register(0, &dma_end_sched);
|
|
}
|
|
|
|
void aica_sb_Reset(bool hard)
|
|
{
|
|
if (hard) {
|
|
SB_ADST = 0;
|
|
SB_G2APRO = 0x7f00;
|
|
}
|
|
}
|
|
|
|
void aica_sb_Term()
|
|
{
|
|
sh4_sched_unregister(dma_sched_id);
|
|
dma_sched_id = -1;
|
|
}
|