Pad: Fix timing issues w/ BIOS
This commit is contained in:
parent
734d1a7ee1
commit
5d1c12c9ad
|
@ -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;
|
||||||
|
|
119
src/pse/pad.cpp
119
src/pse/pad.cpp
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 = {};
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue