Implement resetting DI via HW_RESETS

This commit is contained in:
Pokechu22 2021-06-27 13:34:44 -07:00
parent 954f27c5d7
commit f664cf1903
2 changed files with 100 additions and 6 deletions

View File

@ -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> gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN
static Common::Flags<GPIO> gpio_dir;
Common::Flags<GPIO> 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<u32>(&g_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](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<u32>(&gpio_dir.m_hex),
MMIO::DirectWrite<u32>(&gpio_dir.m_hex));
MMIO::ComplexWrite<u32>([](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>([](u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside();
return gpio_in.m_hex;
}),
MMIO::Nop<u32>());
// 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<u32>(&g_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](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<u32>(&gpio_dir.m_hex),
MMIO::ComplexWrite<u32>([](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>([](u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside();
return gpio_in.m_hex;
}),
MMIO::Nop<u32>());
mmio->Register(base | HW_RESETS, MMIO::DirectRead<u32>(&resets),
MMIO::ComplexWrite<u32>([](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<u32>(), MMIO::Nop<u32>());

View File

@ -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::DIResult> 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<u32>(GPIO::DI_SPIN);
else
HW_GPIO_OUT = old_gpio | static_cast<u32>(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;
}