PAD: Basic support for digital controllers

This commit is contained in:
Connor McLaughlin 2019-09-20 16:47:41 +10:00
parent d84bffead1
commit 8cd75a4937
16 changed files with 552 additions and 4 deletions

View file

@ -79,7 +79,8 @@ static int Run(int argc, char* argv[])
int main(int argc, char* argv[])
{
// set log flags
g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
// g_pLog->SetConsoleOutputParams(true, nullptr, LOGLEVEL_DEBUG);
g_pLog->SetConsoleOutputParams(true, "GPU GPU_HW_OpenGL DMA", LOGLEVEL_DEBUG);
#ifdef Y_BUILD_CONFIG_RELEASE
g_pLog->SetFilterLevel(LOGLEVEL_INFO);

View file

@ -5,6 +5,7 @@
#include "imgui.h"
#include "imgui_impl_opengl3.h"
#include "imgui_impl_sdl.h"
#include "pse/digital_controller.h"
#include "pse/system.h"
#include <cinttypes>
#include <glad.h>
@ -159,6 +160,8 @@ std::unique_ptr<SDLInterface> SDLInterface::Create()
if (!intf->CreateSDLWindow() || !intf->CreateGLContext() || !intf->CreateImGuiContext() || !intf->CreateGLResources())
return nullptr;
intf->m_controller = DigitalController::Create();
return intf;
}
@ -210,6 +213,73 @@ bool SDLInterface::HandleSDLEvent(const SDL_Event* event)
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
{
const bool pressed = (event->type == SDL_KEYDOWN);
switch (event->key.keysym.scancode)
{
case SDL_SCANCODE_KP_8:
case SDL_SCANCODE_I:
m_controller->SetButtonState(DigitalController::Button::Triangle, pressed);
return true;
case SDL_SCANCODE_KP_2:
case SDL_SCANCODE_K:
m_controller->SetButtonState(DigitalController::Button::Cross, pressed);
return true;
case SDL_SCANCODE_KP_4:
case SDL_SCANCODE_J:
m_controller->SetButtonState(DigitalController::Button::Square, pressed);
return true;
case SDL_SCANCODE_KP_6:
case SDL_SCANCODE_L:
m_controller->SetButtonState(DigitalController::Button::Circle, pressed);
return true;
case SDL_SCANCODE_W:
case SDL_SCANCODE_UP:
m_controller->SetButtonState(DigitalController::Button::Up, pressed);
return true;
case SDL_SCANCODE_S:
case SDL_SCANCODE_DOWN:
m_controller->SetButtonState(DigitalController::Button::Down, pressed);
return true;
case SDL_SCANCODE_A:
case SDL_SCANCODE_LEFT:
m_controller->SetButtonState(DigitalController::Button::Left, pressed);
return true;
case SDL_SCANCODE_D:
case SDL_SCANCODE_RIGHT:
m_controller->SetButtonState(DigitalController::Button::Right, pressed);
return true;
case SDL_SCANCODE_Q:
m_controller->SetButtonState(DigitalController::Button::L1, pressed);
return true;
case SDL_SCANCODE_E:
m_controller->SetButtonState(DigitalController::Button::R1, pressed);
return true;
case SDL_SCANCODE_1:
m_controller->SetButtonState(DigitalController::Button::L2, pressed);
return true;
case SDL_SCANCODE_3:
m_controller->SetButtonState(DigitalController::Button::R3, pressed);
return true;
case SDL_SCANCODE_RETURN:
m_controller->SetButtonState(DigitalController::Button::Start, pressed);
return true;
case SDL_SCANCODE_BACKSPACE:
m_controller->SetButtonState(DigitalController::Button::Select, pressed);
return true;
default:
break;
}
}
break;
case SDL_QUIT:
m_running = false;
break;
@ -452,6 +522,8 @@ void SDLInterface::DoSaveState(u32 index)
void SDLInterface::Run()
{
m_system->SetPadDevice(0, m_controller);
while (m_running)
{
for (;;)

View file

@ -8,8 +8,10 @@
#include <array>
#include <deque>
#include <mutex>
#include <memory>
class System;
class DigitalController;
class SDLInterface : public HostInterface
{
@ -72,4 +74,6 @@ private:
std::deque<OSDMessage> m_osd_messages;
std::mutex m_osd_messages_lock;
std::shared_ptr<DigitalController> m_controller;
};

View file

@ -9,6 +9,7 @@
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "pad.h"
#include <cstdio>
Log_SetChannel(Bus);
@ -24,7 +25,7 @@ Bus::Bus() = default;
Bus::~Bus() = default;
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom)
bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad)
{
if (!LoadBIOS())
return false;
@ -34,6 +35,7 @@ bool Bus::Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_co
m_interrupt_controller = interrupt_controller;
m_gpu = gpu;
m_cdrom = cdrom;
m_pad = pad;
return true;
}
@ -216,6 +218,18 @@ bool Bus::WriteExpansionRegion2(MemoryAccessSize size, u32 offset, u32 value)
return DoInvalidAccess(MemoryAccessType::Write, size, EXP2_BASE | offset, EXP2_BASE | offset, value);
}
bool Bus::DoReadPad(MemoryAccessSize size, u32 offset, u32& value)
{
value = m_pad->ReadRegister(offset);
return true;
}
bool Bus::DoWritePad(MemoryAccessSize size, u32 offset, u32 value)
{
m_pad->WriteRegister(offset, value);
return true;
}
bool Bus::DoReadCDROM(MemoryAccessSize size, u32 offset, u32& value)
{
// TODO: Splitting of half/word reads.

View file

@ -15,6 +15,7 @@ class DMA;
class InterruptController;
class GPU;
class CDROM;
class Pad;
class System;
class Bus
@ -23,7 +24,7 @@ public:
Bus();
~Bus();
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom);
bool Initialize(CPU::Core* cpu, DMA* dma, InterruptController* interrupt_controller, GPU* gpu, CDROM* cdrom, Pad* pad);
void Reset();
bool DoState(StateWrapper& sw);
@ -40,6 +41,9 @@ public:
void PatchBIOS(u32 address, u32 value, u32 mask = UINT32_C(0xFFFFFFFF));
private:
static constexpr u32 PAD_BASE = 0x1F801040;
static constexpr u32 PAD_SIZE = 0x10;
static constexpr u32 PAD_MASK = PAD_SIZE - 1;
static constexpr u32 INTERRUPT_CONTROLLER_BASE = 0x1F801070;
static constexpr u32 INTERRUPT_CONTROLLER_SIZE = 0x08;
static constexpr u32 INTERRUPT_CONTROLLER_MASK = INTERRUPT_CONTROLLER_SIZE - 1;
@ -75,6 +79,9 @@ private:
bool ReadExpansionRegion2(MemoryAccessSize size, u32 offset, u32& value);
bool WriteExpansionRegion2(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadPad(MemoryAccessSize size, u32 offset, u32& value);
bool DoWritePad(MemoryAccessSize size, u32 offset, u32 value);
bool DoReadCDROM(MemoryAccessSize size, u32 offset, u32& value);
bool DoWriteCDROM(MemoryAccessSize size, u32 offset, u32 value);
@ -95,6 +102,7 @@ private:
InterruptController* m_interrupt_controller = nullptr;
GPU* m_gpu = nullptr;
CDROM* m_cdrom = nullptr;
Pad* m_pad = nullptr;
std::array<u8, 2097152> m_ram{}; // 2MB RAM
std::array<u8, 524288> m_bios{}; // 512K BIOS ROM

View file

@ -80,6 +80,15 @@ bool Bus::DispatchAccess(PhysicalMemoryAddress cpu_address, PhysicalMemoryAddres
{
return DoRAMAccess<type, size>(bus_address, value);
}
else if (bus_address < PAD_BASE)
{
return DoInvalidAccess(type, size, cpu_address, bus_address, value);
}
else if (bus_address < (PAD_BASE + PAD_SIZE))
{
return (type == MemoryAccessType::Read) ? DoReadPad(size, bus_address & PAD_MASK, value) :
DoWritePad(size, bus_address & PAD_MASK, value);
}
else if (bus_address < INTERRUPT_CONTROLLER_BASE)
{
return DoInvalidAccess(type, size, cpu_address, bus_address, value);

View file

@ -0,0 +1,77 @@
#include "digital_controller.h"
#include "YBaseLib/Log.h"
Log_SetChannel(DigitalController);
DigitalController::DigitalController() = default;
DigitalController::~DigitalController() = default;
void DigitalController::SetButtonState(Button button, bool pressed)
{
if (pressed)
m_button_state &= ~(u16(1) << static_cast<u8>(button));
else
m_button_state |= u16(1) << static_cast<u8>(button);
}
bool DigitalController::Transfer(const u8 data_in, u8* data_out)
{
bool ack;
switch (data_in)
{
case 0x01: // tests if the controller is present
{
Log_DebugPrintf("Access");
// response is hi-z
*data_out = data_in;
ack = true;
}
break;
case 0x42: // query state
{
Log_DebugPrintf("Query state");
QueryState();
[[fallthrough]];
}
default: // sending response
{
if (m_transfer_fifo.IsEmpty())
{
Log_WarningPrint("FIFO empty on read");
*data_out = 0xFF;
ack = false;
}
else
{
*data_out = m_transfer_fifo.Pop();
ack = !m_transfer_fifo.IsEmpty();
}
}
break;
}
Log_DebugPrintf("Transfer, data_in=0x%02X, data_out=0x%02X, ack=%s", data_in, *data_out, ack ? "true" : "false");
return ack;
}
void DigitalController::QueryState()
{
constexpr u16 ID = 0x5A41;
m_transfer_fifo.Clear();
m_transfer_fifo.Push(Truncate8(ID));
m_transfer_fifo.Push(Truncate8(ID >> 8));
m_transfer_fifo.Push(Truncate8(m_button_state)); // Digital switches low
m_transfer_fifo.Push(Truncate8(m_button_state >> 8)); // Digital switches high
}
std::shared_ptr<DigitalController> DigitalController::Create()
{
return std::make_shared<DigitalController>();
}

View file

@ -0,0 +1,45 @@
#pragma once
#include "common/fifo_queue.h"
#include "pad_device.h"
#include <memory>
class DigitalController final : public PadDevice
{
public:
enum class Button : u8
{
Select = 0,
L3 = 1,
R3 = 2,
Start = 3,
Up = 4,
Right = 5,
Down = 6,
Left = 7,
L2 = 8,
R2 = 9,
L1 = 10,
R1 = 11,
Triangle = 12,
Circle = 13,
Cross = 14,
Square = 15
};
DigitalController();
~DigitalController() override;
static std::shared_ptr<DigitalController> Create();
void SetButtonState(Button button, bool pressed);
bool Transfer(const u8 data_in, u8* data_out) override;
private:
void QueryState();
// buttons are active low
u16 m_button_state = UINT16_C(0xFFFF);
InlineFIFOQueue<u8, 8> m_transfer_fifo;
};

172
src/pse/pad.cpp Normal file
View file

@ -0,0 +1,172 @@
#include "pad.h"
#include "YBaseLib/Log.h"
#include "common/state_wrapper.h"
#include "interrupt_controller.h"
#include "pad_device.h"
Log_SetChannel(Pad);
Pad::Pad() = default;
Pad::~Pad() = default;
bool Pad::Initialize(InterruptController* interrupt_controller)
{
m_interrupt_controller = interrupt_controller;
return true;
}
void Pad::Reset()
{
SoftReset();
}
bool Pad::DoState(StateWrapper& sw)
{
return !sw.HasError();
}
u32 Pad::ReadRegister(u32 offset)
{
switch (offset)
{
case 0x00: // JOY_DATA
{
if (m_RX_FIFO.IsEmpty())
{
Log_WarningPrint("Read from RX fifo when empty");
return 0;
}
const u8 value = m_RX_FIFO.Pop();
UpdateJoyStat();
Log_DebugPrintf("JOY_DATA (R) -> 0x%02X", ZeroExtend32(value));
return ZeroExtend32(value);
}
case 0x04: // JOY_STAT
return m_JOY_STAT.bits;
case 0x08: // JOY_MODE
return ZeroExtend32(m_JOY_MODE.bits);
case 0x0A: // JOY_CTRL
return ZeroExtend32(m_JOY_CTRL.bits);
default:
Log_ErrorPrintf("Unknown register read: 0x%X", offset);
return UINT32_C(0xFFFFFFFF);
}
}
void Pad::WriteRegister(u32 offset, u32 value)
{
switch (offset)
{
case 0x00: // JOY_DATA
{
Log_DebugPrintf("JOY_DATA (W) <- 0x%02X", value);
if (m_TX_FIFO.IsFull())
{
Log_WarningPrint("TX FIFO overrun");
m_TX_FIFO.RemoveOne();
}
m_TX_FIFO.Push(Truncate8(value));
if (m_JOY_CTRL.SELECT)
DoTransfer();
return;
}
case 0x0A: // JOY_CTRL
{
Log_DebugPrintf("JOY_CTRL <- 0x%04X", value);
const bool old_select = m_JOY_CTRL.SELECT;
m_JOY_CTRL.bits = Truncate16(value);
if (m_JOY_CTRL.RESET)
SoftReset();
if (m_JOY_CTRL.ACK)
{
// reset stat bits
m_JOY_STAT.ACKINPUTLEVEL = false;
m_JOY_STAT.INTR = false;
m_JOY_CTRL.ACK = true;
}
if (!old_select && m_JOY_CTRL.SELECT && !m_TX_FIFO.IsEmpty())
DoTransfer();
return;
}
case 0x08: // JOY_MODE
{
Log_DebugPrintf("JOY_MODE <- 0x%08X", value);
m_JOY_MODE.bits = Truncate16(value);
return;
}
case 0x0E:
{
Log_WarningPrintf("JOY_BAUD <- 0x%08X", value);
return;
}
default:
Log_ErrorPrintf("Unknown register write: 0x%X <- 0x%08X", offset, value);
return;
}
}
void Pad::SoftReset()
{
m_JOY_CTRL.bits = 0;
m_JOY_STAT.bits = 0;
m_JOY_MODE.bits = 0;
m_RX_FIFO.Clear();
m_TX_FIFO.Clear();
UpdateJoyStat();
}
void Pad::UpdateJoyStat()
{
m_JOY_STAT.RXFIFONEMPTY = !m_RX_FIFO.IsEmpty();
m_JOY_STAT.TXDONE = m_TX_FIFO.IsEmpty();
m_JOY_STAT.TXRDY = !m_TX_FIFO.IsFull();
}
void Pad::DoTransfer()
{
Log_DebugPrintf("Transferring slot %d", m_JOY_CTRL.SLOT.GetValue());
const std::shared_ptr<PadDevice>& dev = m_devices[m_JOY_CTRL.SLOT];
if (!dev)
{
// no device present, don't set ACK and read hi-z
m_TX_FIFO.Clear();
m_RX_FIFO.Clear();
m_RX_FIFO.Push(0xFF);
UpdateJoyStat();
return;
}
while (!m_TX_FIFO.IsEmpty())
{
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)
{
m_JOY_STAT.INTR = true;
m_interrupt_controller->InterruptRequest(InterruptController::IRQ::IRQ7);
}
UpdateJoyStat();
}

86
src/pse/pad.h Normal file
View file

@ -0,0 +1,86 @@
#pragma once
#include "common/bitfield.h"
#include "common/fifo_queue.h"
#include "types.h"
#include <array>
#include <memory>
class StateWrapper;
class InterruptController;
class PadDevice;
class Pad
{
public:
Pad();
~Pad();
bool Initialize(InterruptController* interrupt_controller);
void Reset();
bool DoState(StateWrapper& sw);
PadDevice* GetDevice(u32 slot) { return m_devices[slot].get(); }
void SetDevice(u32 slot, std::shared_ptr<PadDevice> dev) { m_devices[slot] = dev; }
u32 ReadRegister(u32 offset);
void WriteRegister(u32 offset, u32 value);
private:
static constexpr u32 NUM_SLOTS = 2;
union JOY_CTRL
{
u32 bits;
BitField<u16, bool, 0, 1> TXEN;
BitField<u16, bool, 1, 1> SELECT;
BitField<u16, bool, 2, 1> RXEN;
BitField<u16, bool, 4, 1> ACK;
BitField<u16, bool, 6, 1> RESET;
BitField<u16, u8, 8, 2> RXIMODE;
BitField<u16, bool, 10, 1> TXINTEN;
BitField<u16, bool, 11, 1> RXINTEN;
BitField<u16, bool, 12, 1> ACKINTEN;
BitField<u16, u8, 13, 1> SLOT;
};
union JOY_STAT
{
u32 bits;
BitField<u32, bool, 0, 1> TXRDY;
BitField<u32, bool, 1, 1> RXFIFONEMPTY;
BitField<u32, bool, 2, 1> TXDONE;
BitField<u32, bool, 3, 1> ACKINPUTLEVEL;
BitField<u32, bool, 7, 1> ACKINPUT;
BitField<u32, bool, 9, 1> INTR;
BitField<u32, u32, 11, 21> TMR;
};
union JOY_MODE
{
u16 bits;
BitField<u16, u8, 0, 2> reload_factor;
BitField<u16, u8, 2, 2> character_length;
BitField<u16, bool, 4, 1> parity_enable;
BitField<u16, u8, 5, 1> parity_type;
BitField<u16, u8, 8, 1> clk_polarity;
};
void SoftReset();
void UpdateJoyStat();
void DoTransfer();
InterruptController* m_interrupt_controller = nullptr;
JOY_CTRL m_JOY_CTRL = {};
JOY_STAT m_JOY_STAT = {};
JOY_MODE m_JOY_MODE = {};
InlineFIFOQueue<u8, 8> m_RX_FIFO;
InlineFIFOQueue<u8, 2> m_TX_FIFO;
std::array<std::shared_ptr<PadDevice>, NUM_SLOTS> m_devices;
};

11
src/pse/pad_device.cpp Normal file
View file

@ -0,0 +1,11 @@
#include "pad_device.h"
PadDevice::PadDevice() = default;
PadDevice::~PadDevice() = default;
bool PadDevice::Transfer(const u8 data_in, u8* data_out)
{
*data_out = 0xFF;
return false;
}

13
src/pse/pad_device.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include "types.h"
class PadDevice
{
public:
PadDevice();
virtual ~PadDevice();
// Returns the value of ACK, as well as filling out_data.
virtual bool Transfer(const u8 data_in, u8* data_out);
};

View file

@ -39,6 +39,7 @@
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="cpu_core.cpp" />
<ClCompile Include="cpu_disasm.cpp" />
<ClCompile Include="digital_controller.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="dma.cpp" />
<ClCompile Include="gpu.cpp" />
@ -46,6 +47,8 @@
<ClCompile Include="gpu_hw_opengl.cpp" />
<ClCompile Include="host_interface.cpp" />
<ClCompile Include="interrupt_controller.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="system.cpp" />
</ItemGroup>
<ItemGroup>
@ -53,6 +56,7 @@
<ClInclude Include="cdrom.h" />
<ClInclude Include="cpu_core.h" />
<ClInclude Include="cpu_disasm.h" />
<ClInclude Include="digital_controller.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="cpu_types.h" />
<ClInclude Include="dma.h" />
@ -62,6 +66,8 @@
<ClInclude Include="gte_types.h" />
<ClInclude Include="host_interface.h" />
<ClInclude Include="interrupt_controller.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="pad_device.h" />
<ClInclude Include="save_state_version.h" />
<ClInclude Include="system.h" />
<ClInclude Include="types.h" />

View file

@ -13,6 +13,9 @@
<ClCompile Include="interrupt_controller.cpp" />
<ClCompile Include="cdrom.cpp" />
<ClCompile Include="gte.cpp" />
<ClCompile Include="pad.cpp" />
<ClCompile Include="pad_device.cpp" />
<ClCompile Include="digital_controller.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -31,6 +34,9 @@
<ClInclude Include="cdrom.h" />
<ClInclude Include="gte.h" />
<ClInclude Include="gte_types.h" />
<ClInclude Include="pad.h" />
<ClInclude Include="pad_device.h" />
<ClInclude Include="digital_controller.h" />
</ItemGroup>
<ItemGroup>
<None Include="cpu_core.inl" />

View file

@ -7,6 +7,8 @@
#include "dma.h"
#include "gpu.h"
#include "interrupt_controller.h"
#include "pad.h"
#include "pad_device.h"
System::System(HostInterface* host_interface) : m_host_interface(host_interface)
{
@ -17,6 +19,7 @@ System::System(HostInterface* host_interface) : m_host_interface(host_interface)
// m_gpu = std::make_unique<GPU>();
m_gpu = GPU::CreateHardwareOpenGLRenderer();
m_cdrom = std::make_unique<CDROM>();
m_pad = std::make_unique<Pad>();
}
System::~System() = default;
@ -26,8 +29,11 @@ bool System::Initialize()
if (!m_cpu->Initialize(m_bus.get()))
return false;
if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get()))
if (!m_bus->Initialize(m_cpu.get(), m_dma.get(), m_interrupt_controller.get(), m_gpu.get(), m_cdrom.get(),
m_pad.get()))
{
return false;
}
if (!m_dma->Initialize(m_bus.get(), m_gpu.get()))
return false;
@ -41,6 +47,9 @@ bool System::Initialize()
if (!m_cdrom->Initialize(m_dma.get(), m_interrupt_controller.get()))
return false;
if (!m_pad->Initialize(m_interrupt_controller.get()))
return false;
return true;
}
@ -64,6 +73,9 @@ bool System::DoState(StateWrapper& sw)
if (!sw.DoMarker("CDROM") || !m_cdrom->DoState(sw))
return false;
if (!sw.DoMarker("Pad") || !m_pad->DoState(sw))
return false;
return !sw.HasError();
}
@ -77,6 +89,7 @@ void System::Reset()
m_interrupt_controller->Reset();
m_gpu->Reset();
m_cdrom->Reset();
m_pad->Reset();
m_frame_number = 1;
}
@ -195,3 +208,8 @@ void System::SetSliceTicks(TickCount downcount)
{
m_cpu->SetSliceTicks(downcount);
}
void System::SetPadDevice(u32 slot, std::shared_ptr<PadDevice> dev)
{
m_pad->SetDevice(slot, std::move(dev));
}

View file

@ -1,5 +1,6 @@
#pragma once
#include "types.h"
#include <memory>
class ByteStream;
class StateWrapper;
@ -16,6 +17,8 @@ class DMA;
class InterruptController;
class GPU;
class CDROM;
class Pad;
class PadDevice;
class System
{
@ -40,6 +43,8 @@ public:
void SetSliceTicks(TickCount downcount);
void SetPadDevice(u32 slot, std::shared_ptr<PadDevice> dev);
private:
bool DoState(StateWrapper& sw);
@ -50,5 +55,6 @@ private:
std::unique_ptr<InterruptController> m_interrupt_controller;
std::unique_ptr<GPU> m_gpu;
std::unique_ptr<CDROM> m_cdrom;
std::unique_ptr<Pad> m_pad;
u32 m_frame_number = 1;
};