Pad: Fix timing issues w/ BIOS

This commit is contained in:
Connor McLaughlin 2019-09-23 01:25:58 +10:00
parent 734d1a7ee1
commit 5d1c12c9ad
4 changed files with 127 additions and 22 deletions

View File

@ -25,7 +25,7 @@ bool DigitalController::Transfer(const u8 data_in, u8* data_out)
Log_DebugPrintf("Access"); Log_DebugPrintf("Access");
// response is hi-z // response is hi-z
*data_out = data_in; *data_out = 0xFF;
ack = true; ack = true;
} }
break; break;

View File

@ -3,14 +3,16 @@
#include "common/state_wrapper.h" #include "common/state_wrapper.h"
#include "interrupt_controller.h" #include "interrupt_controller.h"
#include "pad_device.h" #include "pad_device.h"
#include "system.h"
Log_SetChannel(Pad); Log_SetChannel(Pad);
Pad::Pad() = default; Pad::Pad() = default;
Pad::~Pad() = default; Pad::~Pad() = default;
bool Pad::Initialize(InterruptController* interrupt_controller) bool Pad::Initialize(System* system, InterruptController* interrupt_controller)
{ {
m_system = system;
m_interrupt_controller = interrupt_controller; m_interrupt_controller = interrupt_controller;
return true; return true;
} }
@ -22,6 +24,8 @@ void Pad::Reset()
bool Pad::DoState(StateWrapper& sw) bool Pad::DoState(StateWrapper& sw)
{ {
sw.Do(&m_state);
sw.Do(&m_ticks_remaining);
sw.Do(&m_JOY_CTRL.bits); sw.Do(&m_JOY_CTRL.bits);
sw.Do(&m_JOY_STAT.bits); sw.Do(&m_JOY_STAT.bits);
sw.Do(&m_JOY_MODE.bits); sw.Do(&m_JOY_MODE.bits);
@ -49,7 +53,11 @@ u32 Pad::ReadRegister(u32 offset)
} }
case 0x04: // JOY_STAT case 0x04: // JOY_STAT
return m_JOY_STAT.bits; {
const u32 bits = m_JOY_STAT.bits;
m_JOY_STAT.ACKINPUT = false;
return bits;
}
case 0x08: // JOY_MODE case 0x08: // JOY_MODE
return ZeroExtend32(m_JOY_MODE.bits); return ZeroExtend32(m_JOY_MODE.bits);
@ -78,8 +86,8 @@ void Pad::WriteRegister(u32 offset, u32 value)
m_TX_FIFO.Push(Truncate8(value)); m_TX_FIFO.Push(Truncate8(value));
if (m_JOY_CTRL.SELECT) if (!IsTransmitting() && CanTransfer())
DoTransfer(); BeginTransfer();
return; return;
} }
@ -96,13 +104,19 @@ void Pad::WriteRegister(u32 offset, u32 value)
if (m_JOY_CTRL.ACK) if (m_JOY_CTRL.ACK)
{ {
// reset stat bits // reset stat bits
m_JOY_STAT.ACKINPUTLEVEL = false;
m_JOY_STAT.INTR = false; m_JOY_STAT.INTR = false;
m_JOY_CTRL.ACK = true;
} }
if (!old_select && m_JOY_CTRL.SELECT && !m_TX_FIFO.IsEmpty()) if (!m_JOY_CTRL.SELECT || !m_JOY_CTRL.TXEN)
DoTransfer(); {
if (IsTransmitting())
EndTransfer();
}
else
{
if (!IsTransmitting() && CanTransfer())
BeginTransfer();
}
return; return;
} }
@ -126,8 +140,28 @@ void Pad::WriteRegister(u32 offset, u32 value)
} }
} }
void Pad::Execute(TickCount ticks)
{
switch (m_state)
{
case State::Idle:
break;
case State::Transmitting:
{
m_ticks_remaining -= ticks;
if (m_ticks_remaining <= 0)
DoTransfer();
}
break;
}
}
void Pad::SoftReset() void Pad::SoftReset()
{ {
if (IsTransmitting())
EndTransfer();
m_JOY_CTRL.bits = 0; m_JOY_CTRL.bits = 0;
m_JOY_STAT.bits = 0; m_JOY_STAT.bits = 0;
m_JOY_MODE.bits = 0; m_JOY_MODE.bits = 0;
@ -143,35 +177,84 @@ void Pad::UpdateJoyStat()
m_JOY_STAT.TXRDY = !m_TX_FIFO.IsFull(); m_JOY_STAT.TXRDY = !m_TX_FIFO.IsFull();
} }
void Pad::BeginTransfer()
{
DebugAssert(m_state == State::Idle && CanTransfer());
Log_DebugPrintf("Starting transfer");
m_JOY_CTRL.RXEN = true;
// The transfer or the interrupt must be delayed, otherwise the BIOS thinks there's no device detected.
// It seems to do something resembling the following:
// 1) Sets the control register up for transmitting, interrupt on ACK.
// 2) Writes 0x01 to the TX FIFO.
// 3) Delays for a bit.
// 4) Writes ACK to the control register, clearing the interrupt flag.
// 5) Clears IRQ7 in the interrupt controller.
// 6) Waits until the RX FIFO is not empty, reads the first byte to $zero.
// 7) Checks if the interrupt status register had IRQ7 set. If not, no device connected.
//
// Performing the transfer immediately will result in both the INTR bit and the bit in the interrupt
// controller being discarded in (4)/(5), but this bit was set by the *new* transfer. Therefore, the
// test in (7) will fail, and it won't send any more data. So, the transfer/interrupt must be delayed
// until after (4) and (5) have been completed.
m_system->Synchronize();
m_state = State::Transmitting;
m_ticks_remaining = TRANSFER_TICKS;
m_system->SetDowncount(m_ticks_remaining);
}
void Pad::DoTransfer() void Pad::DoTransfer()
{ {
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue()); Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
const std::shared_ptr<PadDevice>& dev = m_devices[m_JOY_CTRL.SLOT]; const std::shared_ptr<PadDevice>& dev = m_devices[m_JOY_CTRL.SLOT];
if (!dev) if (!dev || !CanTransfer())
{ {
// no device present, don't set ACK and read hi-z // no device present, don't set ACK and read hi-z
m_TX_FIFO.Clear(); m_TX_FIFO.Clear();
m_RX_FIFO.Clear(); m_RX_FIFO.Clear();
m_RX_FIFO.Push(0xFF); m_RX_FIFO.Push(0xFF);
UpdateJoyStat(); UpdateJoyStat();
EndTransfer();
return; return;
} }
while (!m_TX_FIFO.IsEmpty()) // set rx?
{ m_JOY_CTRL.RXEN = true;
const u8 data_out = m_TX_FIFO.Pop();
u8 data_in;
m_JOY_STAT.ACKINPUTLEVEL |= dev->Transfer(data_out, &data_in);
m_RX_FIFO.Push(data_in);
m_JOY_CTRL.RXEN = true;
}
if (m_JOY_STAT.ACKINPUTLEVEL && m_JOY_CTRL.ACKINTEN) const u8 data_out = m_TX_FIFO.Pop();
u8 data_in;
m_JOY_STAT.ACKINPUT |= dev->Transfer(data_out, &data_in);
m_RX_FIFO.Push(data_in);
if (m_JOY_STAT.ACKINPUT && m_JOY_CTRL.ACKINTEN)
{ {
Log_DebugPrintf("Triggering interrupt");
m_JOY_STAT.INTR = true; m_JOY_STAT.INTR = true;
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::IRQ7); m_interrupt_controller->InterruptRequest(InterruptController::IRQ::IRQ7);
} }
if (m_TX_FIFO.IsEmpty())
{
EndTransfer();
}
else
{
// queue the next byte
m_ticks_remaining += TRANSFER_TICKS;
m_system->SetDowncount(m_ticks_remaining);
}
UpdateJoyStat(); UpdateJoyStat();
} }
void Pad::EndTransfer()
{
DebugAssert(m_state == State::Transmitting);
Log_DebugPrintf("Ending transfer");
m_state = State::Idle;
m_ticks_remaining = 0;
}

