From 5a2a4f002da989f825dcaa13bd24ced0c57de4fa 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 | 288 ++++++++++++++++++++++++++++---- Source/Core/Core/State.cpp | 2 +- 2 files changed, 257 insertions(+), 33 deletions(-) diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index e388de87fe76..e2430d25e143 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" @@ -111,6 +114,59 @@ Common::Flags g_gpio_out; 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; static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0); @@ -124,6 +180,9 @@ void DoState(PointerWrap& p) p.Do(arm_irq_flags); p.Do(arm_irq_masks); p.Do(g_gpio_out); + p.Do(gpio_dir); + p.Do(i2c_state); + p.Do(ave_state); } static void InitState() @@ -152,6 +211,9 @@ static void InitState() resets = 0xffffffff; ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY; + + i2c_state = {}; + ave_state = {}; } void Init() @@ -170,6 +232,196 @@ void 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() +{ + Common::Flags gpio_in; + gpio_in[GPIO::SLOT_IN] = DVDInterface::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; +} + +static void WriteGPIOOut(bool broadway, u32 value) +{ + Common::Flags old_value = g_gpio_out; + + if (broadway) + g_gpio_out.m_hex = (value & gpio_owner.m_hex) | (g_gpio_out.m_hex & ~gpio_owner.m_hex); + else + g_gpio_out.m_hex = (value & ~gpio_owner.m_hex) | (g_gpio_out.m_hex & gpio_owner.m_hex); + + if (g_gpio_out[GPIO::DO_EJECT]) + { + INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); + DVDInterface::EjectDisc(DVDInterface::EjectCause::Software); + } + + // I²C logic for the audio/video encoder (AVE) + if (gpio_dir[GPIO::AVE_SCL]) + { + if (old_value[GPIO::AVE_SCL] && g_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 (gpio_dir[GPIO::AVE_SDA]) + { + if (old_value[GPIO::AVE_SDA] && !g_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] && g_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] && g_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 (g_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 RegisterMMIO(MMIO::Mapping* mmio, u32 base) { mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead(), MMIO::DirectWrite(&ppc_msg)); @@ -206,26 +458,12 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) })); mmio->Register(base | GPIOB_OUT, MMIO::DirectRead(&g_gpio_out.m_hex), - MMIO::ComplexWrite([](u32, u32 val) { - g_gpio_out.m_hex = - (val & gpio_owner.m_hex) | (g_gpio_out.m_hex & ~gpio_owner.m_hex); - if (g_gpio_out[GPIO::DO_EJECT]) - { - INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); - DVDInterface::EjectDisc(DVDInterface::EjectCause::Software); - } - // SENSOR_BAR is checked by WiimoteEmu::CameraLogic - // TODO: AVE, SLOT_LED - })); + MMIO::ComplexWrite([](u32, u32 val) { WriteGPIOOut(true, val); })); mmio->Register(base | GPIOB_DIR, MMIO::DirectRead(&gpio_dir.m_hex), MMIO::ComplexWrite([](u32, u32 val) { gpio_dir.m_hex = (val & gpio_owner.m_hex) | (gpio_dir.m_hex & ~gpio_owner.m_hex); })); - mmio->Register(base | GPIOB_IN, MMIO::ComplexRead([](u32) { - Common::Flags gpio_in; - gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside(); - return gpio_in.m_hex; - }), + mmio->Register(base | GPIOB_IN, MMIO::ComplexRead([](u32) { return ReadGPIOIn(); }), MMIO::Nop()); // Starlet GPIO registers, not normally accessible by PPC (but they can be depending on how // AHBPROT is set up). We just always allow access, since some homebrew uses them. @@ -239,26 +477,12 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base) // go through the HW_GPIOB registers if the corresponding bit is set in the HW_GPIO_OWNER // register. mmio->Register(base | GPIO_OUT, MMIO::DirectRead(&g_gpio_out.m_hex), - MMIO::ComplexWrite([](u32, u32 val) { - g_gpio_out.m_hex = - (g_gpio_out.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex); - if (g_gpio_out[GPIO::DO_EJECT]) - { - INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); - DVDInterface::EjectDisc(DVDInterface::EjectCause::Software); - } - // SENSOR_BAR is checked by WiimoteEmu::CameraLogic - // TODO: AVE, SLOT_LED - })); + MMIO::ComplexWrite([](u32, u32 val) { WriteGPIOOut(false, val); })); mmio->Register(base | GPIO_DIR, MMIO::DirectRead(&gpio_dir.m_hex), MMIO::ComplexWrite([](u32, u32 val) { gpio_dir.m_hex = (gpio_dir.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex); })); - mmio->Register(base | GPIO_IN, MMIO::ComplexRead([](u32) { - Common::Flags gpio_in; - gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside(); - return gpio_in.m_hex; - }), + mmio->Register(base | GPIO_IN, MMIO::ComplexRead([](u32) { return ReadGPIOIn(); }), MMIO::Nop()); mmio->Register(base | HW_RESETS, MMIO::DirectRead(&resets), diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index bccb56a835f8..dbfd7312adf7 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static std::recursive_mutex g_save_thread_mutex; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 145; // Last changed in PR 10879 +constexpr u32 STATE_VERSION = 146; // Last changed in PR 10863 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list,