From a84cd150231c29f63c27303ab56e59279cfc7ed6 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 16 Jul 2022 00:05:03 -0700 Subject: [PATCH] Implement stub version of the Wii AV Encoder --- Source/Core/Core/HW/WII_IPC.cpp | 283 +++++++++++++++++++++++++++++--- Source/Core/Core/HW/WII_IPC.h | 1 + Source/Core/Core/State.cpp | 2 +- 3 files changed, 259 insertions(+), 27 deletions(-) diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index 8ce63d7c1f..1e53bdff11 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -3,6 +3,9 @@ #include "Core/HW/WII_IPC.h" +#include +#include + #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -60,6 +63,63 @@ enum static constexpr Common::Flags 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 pad2; // 0x0B-0x0F + std::array gamma_coefficients; // 0x10-0x30 + std::array pad3; // 0x31-0x3F + std::array macrovision_code; // 0x40-0x59, analog copy protection + std::array pad4; // 0x5A-0x61 + u8 rgb_switch; // 0x62, swap blue and red channels + std::array pad5; // 0x63-0x64 + u8 color_dac; // 0x65 + u8 pad6; // 0x66 + u8 color_test; // 0x67, display a color test pattern + std::array pad7; // 0x68-0x69 + u8 ccsel; // 0x6A + std::array pad8; // 0x6B-0x6C + u8 mute; // 0x6D + u8 rgb_output_filter; // 0x6E + std::array pad9; // 0x6F-0x70 + u8 right_volume; // 0x71 + u8 left_volume; // 0x72 + std::array pad10; // 0x73-0x79 + std::array closed_captioning; // 0x7A-0x7D + std::array 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_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 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(&ave_state)[i2c_state.current_address]; + reinterpret_cast(&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(), MMIO::DirectWrite(&m_ppc_msg)); @@ -172,16 +425,7 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base) mmio->Register(base | GPIOB_OUT, MMIO::DirectRead(&m_gpio_out.m_hex), MMIO::ComplexWrite([](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(&m_gpio_dir.m_hex), MMIO::ComplexWrite([](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([](Core::System& system, u32) { - Common::Flags gpio_in; - gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside(); - return gpio_in.m_hex; + return ReadGPIOIn(system); }), MMIO::Nop()); // 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(&m_gpio_out.m_hex), MMIO::ComplexWrite([](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(&m_gpio_dir.m_hex), MMIO::ComplexWrite([](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([](Core::System& system, u32) { - Common::Flags gpio_in; - gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside(); - return gpio_in.m_hex; + return ReadGPIOIn(system); }), MMIO::Nop()); diff --git a/Source/Core/Core/HW/WII_IPC.h b/Source/Core/Core/HW/WII_IPC.h index 64144f43c0..053280b933 100644 --- a/Source/Core/Core/HW/WII_IPC.h +++ b/Source/Core/Core/HW/WII_IPC.h @@ -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; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index f9964c0fa8..daf41f4362 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -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