View File

@ -7,6 +7,7 @@
class StateWrapper; class StateWrapper;
class System;
class InterruptController; class InterruptController;
class PadDevice; class PadDevice;
@ -16,7 +17,7 @@ public:
Pad(); Pad();
~Pad(); ~Pad();
bool Initialize(InterruptController* interrupt_controller); bool Initialize(System* system, InterruptController* interrupt_controller);
void Reset(); void Reset();
bool DoState(StateWrapper& sw); bool DoState(StateWrapper& sw);
@ -26,8 +27,17 @@ public:
u32 ReadRegister(u32 offset); u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value); void WriteRegister(u32 offset, u32 value);
void Execute(TickCount ticks);
private: private:
static constexpr u32 NUM_SLOTS = 2; static constexpr u32 NUM_SLOTS = 2;
static constexpr u32 TRANSFER_TICKS = 500;
enum class State : u32
{
Idle,
Transmitting
};
union JOY_CTRL union JOY_CTRL
{ {
@ -52,7 +62,6 @@ private:
BitField<u32, bool, 0, 1> TXRDY; BitField<u32, bool, 0, 1> TXRDY;
BitField<u32, bool, 1, 1> RXFIFONEMPTY; BitField<u32, bool, 1, 1> RXFIFONEMPTY;
BitField<u32, bool, 2, 1> TXDONE; BitField<u32, bool, 2, 1> TXDONE;
BitField<u32, bool, 3, 1> ACKINPUTLEVEL;
BitField<u32, bool, 7, 1> ACKINPUT; BitField<u32, bool, 7, 1> ACKINPUT;
BitField<u32, bool, 9, 1> INTR; BitField<u32, bool, 9, 1> INTR;
BitField<u32, u32, 11, 21> TMR; BitField<u32, u32, 11, 21> TMR;
@ -69,12 +78,24 @@ private:
BitField<u16, u8, 8, 1> clk_polarity; BitField<u16, u8, 8, 1> clk_polarity;
}; };
bool IsTransmitting() const { return m_state == State::Transmitting; }
bool CanTransfer() const
{
return !m_TX_FIFO.IsEmpty() && !m_RX_FIFO.IsFull() && m_JOY_CTRL.SELECT && m_JOY_CTRL.TXEN;
}
void SoftReset(); void SoftReset();
void UpdateJoyStat(); void UpdateJoyStat();
void BeginTransfer();
void DoTransfer(); void DoTransfer();
void EndTransfer();
System* m_system = nullptr;
InterruptController* m_interrupt_controller = nullptr; InterruptController* m_interrupt_controller = nullptr;
State m_state = State::Idle;
TickCount m_ticks_remaining = 0;
JOY_CTRL m_JOY_CTRL = {}; JOY_CTRL m_JOY_CTRL = {};
JOY_STAT m_JOY_STAT = {}; JOY_STAT m_JOY_STAT = {};
JOY_MODE m_JOY_MODE = {}; JOY_MODE m_JOY_MODE = {};

View File

@ -49,7 +49,7 @@ bool System::Initialize()
if (!m_cdrom->Initialize(this, m_dma.get(), m_interrupt_controller.get())) if (!m_cdrom->Initialize(this, m_dma.get(), m_interrupt_controller.get()))
return false; return false;
if (!m_pad->Initialize(m_interrupt_controller.get())) if (!m_pad->Initialize(this, m_interrupt_controller.get()))
return false; return false;
if (!m_timers->Initialize(this, m_interrupt_controller.get())) if (!m_timers->Initialize(this, m_interrupt_controller.get()))
@ -219,6 +219,7 @@ void System::Synchronize()
m_gpu->Execute(pending_ticks); m_gpu->Execute(pending_ticks);
m_timers->AddSystemTicks(pending_ticks); m_timers->AddSystemTicks(pending_ticks);
m_cdrom->Execute(pending_ticks); m_cdrom->Execute(pending_ticks);
m_pad->Execute(pending_ticks);
} }
void System::SetDowncount(TickCount downcount) void System::SetDowncount(TickCount downcount)