Partial implementation of the I2S
Only covers the DSi mic (with ARM7 interface) NDMA mode 0x0C is not implemented SPU / DSP sampling should be done here, but this is not implemented (would need some discussion since it deeply affects how the frontend handles audio, also especially since the sample rate can change here) Some things are a complete guess (only some things have been hardware tested)
This commit is contained in:
parent
1b8daa0465
commit
44e6dec81e
|
@ -21,6 +21,7 @@ add_library(core STATIC
|
||||||
DSi_Camera.cpp
|
DSi_Camera.cpp
|
||||||
DSi_DSP.cpp
|
DSi_DSP.cpp
|
||||||
DSi_I2C.cpp
|
DSi_I2C.cpp
|
||||||
|
DSi_I2S.cpp
|
||||||
DSi_NAND.cpp
|
DSi_NAND.cpp
|
||||||
DSi_NDMA.cpp
|
DSi_NDMA.cpp
|
||||||
DSi_NWifi.cpp
|
DSi_NWifi.cpp
|
||||||
|
|
77
src/DSi.cpp
77
src/DSi.cpp
|
@ -35,6 +35,7 @@
|
||||||
|
|
||||||
#include "DSi_NDMA.h"
|
#include "DSi_NDMA.h"
|
||||||
#include "DSi_I2C.h"
|
#include "DSi_I2C.h"
|
||||||
|
#include "DSi_I2S.h"
|
||||||
#include "DSi_SD.h"
|
#include "DSi_SD.h"
|
||||||
#include "DSi_AES.h"
|
#include "DSi_AES.h"
|
||||||
#include "DSi_NAND.h"
|
#include "DSi_NAND.h"
|
||||||
|
@ -108,6 +109,7 @@ DSi::DSi(DSiArgs&& args, void* userdata) noexcept :
|
||||||
SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)),
|
SDMMC(*this, std::move(args.NANDImage), std::move(args.DSiSDCard)),
|
||||||
SDIO(*this),
|
SDIO(*this),
|
||||||
I2C(*this),
|
I2C(*this),
|
||||||
|
I2S(*this),
|
||||||
CamModule(*this),
|
CamModule(*this),
|
||||||
AES(*this)
|
AES(*this)
|
||||||
{
|
{
|
||||||
|
@ -141,6 +143,7 @@ void DSi::Reset()
|
||||||
for (int i = 0; i < 8; i++) NDMAs[i].Reset();
|
for (int i = 0; i < 8; i++) NDMAs[i].Reset();
|
||||||
|
|
||||||
I2C.Reset();
|
I2C.Reset();
|
||||||
|
I2S.Reset();
|
||||||
CamModule.Reset();
|
CamModule.Reset();
|
||||||
DSP.Reset();
|
DSP.Reset();
|
||||||
|
|
||||||
|
@ -210,6 +213,13 @@ void DSi::CamInputFrame(int cam, const u32* data, int width, int height, bool rg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DSi::MicInputFrame(s16* data, int samples)
|
||||||
|
{
|
||||||
|
SPI.GetTSC()->MicInputFrame(data, samples);
|
||||||
|
I2S.MicInputFrame(data, samples);
|
||||||
|
// TODO: Need to send the mic samples to the DSP!
|
||||||
|
}
|
||||||
|
|
||||||
void DSi::DoSavestateExtra(Savestate* file)
|
void DSi::DoSavestateExtra(Savestate* file)
|
||||||
{
|
{
|
||||||
file->Section("DSIG");
|
file->Section("DSIG");
|
||||||
|
@ -285,6 +295,7 @@ void DSi::DoSavestateExtra(Savestate* file)
|
||||||
CamModule.DoSavestate(file);
|
CamModule.DoSavestate(file);
|
||||||
DSP.DoSavestate(file);
|
DSP.DoSavestate(file);
|
||||||
I2C.DoSavestate(file);
|
I2C.DoSavestate(file);
|
||||||
|
I2S.DoSavestate(file);
|
||||||
SDMMC.DoSavestate(file);
|
SDMMC.DoSavestate(file);
|
||||||
SDIO.DoSavestate(file);
|
SDIO.DoSavestate(file);
|
||||||
}
|
}
|
||||||
|
@ -644,6 +655,8 @@ void DSi::SetupDirectBoot()
|
||||||
|
|
||||||
SPI.GetFirmwareMem()->SetupDirectBoot();
|
SPI.GetFirmwareMem()->SetupDirectBoot();
|
||||||
|
|
||||||
|
I2S.WriteSndExCnt(0x8008, 0xFFFF);
|
||||||
|
|
||||||
ARM9.CP15Write(0x100, 0x00056078);
|
ARM9.CP15Write(0x100, 0x00056078);
|
||||||
ARM9.CP15Write(0x200, 0x0000004A);
|
ARM9.CP15Write(0x200, 0x0000004A);
|
||||||
ARM9.CP15Write(0x201, 0x0000004A);
|
ARM9.CP15Write(0x201, 0x0000004A);
|
||||||
|
@ -690,6 +703,9 @@ void DSi::SoftReset()
|
||||||
|
|
||||||
NDS::MapSharedWRAM(3);
|
NDS::MapSharedWRAM(3);
|
||||||
|
|
||||||
|
// TODO: is this actually reset?
|
||||||
|
I2S.Reset();
|
||||||
|
|
||||||
// TODO: does the DSP get reset? NWRAM doesn't, so I'm assuming no
|
// TODO: does the DSP get reset? NWRAM doesn't, so I'm assuming no
|
||||||
// *HOWEVER*, the bootrom (which does get rerun) does remap NWRAM, and thus
|
// *HOWEVER*, the bootrom (which does get rerun) does remap NWRAM, and thus
|
||||||
// the DSP most likely gets reset
|
// the DSP most likely gets reset
|
||||||
|
@ -2707,8 +2723,16 @@ u8 DSi::ARM7IORead8(u32 addr)
|
||||||
case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 56;
|
case 0x04004D07: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 56;
|
||||||
case 0x04004D08: return 0;
|
case 0x04004D08: return 0;
|
||||||
|
|
||||||
case 0x4004700: return DSP.ReadSNDExCnt() & 0xFF;
|
case 0x4004600: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicCnt() & 0xFF;
|
||||||
case 0x4004701: return DSP.ReadSNDExCnt() >> 8;
|
case 0x4004601: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicCnt() >> 8;
|
||||||
|
case 0x4004602: return 0;
|
||||||
|
case 0x4004603: return 0;
|
||||||
|
case 0x4004604: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicData() & 0xFF;
|
||||||
|
case 0x4004605: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return (I2S.ReadMicData() >> 8) & 0xFF;
|
||||||
|
case 0x4004606: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return (I2S.ReadMicData() >> 16) & 0xFF;
|
||||||
|
case 0x4004607: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicData() >> 24;
|
||||||
|
case 0x4004700: if (!(SCFG_EXT[1] & (1 << 21))) return 0; return I2S.ReadSndExCnt() & 0xFF;
|
||||||
|
case 0x4004701: if (!(SCFG_EXT[1] & (1 << 21))) return 0; return I2S.ReadSndExCnt() >> 8;
|
||||||
|
|
||||||
case 0x04004C00: return GPIO_Data;
|
case 0x04004C00: return GPIO_Data;
|
||||||
case 0x04004C01: return GPIO_Dir;
|
case 0x04004C01: return GPIO_Dir;
|
||||||
|
@ -2751,7 +2775,11 @@ u16 DSi::ARM7IORead16(u32 addr)
|
||||||
case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 48;
|
case 0x04004D06: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 48;
|
||||||
case 0x04004D08: return 0;
|
case 0x04004D08: return 0;
|
||||||
|
|
||||||
case 0x4004700: return DSP.ReadSNDExCnt();
|
case 0x4004600: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicCnt();
|
||||||
|
case 0x4004602: return 0;
|
||||||
|
case 0x4004604: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicData() >> 16;
|
||||||
|
case 0x4004606: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicData() & 0xFFFF;
|
||||||
|
case 0x4004700: if (!(SCFG_EXT[1] & (1 << 21))) return 0; return I2S.ReadSndExCnt();
|
||||||
|
|
||||||
case 0x04004C00: return GPIO_Data | ((u16)GPIO_Dir << 8);
|
case 0x04004C00: return GPIO_Data | ((u16)GPIO_Dir << 8);
|
||||||
case 0x04004C02: return GPIO_IEdgeSel | ((u16)GPIO_IE << 8);
|
case 0x04004C02: return GPIO_IEdgeSel | ((u16)GPIO_IE << 8);
|
||||||
|
@ -2829,9 +2857,9 @@ u32 DSi::ARM7IORead32(u32 addr)
|
||||||
case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 32;
|
case 0x04004D04: if (SCFG_BIOS & (1<<10)) return 0; return SDMMC.GetNAND()->GetConsoleID() >> 32;
|
||||||
case 0x04004D08: return 0;
|
case 0x04004D08: return 0;
|
||||||
|
|
||||||
case 0x4004700:
|
case 0x4004600: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicCnt();
|
||||||
Log(LogLevel::Debug, "32-Bit SNDExCnt read? %08X\n", ARM7.R[15]);
|
case 0x4004604: if (!(SCFG_EXT[1] & (1 << 20))) return 0; return I2S.ReadMicData();
|
||||||
return DSP.ReadSNDExCnt();
|
case 0x4004700: if (!(SCFG_EXT[1] & (1 << 21))) return 0; return I2S.ReadSndExCnt();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addr >= 0x04004800 && addr < 0x04004A00)
|
if (addr >= 0x04004800 && addr < 0x04004A00)
|
||||||
|
@ -2884,11 +2912,25 @@ void DSi::ARM7IOWrite8(u32 addr, u8 val)
|
||||||
case 0x04004500: I2C.WriteData(val); return;
|
case 0x04004500: I2C.WriteData(val); return;
|
||||||
case 0x04004501: I2C.WriteCnt(val); return;
|
case 0x04004501: I2C.WriteCnt(val); return;
|
||||||
|
|
||||||
|
case 0x4004600:
|
||||||
|
if (!(SCFG_EXT[1] & (1 << 20)))
|
||||||
|
return;
|
||||||
|
I2S.WriteMicCnt((u16)val, 0xFF);
|
||||||
|
return;
|
||||||
|
case 0x4004601:
|
||||||
|
if (!(SCFG_EXT[1] & (1 << 20)))
|
||||||
|
return;
|
||||||
|
I2S.WriteMicCnt(((u16)val << 8), 0xFF00);
|
||||||
|
return;
|
||||||
case 0x4004700:
|
case 0x4004700:
|
||||||
DSP.WriteSNDExCnt((u16)val, 0xFF);
|
if (!(SCFG_EXT[1] & (1 << 21)))
|
||||||
|
return;
|
||||||
|
I2S.WriteSndExCnt((u16)val, 0xFF);
|
||||||
return;
|
return;
|
||||||
case 0x4004701:
|
case 0x4004701:
|
||||||
DSP.WriteSNDExCnt(((u16)val << 8), 0xFF00);
|
if (!(SCFG_EXT[1] & (1 << 21)))
|
||||||
|
return;
|
||||||
|
I2S.WriteSndExCnt(((u16)val << 8), 0xFF00);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0x04004C00:
|
case 0x04004C00:
|
||||||
|
@ -2987,8 +3029,15 @@ void DSi::ARM7IOWrite16(u32 addr, u16 val)
|
||||||
AES.WriteBlkCnt(val<<16);
|
AES.WriteBlkCnt(val<<16);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case 0x4004600:
|
||||||
|
if (!(SCFG_EXT[1] & (1 << 20)))
|
||||||
|
return;
|
||||||
|
I2S.WriteMicCnt(val, 0xFFFF);
|
||||||
|
return;
|
||||||
case 0x4004700:
|
case 0x4004700:
|
||||||
DSP.WriteSNDExCnt(val, 0xFFFF);
|
if (!(SCFG_EXT[1] & (1 << 21)))
|
||||||
|
return;
|
||||||
|
I2S.WriteSndExCnt(val, 0xFFFF);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 0x04004C00:
|
case 0x04004C00:
|
||||||
|
@ -3136,9 +3185,15 @@ void DSi::ARM7IOWrite32(u32 addr, u32 val)
|
||||||
case 0x04004404: AES.WriteBlkCnt(val); return;
|
case 0x04004404: AES.WriteBlkCnt(val); return;
|
||||||
case 0x04004408: AES.WriteInputFIFO(val); return;
|
case 0x04004408: AES.WriteInputFIFO(val); return;
|
||||||
|
|
||||||
|
case 0x4004600:
|
||||||
|
if (!(SCFG_EXT[1] & (1 << 20)))
|
||||||
|
return;
|
||||||
|
I2S.WriteMicCnt(val, 0xFFFF);
|
||||||
|
return;
|
||||||
case 0x4004700:
|
case 0x4004700:
|
||||||
Log(LogLevel::Debug, "32-Bit SNDExCnt write? %08X %08X\n", val, ARM7.R[15]);
|
if (!(SCFG_EXT[1] & (1 << 21)))
|
||||||
DSP.WriteSNDExCnt(val, 0xFFFF);
|
return;
|
||||||
|
I2S.WriteSndExCnt(val, 0xFFFF);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
#include "NDS.h"
|
#include "NDS.h"
|
||||||
#include "DSi_NDMA.h"
|
#include "DSi_NDMA.h"
|
||||||
|
#include "DSi_I2S.h"
|
||||||
#include "DSi_SD.h"
|
#include "DSi_SD.h"
|
||||||
#include "DSi_DSP.h"
|
#include "DSi_DSP.h"
|
||||||
#include "DSi_AES.h"
|
#include "DSi_AES.h"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
namespace melonDS
|
namespace melonDS
|
||||||
{
|
{
|
||||||
class DSi_I2CHost;
|
class DSi_I2CHost;
|
||||||
|
class DSi_I2S;
|
||||||
class DSi_CamModule;
|
class DSi_CamModule;
|
||||||
class DSi_AES;
|
class DSi_AES;
|
||||||
class DSi_DSP;
|
class DSi_DSP;
|
||||||
|
@ -69,6 +71,7 @@ public:
|
||||||
u32 NWRAMMask[2][3];
|
u32 NWRAMMask[2][3];
|
||||||
|
|
||||||
DSi_I2CHost I2C;
|
DSi_I2CHost I2C;
|
||||||
|
DSi_I2S I2S;
|
||||||
DSi_CamModule CamModule;
|
DSi_CamModule CamModule;
|
||||||
DSi_AES AES;
|
DSi_AES AES;
|
||||||
DSi_DSP DSP;
|
DSi_DSP DSP;
|
||||||
|
@ -155,6 +158,7 @@ public:
|
||||||
void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); }
|
void SetSDCard(std::optional<FATStorage>&& sdcard) noexcept { SDMMC.SetSDCard(std::move(sdcard)); }
|
||||||
|
|
||||||
void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) override;
|
void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) override;
|
||||||
|
void MicInputFrame(s16* data, int samples) override;
|
||||||
bool DMAsInMode(u32 cpu, u32 mode) const override;
|
bool DMAsInMode(u32 cpu, u32 mode) const override;
|
||||||
bool DMAsRunning(u32 cpu) const override;
|
bool DMAsRunning(u32 cpu) const override;
|
||||||
void StopDMAs(u32 cpu, u32 mode) override;
|
void StopDMAs(u32 cpu, u32 mode) override;
|
||||||
|
|
|
@ -178,8 +178,6 @@ void DSi_DSP::Reset()
|
||||||
TeakraCore->Reset();
|
TeakraCore->Reset();
|
||||||
|
|
||||||
DSi.CancelEvent(Event_DSi_DSP);
|
DSi.CancelEvent(Event_DSi_DSP);
|
||||||
|
|
||||||
SNDExCnt = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DSi_DSP::IsRstReleased() const
|
bool DSi_DSP::IsRstReleased() const
|
||||||
|
@ -536,23 +534,6 @@ void DSi_DSP::Write32(u32 addr, u32 val)
|
||||||
Write16(addr, val & 0xFFFF);
|
Write16(addr, val & 0xFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSi_DSP::WriteSNDExCnt(u16 val, u16 mask)
|
|
||||||
{
|
|
||||||
val = (val & mask) | (SNDExCnt & ~mask);
|
|
||||||
|
|
||||||
// it can be written even in NDS mode
|
|
||||||
|
|
||||||
// mic frequency can only be changed if it was disabled
|
|
||||||
// before the write
|
|
||||||
if (SNDExCnt & 0x8000)
|
|
||||||
{
|
|
||||||
val &= ~0x2000;
|
|
||||||
val |= SNDExCnt & 0x2000;
|
|
||||||
}
|
|
||||||
|
|
||||||
SNDExCnt = val & 0xE00F;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DSi_DSP::Run(u32 cycles)
|
void DSi_DSP::Run(u32 cycles)
|
||||||
{
|
{
|
||||||
if (!IsDSPCoreEnabled())
|
if (!IsDSPCoreEnabled())
|
||||||
|
|
|
@ -54,9 +54,6 @@ public:
|
||||||
u32 Read32(u32 addr);
|
u32 Read32(u32 addr);
|
||||||
void Write32(u32 addr, u32 val);
|
void Write32(u32 addr, u32 val);
|
||||||
|
|
||||||
u16 ReadSNDExCnt() const { return SNDExCnt; }
|
|
||||||
void WriteSNDExCnt(u16 val, u16 mask);
|
|
||||||
|
|
||||||
// NOTE: checks SCFG_CLK9
|
// NOTE: checks SCFG_CLK9
|
||||||
void Run(u32 cycles);
|
void Run(u32 cycles);
|
||||||
|
|
||||||
|
@ -70,8 +67,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
melonDS::DSi& DSi;
|
melonDS::DSi& DSi;
|
||||||
// not sure whether to not rather put it somewhere else
|
|
||||||
u16 SNDExCnt;
|
|
||||||
|
|
||||||
Teakra::Teakra* TeakraCore;
|
Teakra::Teakra* TeakraCore;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS 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 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "DSi.h"
|
||||||
|
#include "DSi_I2S.h"
|
||||||
|
#include "Platform.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
using Platform::Log;
|
||||||
|
using Platform::LogLevel;
|
||||||
|
|
||||||
|
|
||||||
|
DSi_I2S::DSi_I2S(melonDS::DSi& dsi) : DSi(dsi)
|
||||||
|
{
|
||||||
|
DSi.RegisterEventFunc(Event_DSi_I2S, 0, MemberEventFunc(DSi_I2S, Clock));
|
||||||
|
|
||||||
|
MicCnt = 0;
|
||||||
|
SndExCnt = 0;
|
||||||
|
MicClockDivider = 0;
|
||||||
|
|
||||||
|
MicBufferLen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DSi_I2S::~DSi_I2S()
|
||||||
|
{
|
||||||
|
DSi.UnregisterEventFunc(Event_DSi_I2S, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::Reset()
|
||||||
|
{
|
||||||
|
MicCnt = 0;
|
||||||
|
SndExCnt = 0;
|
||||||
|
MicClockDivider = 0;
|
||||||
|
|
||||||
|
MicFifo.Clear();
|
||||||
|
|
||||||
|
MicBufferLen = 0;
|
||||||
|
|
||||||
|
DSi.ScheduleEvent(Event_DSi_I2S, false, 1024, 0, I2S_Freq_32728Hz);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::DoSavestate(Savestate* file)
|
||||||
|
{
|
||||||
|
file->Section("I2Si");
|
||||||
|
|
||||||
|
file->Var16(&MicCnt);
|
||||||
|
file->Var16(&SndExCnt);
|
||||||
|
file->Var8(&MicClockDivider);
|
||||||
|
|
||||||
|
MicFifo.DoSavestate(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::MicInputFrame(const s16* data, int samples)
|
||||||
|
{
|
||||||
|
if (!data)
|
||||||
|
{
|
||||||
|
MicBufferLen = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samples > 1024) samples = 1024;
|
||||||
|
memcpy(MicBuffer, data, samples * sizeof(s16));
|
||||||
|
MicBufferLen = samples;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 DSi_I2S::ReadMicCnt()
|
||||||
|
{
|
||||||
|
u16 ret = MicCnt;
|
||||||
|
if (MicFifo.Level() == 0) ret |= 1 << 8;
|
||||||
|
if (MicFifo.Level() >= 16) ret |= 1 << 9;
|
||||||
|
if (MicFifo.Level() >= 32) ret |= 1 << 10;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::WriteMicCnt(u16 val, u16 mask)
|
||||||
|
{
|
||||||
|
val = (val & mask) | (MicCnt & ~mask);
|
||||||
|
|
||||||
|
// FIFO clear can only happen if the mic was disabled before the write
|
||||||
|
if (!(MicCnt & (1 << 15)) && (val & (1 << 12)))
|
||||||
|
{
|
||||||
|
MicCnt &= ~(1 << 11);
|
||||||
|
MicFifo.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
MicCnt = (val & 0xE00F) | (MicCnt & (1 << 11));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 DSi_I2S::ReadMicData()
|
||||||
|
{
|
||||||
|
// CHECKME: This is a complete guess on how mic data reads work
|
||||||
|
// gbatek states the FIFO is 16 words large, with 1 word having 2 samples
|
||||||
|
u32 ret = MicFifo.IsEmpty() ? 0 : (u16)MicFifo.Read();
|
||||||
|
ret |= (MicFifo.IsEmpty() ? 0 : (u16)MicFifo.Read()) << 16;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 DSi_I2S::ReadSndExCnt()
|
||||||
|
{
|
||||||
|
return SndExCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::WriteSndExCnt(u16 val, u16 mask)
|
||||||
|
{
|
||||||
|
val = (val & mask) | (SndExCnt & ~mask);
|
||||||
|
|
||||||
|
// Note: SNDEXCNT can be accessed in "NDS mode"
|
||||||
|
// This is due to the corresponding SCFG_EXT flag not being disabled
|
||||||
|
// If it is disabled (with homebrew), SNDEXCNT cannot be accessed
|
||||||
|
// This is more purely a software mistake on the DSi menu's part
|
||||||
|
|
||||||
|
// I2S frequency can only be changed if it was disabled before the write
|
||||||
|
if (SndExCnt & (1 << 15))
|
||||||
|
{
|
||||||
|
val &= ~(1 << 13);
|
||||||
|
val |= SndExCnt & (1 << 13);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((SndExCnt ^ val) & (1 << 13))
|
||||||
|
{
|
||||||
|
Log(LogLevel::Debug, "Changed I2S frequency to %dHz\n", (SndExCnt & (1 << 13)) ? 47605 : 32728);
|
||||||
|
}
|
||||||
|
|
||||||
|
SndExCnt = val & 0xE00F;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DSi_I2S::Clock(u32 freq)
|
||||||
|
{
|
||||||
|
if (SndExCnt & (1 << 15))
|
||||||
|
{
|
||||||
|
// CHECKME (from gbatek)
|
||||||
|
// "The Sampling Rate becomes zero (no data arriving) when SNDEXCNT.Bit15=0, or when MIC_CNT.bit0-1=3, or when MIC_CNT.bit15=0, or when Overrun has occurred."
|
||||||
|
// This likely means on any of these conditions the mic completely ignores any I2S clocks
|
||||||
|
// Although perhaps it might still acknowledge the clocks in some capacity (maybe affecting below clock division)
|
||||||
|
if ((MicCnt & (1 << 15)) && !(MicCnt & (1 << 11)) && (MicCnt & 3) != 3)
|
||||||
|
{
|
||||||
|
// CHECKME (from gbatek)
|
||||||
|
// "2-3 Sampling Rate (0..3=F/1, F/2, F/3, F/4)"
|
||||||
|
// This likely works with an internal counter compared with the sampling rate
|
||||||
|
// This counter is then likely reset on mic sample
|
||||||
|
// But this is completely untested
|
||||||
|
|
||||||
|
MicClockDivider++;
|
||||||
|
u8 micRate = (MicCnt >> 2) & 3;
|
||||||
|
if (MicClockDivider > micRate)
|
||||||
|
{
|
||||||
|
MicClockDivider = 0;
|
||||||
|
|
||||||
|
s16 sample = 0;
|
||||||
|
if (MicBufferLen > 0)
|
||||||
|
{
|
||||||
|
// 560190 cycles per frame
|
||||||
|
u32 cyclepos = (u32)DSi.GetSysClockCycles(2);
|
||||||
|
u32 samplepos = (cyclepos * MicBufferLen) / 560190;
|
||||||
|
if (samplepos >= MicBufferLen) samplepos = MicBufferLen - 1;
|
||||||
|
sample = MicBuffer[samplepos];
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 oldLevel = MicFifo.Level();
|
||||||
|
if ((MicCnt & 3) == 0)
|
||||||
|
{
|
||||||
|
// stereo (this just duplicates the sample, as the mic itself is mono)
|
||||||
|
if (MicFifo.IsFull()) MicCnt |= 1 << 11;
|
||||||
|
MicFifo.Write(sample);
|
||||||
|
if (MicFifo.IsFull()) MicCnt |= 1 << 11;
|
||||||
|
MicFifo.Write(sample);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// mono
|
||||||
|
if (MicFifo.IsFull()) MicCnt |= 1 << 11;
|
||||||
|
MicFifo.Write(sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if bit 13 is set, an IRQ is generated when the mic FIFO is half full
|
||||||
|
if (MicCnt & (1 << 13))
|
||||||
|
{
|
||||||
|
if (oldLevel < 16 && MicFifo.Level() >= 16) DSi.SetIRQ2(IRQ2_DSi_MicExt);
|
||||||
|
}
|
||||||
|
// if bit 13 is not set and bit 14 is set, an IRQ is generated when the mic FIFO is full
|
||||||
|
else if (MicCnt & (1 << 14))
|
||||||
|
{
|
||||||
|
if (oldLevel < 32 && MicFifo.Level() >= 32) DSi.SetIRQ2(IRQ2_DSi_MicExt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: SPU and DSP sampling should happen here
|
||||||
|
// use passed freq to know how much to advance SPU by?
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SndExCnt & (1 << 13))
|
||||||
|
{
|
||||||
|
DSi.ScheduleEvent(Event_DSi_I2S, false, 704, 0, I2S_Freq_47605Hz);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DSi.ScheduleEvent(Event_DSi_I2S, false, 1024, 0, I2S_Freq_32728Hz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
Copyright 2016-2024 melonDS team
|
||||||
|
|
||||||
|
This file is part of melonDS.
|
||||||
|
|
||||||
|
melonDS 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 3 of the License, or (at your option)
|
||||||
|
any later version.
|
||||||
|
|
||||||
|
melonDS 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 melonDS. If not, see http://www.gnu.org/licenses/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef DSI_I2S_H
|
||||||
|
#define DSI_I2S_H
|
||||||
|
|
||||||
|
#include "FIFO.h"
|
||||||
|
#include "types.h"
|
||||||
|
#include "Savestate.h"
|
||||||
|
|
||||||
|
namespace melonDS
|
||||||
|
{
|
||||||
|
class DSi;
|
||||||
|
class DSi_I2S
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DSi_I2S(melonDS::DSi& dsi);
|
||||||
|
~DSi_I2S();
|
||||||
|
void Reset();
|
||||||
|
void DoSavestate(Savestate* file);
|
||||||
|
|
||||||
|
void MicInputFrame(const s16* data, int samples);
|
||||||
|
|
||||||
|
u16 ReadMicCnt();
|
||||||
|
void WriteMicCnt(u16 val, u16 mask);
|
||||||
|
|
||||||
|
u32 ReadMicData();
|
||||||
|
|
||||||
|
u16 ReadSndExCnt();
|
||||||
|
void WriteSndExCnt(u16 val, u16 mask);
|
||||||
|
|
||||||
|
private:
|
||||||
|
melonDS::DSi& DSi;
|
||||||
|
|
||||||
|
u16 MicCnt;
|
||||||
|
u16 SndExCnt;
|
||||||
|
u8 MicClockDivider;
|
||||||
|
FIFO<s16, 32> MicFifo;
|
||||||
|
|
||||||
|
s16 MicBuffer[1024];
|
||||||
|
int MicBufferLen;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
I2S_Freq_32728Hz,
|
||||||
|
I2S_Freq_47605Hz
|
||||||
|
};
|
||||||
|
|
||||||
|
void Clock(u32 freq);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif // DSI_I2S_H
|
|
@ -72,6 +72,7 @@ enum
|
||||||
Event_DSi_CamIRQ,
|
Event_DSi_CamIRQ,
|
||||||
Event_DSi_CamTransfer,
|
Event_DSi_CamTransfer,
|
||||||
Event_DSi_DSP,
|
Event_DSi_DSP,
|
||||||
|
Event_DSi_I2S,
|
||||||
|
|
||||||
Event_MAX
|
Event_MAX
|
||||||
};
|
};
|
||||||
|
@ -400,7 +401,7 @@ public: // TODO: Encapsulate the rest of these members
|
||||||
void SetLidClosed(bool closed);
|
void SetLidClosed(bool closed);
|
||||||
|
|
||||||
virtual void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) {}
|
virtual void CamInputFrame(int cam, const u32* data, int width, int height, bool rgb) {}
|
||||||
void MicInputFrame(s16* data, int samples);
|
virtual void MicInputFrame(s16* data, int samples);
|
||||||
|
|
||||||
void RegisterEventFunc(u32 id, u32 funcid, EventFunc func);
|
void RegisterEventFunc(u32 id, u32 funcid, EventFunc func);
|
||||||
void UnregisterEventFunc(u32 id, u32 funcid);
|
void UnregisterEventFunc(u32 id, u32 funcid);
|
||||||
|
|
Loading…
Reference in New Issue