From f664cf1903ff8403fd66fcbb8cf8b4092048c1ce Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sun, 27 Jun 2021 13:34:44 -0700 Subject: [PATCH] Implement resetting DI via HW_RESETS --- Source/Core/Core/HW/WII_IPC.cpp | 76 +++++++++++++++++++++++++++++++-- Source/Core/Core/IOS/DI/DI.cpp | 30 ++++++++++++- 2 files changed, 100 insertions(+), 6 deletions(-) diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index 5fd98e91ba..e388de87fe 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -43,6 +43,12 @@ enum GPIOB_DIR = 0xc4, GPIOB_IN = 0xc8, + GPIO_OUT = 0xe0, + GPIO_DIR = 0xe4, + GPIO_IN = 0xe8, + + HW_RESETS = 0x194, + UNK_180 = 0x180, UNK_1CC = 0x1cc, UNK_1D0 = 0x1d0, @@ -103,6 +109,8 @@ static constexpr Common::Flags gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN static Common::Flags gpio_dir; Common::Flags g_gpio_out; +static u32 resets; + static CoreTiming::EventType* updateInterrupts; static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0); @@ -129,10 +137,20 @@ static void InitState() arm_irq_flags = 0; arm_irq_masks = 0; - // The only input broadway has is SLOT_IN; all the others it has access to are outputs - gpio_dir = {GPIO::SLOT_LED, GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA}; + // The only inputs are POWER, EJECT_BTN, SLOT_IN, and EEP_MISO; Broadway only has access to + // SLOT_IN + gpio_dir = { + GPIO::POWER, GPIO::SHUTDOWN, GPIO::FAN, GPIO::DC_DC, GPIO::DI_SPIN, GPIO::SLOT_LED, + GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::EEP_CS, GPIO::EEP_CLK, GPIO::EEP_MOSI, GPIO::AVE_SCL, + GPIO::AVE_SDA, GPIO::DEBUG0, GPIO::DEBUG1, GPIO::DEBUG2, GPIO::DEBUG3, GPIO::DEBUG4, + GPIO::DEBUG5, GPIO::DEBUG6, GPIO::DEBUG7, + }; g_gpio_out = {}; + // A cleared bit indicates the device is reset/off, so set everything to 1 (this may not exactly + // match hardware) + resets = 0xffffffff; + ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY; } @@ -189,7 +207,8 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) mmio->Register(base | GPIOB_OUT, MMIO::DirectRead(&g_gpio_out.m_hex), MMIO::ComplexWrite([](u32, u32 val) { - g_gpio_out.m_hex = val & gpio_owner.m_hex; + g_gpio_out.m_hex = + (val & gpio_owner.m_hex) | (g_gpio_out.m_hex & ~gpio_owner.m_hex); if (g_gpio_out[GPIO::DO_EJECT]) { INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); @@ -199,13 +218,62 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) // TODO: AVE, SLOT_LED })); mmio->Register(base | GPIOB_DIR, MMIO::DirectRead(&gpio_dir.m_hex), - MMIO::DirectWrite(&gpio_dir.m_hex)); + MMIO::ComplexWrite([](u32, u32 val) { + gpio_dir.m_hex = (val & gpio_owner.m_hex) | (gpio_dir.m_hex & ~gpio_owner.m_hex); + })); mmio->Register(base | GPIOB_IN, MMIO::ComplexRead([](u32) { Common::Flags gpio_in; gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside(); return gpio_in.m_hex; }), MMIO::Nop()); + // Starlet GPIO registers, not normally accessible by PPC (but they can be depending on how + // AHBPROT is set up). We just always allow access, since some homebrew uses them. + + // Note from WiiBrew: When switching owners, copying of the data is not necessary. For example, if + // pin 0 has certain configuration in the HW_GPIO registers, and that bit is then set in the + // HW_GPIO_OWNER register, those settings will immediately be visible in the HW_GPIOB registers. + // There is only one set of data registers, and the HW_GPIO_OWNER register just controls the + // access that the HW_GPIOB registers have to that data. + // Also: The HW_GPIO registers always have read access to all pins, but any writes (changes) must + // go through the HW_GPIOB registers if the corresponding bit is set in the HW_GPIO_OWNER + // register. + mmio->Register(base | GPIO_OUT, MMIO::DirectRead(&g_gpio_out.m_hex), + MMIO::ComplexWrite([](u32, u32 val) { + g_gpio_out.m_hex = + (g_gpio_out.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex); + if (g_gpio_out[GPIO::DO_EJECT]) + { + INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); + DVDInterface::EjectDisc(DVDInterface::EjectCause::Software); + } + // SENSOR_BAR is checked by WiimoteEmu::CameraLogic + // TODO: AVE, SLOT_LED + })); + mmio->Register(base | GPIO_DIR, MMIO::DirectRead(&gpio_dir.m_hex), + MMIO::ComplexWrite([](u32, u32 val) { + gpio_dir.m_hex = (gpio_dir.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex); + })); + mmio->Register(base | GPIO_IN, MMIO::ComplexRead([](u32) { + Common::Flags gpio_in; + gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside(); + return gpio_in.m_hex; + }), + MMIO::Nop()); + + mmio->Register(base | HW_RESETS, MMIO::DirectRead(&resets), + MMIO::ComplexWrite([](u32, u32 val) { + // A reset occurs when the corresponding bit is cleared + const bool di_reset_triggered = (resets & 0x400) && !(val & 0x400); + resets = val; + if (di_reset_triggered) + { + // The GPIO *disables* spinning up the drive + const bool spinup = !g_gpio_out[GPIO::DI_SPIN]; + INFO_LOG_FMT(WII_IPC, "Resetting DI {} spinup", spinup ? "with" : "without"); + DVDInterface::ResetDrive(spinup); + } + })); // Register some stubbed/unknown MMIOs required to make Wii games work. mmio->Register(base | PPCSPEED, MMIO::InvalidRead(), MMIO::Nop()); diff --git a/Source/Core/Core/IOS/DI/DI.cpp b/Source/Core/Core/IOS/DI/DI.cpp index 7ca6a6fdc2..567e1ecc3a 100644 --- a/Source/Core/Core/IOS/DI/DI.cpp +++ b/Source/Core/Core/IOS/DI/DI.cpp @@ -17,6 +17,7 @@ #include "Core/HW/DVD/DVDThread.h" #include "Core/HW/MMIO.h" #include "Core/HW/Memmap.h" +#include "Core/HW/WII_IPC.h" #include "Core/IOS/ES/ES.h" #include "Core/IOS/ES/Formats.h" #include "DiscIO/Volume.h" @@ -38,6 +39,9 @@ static RegisterWrapper<0x0D806018> DILENGTH; static RegisterWrapper<0x0D80601C> DICR; static RegisterWrapper<0x0D806020> DIIMMBUF; +static RegisterWrapper<0x0D8000E0> HW_GPIO_OUT; +static RegisterWrapper<0x0D800194> HW_RESETS; + namespace IOS::HLE { CoreTiming::EventType* DIDevice::s_finish_executing_di_command; @@ -266,8 +270,30 @@ std::optional DIDevice::StartIOCtl(const IOCtlRequest& reque case DIIoctl::DVDLowReset: { const bool spinup = Memory::Read_U32(request.buffer_in + 4); - INFO_LOG_FMT(IOS_DI, "DVDLowReset {} spinup", spinup ? "with" : "without"); - DVDInterface::ResetDrive(spinup); + + // The GPIO *disables* spinning up the drive. Normally handled via syscall 0x4e. + const u32 old_gpio = HW_GPIO_OUT; + if (spinup) + HW_GPIO_OUT = old_gpio & ~static_cast(GPIO::DI_SPIN); + else + HW_GPIO_OUT = old_gpio | static_cast(GPIO::DI_SPIN); + + // Syscall 0x46 check_di_reset + const bool was_resetting = (HW_RESETS & (1 << 10)) == 0; + if (was_resetting) + { + // This route will not generally be taken in Dolphin but is included for completeness + // Syscall 0x45 deassert_di_reset + HW_RESETS = HW_RESETS | (1 << 10); + } + else + { + // Syscall 0x44 assert_di_reset + HW_RESETS = HW_RESETS & ~(1 << 10); + // Normally IOS sleeps for 12 microseconds here, but we can't easily emulate that + // Syscall 0x45 deassert_di_reset + HW_RESETS = HW_RESETS | (1 << 10); + } ResetDIRegisters(); return DIResult::Success; }