DSP: Fix write masks on AUDIO_*/AR_* MMIO registers
https://bugs.dolphin-emu.org/issues/6749 This change fixes the scratchy audio in Teenage Mutant Ninja Turtles (SX7E52/SX7P52). The game starts an audio interface DMA with an unaligned address, and because Dolphin was not masking off the low 5 bits of AUDIO_DMA_START_LO, all future AI DMAs were misaligned. To understand why, it is instructive to refer to AUDIO_InitDMA() in libogc, which behaves the same as the official SDK: _dspReg[25] = (_dspReg[25]&~0xffe0)|(startaddr&0xffff); The implementation does not mask off the low bits of the passed in value before it ORs them with low bits of the current register value. Therefore, if they are not masked off by the hardware itself, they become permanently stuck once set. Adding a write mask for AUDIO_DMA_START_LO is enough to fix the bug in TMNT, but I decided to run some tests on GC and Wii to find the correct write masks for the surrounding registers, as only a couple were already being masked. Dolphin has gotten away with not masking the rest because many are already A) masked on read (or never read) by the SDK and/or B) masked on use (or never used) in Dolphin. This leaves just three registers where the difference may be observable: AR_DMA_CNT_H and AUDIO_DMA_START_HI/LO.
This commit is contained in:
parent
1d489b3fd5
commit
c2da12ca75
|
@ -231,30 +231,51 @@ void Shutdown()
|
|||
|
||||
void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
{
|
||||
static constexpr u16 WMASK_NONE = 0x0000;
|
||||
static constexpr u16 WMASK_AR_INFO = 0x007f;
|
||||
static constexpr u16 WMASK_AR_REFRESH = 0x07ff;
|
||||
static constexpr u16 WMASK_AR_HI_RESTRICT = 0x03ff;
|
||||
static constexpr u16 WMASK_AR_CNT_DIR_BIT = 0x8000;
|
||||
static constexpr u16 WMASK_AUDIO_HI_RESTRICT_GCN = 0x03ff;
|
||||
static constexpr u16 WMASK_AUDIO_HI_RESTRICT_WII = 0x1fff;
|
||||
static constexpr u16 WMASK_LO_ALIGN_32BIT = 0xffe0;
|
||||
|
||||
// Declare all the boilerplate direct MMIOs.
|
||||
struct
|
||||
{
|
||||
u32 addr;
|
||||
u16* ptr;
|
||||
bool align_writes_on_32_bytes;
|
||||
u16 wmask;
|
||||
} directly_mapped_vars[] = {
|
||||
{AR_INFO, &s_ARAM_Info.Hex},
|
||||
{AR_MODE, &s_AR_MODE},
|
||||
{AR_REFRESH, &s_AR_REFRESH},
|
||||
{AR_DMA_MMADDR_H, MMIO::Utils::HighPart(&s_arDMA.MMAddr)},
|
||||
{AR_DMA_MMADDR_L, MMIO::Utils::LowPart(&s_arDMA.MMAddr), true},
|
||||
{AR_DMA_ARADDR_H, MMIO::Utils::HighPart(&s_arDMA.ARAddr)},
|
||||
{AR_DMA_ARADDR_L, MMIO::Utils::LowPart(&s_arDMA.ARAddr), true},
|
||||
{AR_DMA_CNT_H, MMIO::Utils::HighPart(&s_arDMA.Cnt.Hex)},
|
||||
// This register is read-only
|
||||
{AR_MODE, &s_AR_MODE, WMASK_NONE},
|
||||
|
||||
// For these registers, only some bits can be set
|
||||
{AR_INFO, &s_ARAM_Info.Hex, WMASK_AR_INFO},
|
||||
{AR_REFRESH, &s_AR_REFRESH, WMASK_AR_REFRESH},
|
||||
|
||||
// For AR_DMA_*_H registers, only bits 0x03ff can be set
|
||||
// For AR_DMA_*_L registers, only bits 0xffe0 can be set
|
||||
{AR_DMA_MMADDR_H, MMIO::Utils::HighPart(&s_arDMA.MMAddr), WMASK_AR_HI_RESTRICT},
|
||||
{AR_DMA_MMADDR_L, MMIO::Utils::LowPart(&s_arDMA.MMAddr), WMASK_LO_ALIGN_32BIT},
|
||||
{AR_DMA_ARADDR_H, MMIO::Utils::HighPart(&s_arDMA.ARAddr), WMASK_AR_HI_RESTRICT},
|
||||
{AR_DMA_ARADDR_L, MMIO::Utils::LowPart(&s_arDMA.ARAddr), WMASK_LO_ALIGN_32BIT},
|
||||
// For this register, the topmost (dir) bit can also be set
|
||||
{AR_DMA_CNT_H, MMIO::Utils::HighPart(&s_arDMA.Cnt.Hex),
|
||||
WMASK_AR_HI_RESTRICT | WMASK_AR_CNT_DIR_BIT},
|
||||
// AR_DMA_CNT_L triggers DMA
|
||||
{AUDIO_DMA_START_HI, MMIO::Utils::HighPart(&s_audioDMA.SourceAddress)},
|
||||
{AUDIO_DMA_START_LO, MMIO::Utils::LowPart(&s_audioDMA.SourceAddress)},
|
||||
|
||||
// For AUDIO_DMA_START_HI, only bits 0x03ff can be set on GCN and 0x1fff on Wii
|
||||
// For AUDIO_DMA_START_LO, only bits 0xffe0 can be set
|
||||
// AUDIO_DMA_START_HI requires a complex write handler
|
||||
{AUDIO_DMA_START_LO, MMIO::Utils::LowPart(&s_audioDMA.SourceAddress), WMASK_LO_ALIGN_32BIT},
|
||||
};
|
||||
for (auto& mapped_var : directly_mapped_vars)
|
||||
{
|
||||
u16 write_mask = mapped_var.align_writes_on_32_bytes ? 0xFFE0 : 0xFFFF;
|
||||
mmio->Register(base | mapped_var.addr, MMIO::DirectRead<u16>(mapped_var.ptr),
|
||||
MMIO::DirectWrite<u16>(mapped_var.ptr, write_mask));
|
||||
mapped_var.wmask != WMASK_NONE ?
|
||||
MMIO::DirectWrite<u16>(mapped_var.ptr, mapped_var.wmask) :
|
||||
MMIO::InvalidWrite<u16>());
|
||||
}
|
||||
|
||||
// DSP mail MMIOs call DSP emulator functions to get results or write data.
|
||||
|
@ -340,10 +361,18 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
// ARAM MMIO controlling the DMA start.
|
||||
mmio->Register(base | AR_DMA_CNT_L, MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&s_arDMA.Cnt.Hex)),
|
||||
MMIO::ComplexWrite<u16>([](u32, u16 val) {
|
||||
s_arDMA.Cnt.Hex = (s_arDMA.Cnt.Hex & 0xFFFF0000) | (val & ~31);
|
||||
s_arDMA.Cnt.Hex = (s_arDMA.Cnt.Hex & 0xFFFF0000) | (val & WMASK_LO_ALIGN_32BIT);
|
||||
Do_ARAM_DMA();
|
||||
}));
|
||||
|
||||
mmio->Register(base | AUDIO_DMA_START_HI,
|
||||
MMIO::DirectRead<u16>(MMIO::Utils::HighPart(&s_audioDMA.SourceAddress)),
|
||||
MMIO::ComplexWrite<u16>([](u32, u16 val) {
|
||||
*MMIO::Utils::HighPart(&s_audioDMA.SourceAddress) =
|
||||
val & (SConfig::GetInstance().bWii ? WMASK_AUDIO_HI_RESTRICT_WII :
|
||||
WMASK_AUDIO_HI_RESTRICT_GCN);
|
||||
}));
|
||||
|
||||
// Audio DMA MMIO controlling the DMA start.
|
||||
mmio->Register(
|
||||
base | AUDIO_DMA_CONTROL_LEN, MMIO::DirectRead<u16>(&s_audioDMA.AudioDMAControl.Hex),
|
||||
|
|
Loading…
Reference in New Issue