Skip to content

Commit

Permalink
Implement stub version of the Wii AV Encoder
Browse files Browse the repository at this point in the history
  • Loading branch information
Pokechu22 committed Jul 26, 2022
1 parent 9710029 commit 5a2a4f0
Show file tree
Hide file tree
Showing 2 changed files with 257 additions and 33 deletions.
288 changes: 256 additions & 32 deletions Source/Core/Core/HW/WII_IPC.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -111,6 +114,59 @@ Common::Flags<GPIO> 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<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;
static void UpdateInterrupts(u64 = 0, s64 cyclesLate = 0);

Expand All @@ -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()
Expand Down Expand Up @@ -152,6 +211,9 @@ static void InitState()
resets = 0xffffffff;

ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY;

i2c_state = {};
ave_state = {};
}

void Init()
Expand All @@ -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> 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<GPIO> 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<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 RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead<u32>(), MMIO::DirectWrite<u32>(&ppc_msg));
Expand Down Expand Up @@ -206,26 +458,12 @@ void RegisterMMIO(MMIO::Mapping* mmio, u32 base)
}));

mmio->Register(base | GPIOB_OUT, MMIO::DirectRead<u32>(&g_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](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, u32 val) { WriteGPIOOut(true, val); }));
mmio->Register(base | GPIOB_DIR, MMIO::DirectRead<u32>(&gpio_dir.m_hex),
MMIO::ComplexWrite<u32>([](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>([](u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside();
return gpio_in.m_hex;
}),
mmio->Register(base | GPIOB_IN, MMIO::ComplexRead<u32>([](u32) { return ReadGPIOIn(); }),
MMIO::Nop<u32>());
// 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.
Expand All @@ -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<u32>(&g_gpio_out.m_hex),
MMIO::ComplexWrite<u32>([](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, u32 val) { WriteGPIOOut(false, val); }));
mmio->Register(base | GPIO_DIR, MMIO::DirectRead<u32>(&gpio_dir.m_hex),
MMIO::ComplexWrite<u32>([](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>([](u32) {
Common::Flags<GPIO> gpio_in;
gpio_in[GPIO::SLOT_IN] = DVDInterface::IsDiscInside();
return gpio_in.m_hex;
}),
mmio->Register(base | GPIO_IN, MMIO::ComplexRead<u32>([](u32) { return ReadGPIOIn(); }),
MMIO::Nop<u32>());

mmio->Register(base | HW_RESETS, MMIO::DirectRead<u32>(&resets),
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Core/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 5a2a4f0

Please sign in to comment.