Implement stub version of the Wii AV Encoder

This commit is contained in:
Pokechu22 2022-07-16 00:05:03 -07:00
parent 05cad38abc
commit a84cd15023
3 changed files with 259 additions and 27 deletions

View File

@ -3,6 +3,9 @@
#include "Core/HW/WII_IPC.h"
#include <array>
#include <string_view>
#include "Common/ChunkFile.h"
#include "Common/CommonTypes.h"
#include "Common/Logging/Log.h"
@ -60,6 +63,63 @@ enum
static constexpr Common::Flags<GPIO> gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN, GPIO::SENSOR_BAR,
GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA};
static u32 resets;
struct I2CState
{
bool active;
u8 bit_counter;
bool read_i2c_address;
bool is_correct_i2c_address;
bool is_read;
bool read_ave_address;
bool acknowledge;
u8 current_byte;
u8 current_address;
};
I2CState i2c_state;
#pragma pack(1)
struct AVEState
{
// See https://wiibrew.org/wiki/Hardware/AV_Encoder#Registers_description
// (note that the code snippet indicates that values are big-endian)
u8 timings; // 0x00
u8 video_output_config; // 0x01
u8 vertical_blanking_interval_control; // 0x02
u8 composite_trap_filter_control; // 0x03
u8 audio_video_output_control; // 0x04
u8 cgms_high, cgms_low; // 0x05-0x06
u8 pad1; // 0x07
u8 wss_high, wss_low; // 0x08-0x09, Widescreen signaling?
u8 rgb_color_output_control; // 0x0A, only used when video_output_config is DEBUG (3)?
std::array<u8, 5> pad2; // 0x0B-0x0F
std::array<u8, 33> gamma_coefficients; // 0x10-0x30
std::array<u8, 15> pad3; // 0x31-0x3F
std::array<u8, 26> macrovision_code; // 0x40-0x59, analog copy protection
std::array<u8, 8> pad4; // 0x5A-0x61
u8 rgb_switch; // 0x62, swap blue and red channels
std::array<u8, 2> pad5; // 0x63-0x64
u8 color_dac; // 0x65
u8 pad6; // 0x66
u8 color_test; // 0x67, display a color test pattern
std::array<u8, 2> pad7; // 0x68-0x69
u8 ccsel; // 0x6A
std::array<u8, 2> pad8; // 0x6B-0x6C
u8 mute; // 0x6D
u8 rgb_output_filter; // 0x6E
std::array<u8, 2> pad9; // 0x6F-0x70
u8 right_volume; // 0x71
u8 left_volume; // 0x72
std::array<u8, 7> pad10; // 0x73-0x79
std::array<u8, 4> closed_captioning; // 0x7A-0x7D
std::array<u8, 130> pad11; // 0x7E-0xFF
};
#pragma pack()
static_assert(sizeof(AVEState) == 0x100);
static AVEState ave_state;
static CoreTiming::EventType* updateInterrupts;
WiiIPC::WiiIPC(Core::System& system) : m_system(system)
{
}
@ -106,6 +166,9 @@ void WiiIPC::InitState()
m_resets = 0xffffffff;
m_ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY;
i2c_state = {};
ave_state = {};
}
void WiiIPC::Init()
@ -125,6 +188,196 @@ void WiiIPC::Shutdown()
{
}
static std::string_view GetAVERegisterName(u8 address)
{
if (address == 0x00)
return "A/V Timings";
else if (address == 0x01)
return "Video Output configuration";
else if (address == 0x02)
return "Vertical blanking interval (VBI) control";
else if (address == 0x03)
return "Composite Video Trap Filter control";
else if (address == 0x04)
return "A/V output control";
else if (address == 0x05 || address == 0x06)
return "CGMS protection";
else if (address == 0x08 || address == 0x09)
return "WSS (Widescreen signaling)";
else if (address == 0x0A)
return "RGB color output control";
else if (address >= 0x10 && address <= 0x30)
return "Gamma coefficients";
else if (address >= 0x40 && address <= 0x59)
return "Macrovision code";
else if (address == 0x62)
return "RGB switch control";
else if (address == 0x65)
return "Color DAC control";
else if (address == 0x67)
return "Color Test";
else if (address == 0x6A)
return "CCSEL";
else if (address == 0x6D)
return "Audio mute control";
else if (address == 0x6E)
return "RGB output filter";
else if (address == 0x71)
return "Audio stereo output control - right volume";
else if (address == 0x72)
return "Audio stereo output control - right volume";
else if (address >= 0x7a && address <= 0x7d)
return "Closed Captioning control";
else
return fmt::format("Unknown ({:02x})", address);
}
static u32 ReadGPIOIn(Core::System& system)
{
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside();
// Note: This doesn't implement the direction logic currently (are bits not included in the
// direction treated as clear?)
if (i2c_state.bit_counter == 9 && i2c_state.acknowledge)
gpio_in[GPIO::AVE_SDA] = false; // pull low
else
gpio_in[GPIO::AVE_SDA] = true; // passive pullup
return gpio_in.m_hex;
}
void WiiIPC::WriteGPIOOut(Core::System& system, bool broadway, u32 value)
{
Common::Flags<GPIO> old_value = m_gpio_out;
if (broadway)
m_gpio_out.m_hex = (value & gpio_owner.m_hex) | (m_gpio_out.m_hex & ~gpio_owner.m_hex);
else
m_gpio_out.m_hex = (value & ~gpio_owner.m_hex) | (m_gpio_out.m_hex & gpio_owner.m_hex);
if (m_gpio_out[GPIO::DO_EJECT])
{
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{system}, DVD::EjectCause::Software);
}
// I²C logic for the audio/video encoder (AVE)
if (m_gpio_dir[GPIO::AVE_SCL])
{
if (old_value[GPIO::AVE_SCL] && m_gpio_out[GPIO::AVE_SCL])
{
// Check for changes to SDA while the clock is high. This only makes sense if the SDA pin is
// outbound.
if (m_gpio_dir[GPIO::AVE_SDA])
{
if (old_value[GPIO::AVE_SDA] && !m_gpio_out[GPIO::AVE_SDA])
{
DEBUG_LOG_FMT(WII_IPC, "AVE: Start I2C");
// SDA falling edge (now pulled low) while SCL is high indicates I²C start
i2c_state.active = true;
i2c_state.acknowledge = false;
i2c_state.bit_counter = 0;
i2c_state.read_i2c_address = false;
i2c_state.is_correct_i2c_address = false;
i2c_state.read_ave_address = false;
}
else if (!old_value[GPIO::AVE_SDA] && m_gpio_out[GPIO::AVE_SDA])
{
DEBUG_LOG_FMT(WII_IPC, "AVE: Stop I2C");
// SDA rising edge (now passive pullup) while SCL is high indicates I²C stop
i2c_state.active = false;
i2c_state.bit_counter = 0;
}
}
}
else if (!old_value[GPIO::AVE_SCL] && m_gpio_out[GPIO::AVE_SCL])
{
// Clock changed from low to high; transfer a new bit.
if (i2c_state.active && (!i2c_state.read_i2c_address || i2c_state.is_correct_i2c_address))
{
if (i2c_state.bit_counter == 9)
{
// Note: 9 not 8, as an extra clock is spent for acknowleding
i2c_state.acknowledge = false;
i2c_state.current_byte = 0;
i2c_state.bit_counter = 0;
}
// Rising edge: a new bit
if (i2c_state.bit_counter < 8)
{
i2c_state.current_byte <<= 1;
if (m_gpio_out[GPIO::AVE_SDA])
i2c_state.current_byte |= 1;
}
if (i2c_state.bit_counter == 8)
{
i2c_state.acknowledge = true;
DEBUG_LOG_FMT(WII_IPC, "AVE: New byte: {:02x}", i2c_state.current_byte);
if (!i2c_state.read_i2c_address)
{
i2c_state.read_i2c_address = true;
if ((i2c_state.current_byte >> 1) == 0x70)
{
i2c_state.is_correct_i2c_address = true;
}
else
{
WARN_LOG_FMT(WII_IPC, "AVE: Wrong I2C address: {:02x}", i2c_state.current_byte >> 1);
i2c_state.acknowledge = false;
i2c_state.is_correct_i2c_address = false;
}
if ((i2c_state.current_byte & 1) == 0)
{
i2c_state.is_read = false;
}
else
{
WARN_LOG_FMT(WII_IPC, "AVE: Reads aren't implemented yet");
i2c_state.is_read = true;
i2c_state.acknowledge = false; // until reads are implemented
}
}
else if (!i2c_state.read_ave_address)
{
i2c_state.read_ave_address = true;
i2c_state.current_address = i2c_state.current_byte;
DEBUG_LOG_FMT(WII_IPC, "AVE address: {:02x} ({})", i2c_state.current_address,
GetAVERegisterName(i2c_state.current_address));
}
else
{
// This is always inbounds, as we're indexing with a u8 and the struct is 0x100 bytes
const u8 old_ave_value = reinterpret_cast<u8*>(&ave_state)[i2c_state.current_address];
reinterpret_cast<u8*>(&ave_state)[i2c_state.current_address] = i2c_state.current_byte;
if (old_ave_value != i2c_state.current_byte)
{
INFO_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", i2c_state.current_byte,
i2c_state.current_address,
GetAVERegisterName(i2c_state.current_address));
}
else
{
DEBUG_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", i2c_state.current_byte,
i2c_state.current_address,
GetAVERegisterName(i2c_state.current_address));
}
i2c_state.current_address++;
}
}
i2c_state.bit_counter++;
}
}
}
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
// TODO: SLOT_LED
}
void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead<u32>(), MMIO::DirectWrite<u32>(&m_ppc_msg));
@ -172,16 +425,7 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
mmio->Register(base | GPIOB_OUT, MMIO::DirectRead<u32>(&m_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
auto& wii_ipc = system.GetWiiIPC();
wii_ipc.m_gpio_out.m_hex =
(val & gpio_owner.m_hex) | (wii_ipc.m_gpio_out.m_hex & ~gpio_owner.m_hex);
if (wii_ipc.m_gpio_out[GPIO::DO_EJECT])
{
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{system},
DVD::EjectCause::Software);
}
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
// TODO: AVE, SLOT_LED
wii_ipc.WriteGPIOOut(system, true, val);
}));
mmio->Register(base | GPIOB_DIR, MMIO::DirectRead<u32>(&m_gpio_dir.m_hex),
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
@ -190,9 +434,7 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
(val & gpio_owner.m_hex) | (wii_ipc.m_gpio_dir.m_hex & ~gpio_owner.m_hex);
}));
mmio->Register(base | GPIOB_IN, MMIO::ComplexRead<u32>([](Core::System& system, u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside();
return gpio_in.m_hex;
return ReadGPIOIn(system);
}),
MMIO::Nop<u32>());
// Starlet GPIO registers, not normally accessible by PPC (but they can be depending on how
@ -209,16 +451,7 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
mmio->Register(base | GPIO_OUT, MMIO::DirectRead<u32>(&m_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
auto& wii_ipc = system.GetWiiIPC();
wii_ipc.m_gpio_out.m_hex =
(wii_ipc.m_gpio_out.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex);
if (wii_ipc.m_gpio_out[GPIO::DO_EJECT])
{
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{system},
DVD::EjectCause::Software);
}
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
// TODO: AVE, SLOT_LED
wii_ipc.WriteGPIOOut(system, false, val);
}));
mmio->Register(base | GPIO_DIR, MMIO::DirectRead<u32>(&m_gpio_dir.m_hex),
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
@ -227,9 +460,7 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
(wii_ipc.m_gpio_dir.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex);
}));
mmio->Register(base | GPIO_IN, MMIO::ComplexRead<u32>([](Core::System& system, u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside();
return gpio_in.m_hex;
return ReadGPIOIn(system);
}),
MMIO::Nop<u32>());

View File

@ -140,6 +140,7 @@ private:
static void UpdateInterruptsCallback(Core::System& system, u64 userdata, s64 cycles_late);
void UpdateInterrupts();
void WriteGPIOOut(Core::System& system, bool broadway, u32 value);
u32 m_ppc_msg = 0;
u32 m_arm_msg = 0;

View File

@ -98,7 +98,7 @@ static size_t s_state_writes_in_queue;
static std::condition_variable s_state_write_queue_is_empty;
// Don't forget to increase this after doing changes on the savestate system
constexpr u32 STATE_VERSION = 169; // Last changed in PR 13074
constexpr u32 STATE_VERSION = 170; // Last changed in PR 10863
// Increase this if the StateExtendedHeader definition changes
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217