diff --git a/Source/Core/Common/BitUtils.h b/Source/Core/Common/BitUtils.h index 8b1b196c249d..fefc37b730b4 100644 --- a/Source/Core/Common/BitUtils.h +++ b/Source/Core/Common/BitUtils.h @@ -218,6 +218,12 @@ class Flags { public: constexpr Flags() = default; + constexpr ~Flags() = default; + constexpr Flags(const Flags& other) = default; + constexpr Flags(Flags&& other) noexcept = default; + constexpr Flags& operator=(const Flags& other) = default; + constexpr Flags& operator=(Flags&& other) noexcept = default; + constexpr Flags(std::initializer_list bits) { for (auto bit : bits) @@ -225,7 +231,13 @@ class Flags m_hex |= static_cast>(bit); } } + constexpr Flags(std::underlying_type_t hex) : m_hex(hex) {} + FlagBit operator[](T bit) { return FlagBit(m_hex, bit); } + constexpr bool operator[](T bit) const + { + return (m_hex & static_cast>(bit)) != 0; + } std::underlying_type_t m_hex = 0; }; diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index 81431e564cef..048fffe2d107 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -79,6 +79,8 @@ add_library(common HostDisassembler.h HttpRequest.cpp HttpRequest.h + I2C.cpp + I2C.h Image.cpp Image.h IniFile.cpp diff --git a/Source/Core/Common/I2C.cpp b/Source/Core/Common/I2C.cpp new file mode 100644 index 000000000000..c8acb3511c78 --- /dev/null +++ b/Source/Core/Common/I2C.cpp @@ -0,0 +1,521 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/I2C.h" + +#include +#include +#include + +#include + +#include "Common/Assert.h" +#include "Common/ChunkFile.h" +#include "Common/EnumFormatter.h" +#include "Common/Logging/Log.h" +#include "Core/Debugger/Debugger_SymbolMap.h" + +namespace Common +{ +void I2CBusBase::AddSlave(I2CSlave* slave) +{ + m_slaves.emplace_back(slave); +} + +void I2CBusBase::RemoveSlave(I2CSlave* slave) +{ + m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end()); +} + +void I2CBusBase::Reset() +{ + m_slaves.clear(); +} + +bool I2CBusBase::StartWrite(u8 slave_addr) +{ + bool got_ack = false; + for (I2CSlave* slave : m_slaves) + { + if (slave->StartWrite(slave_addr)) + { + if (got_ack) + { + WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed starting write for I2C addr {:02x}", + slave_addr); + } + got_ack = true; + } + } + return got_ack; +} + +bool I2CBusBase::StartRead(u8 slave_addr) +{ + bool got_ack = false; + for (I2CSlave* slave : m_slaves) + { + if (slave->StartRead(slave_addr)) + { + if (got_ack) + { + WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed starting read for I2C addr {:02x}", + slave_addr); + } + got_ack = true; + } + } + return got_ack; +} + +void I2CBusBase::Stop() +{ + for (I2CSlave* slave : m_slaves) + slave->Stop(); +} + +std::optional I2CBusBase::ReadByte() +{ + std::optional byte = std::nullopt; + for (I2CSlave* slave : m_slaves) + { + std::optional byte2 = slave->ReadByte(); + if (byte.has_value() && byte2.has_value()) + { + WARN_LOG_FMT(WII_IPC, "Multiple slaves responded to read: {:02x} vs {:02x}", *byte, *byte2); + } + else if (byte2.has_value()) + { + INFO_LOG_FMT(WII_IPC, "I2C: Read {:02x}", byte2.value()); + byte = byte2; + } + } + return byte; +} + +bool I2CBusBase::WriteByte(u8 value) +{ + bool got_ack = false; + for (I2CSlave* slave : m_slaves) + { + if (slave->WriteByte(value)) + { + if (got_ack) + { + WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed write of {:02x}", value); + } + got_ack = true; + } + } + return got_ack; +} + +int I2CBusSimple::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +{ + if (!StartWrite(slave_addr)) + { + WARN_LOG_FMT(WII_IPC, "I2C: Failed to start write to {:02x} ({:02x}, {:02x})", slave_addr, addr, + count); + Stop(); + return 0; + } + if (!WriteByte(addr)) + { + WARN_LOG_FMT(WII_IPC, "I2C: Failed to write device address {:02x} to {:02x} ({:02x})", addr, + slave_addr, count); + Stop(); + return 0; + } + for (int i = 0; i < count; i++) + { + if (!WriteByte(data_in[i])) + { + WARN_LOG_FMT(WII_IPC, + "I2C: Failed to byte {} of {} starting at device address {:02x} to {:02x}", i, + count, addr, slave_addr); + Stop(); + return i; + } + } + Stop(); + return count; +} + +int I2CBusSimple::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +{ + if (!StartWrite(slave_addr)) + { + WARN_LOG_FMT(WII_IPC, "I2C: Failed to start write for read from {:02x} ({:02x}, {:02x})", + slave_addr, addr, count); + Stop(); + return 0; + } + if (!WriteByte(addr)) + { + WARN_LOG_FMT(WII_IPC, "I2C: Failed to write device address {:02x} to {:02x} ({:02x})", addr, + slave_addr, count); + Stop(); + return 0; + } + // Note: No Stop() call before StartRead. + if (!StartRead(slave_addr)) + { + WARN_LOG_FMT(WII_IPC, "I2C: Failed to start read from {:02x} ({:02x}, {:02x})", slave_addr, + addr, count); + Stop(); + return 0; + } + for (int i = 0; i < count; i++) + { + const std::optional byte = ReadByte(); + if (!byte.has_value()) + { + WARN_LOG_FMT(WII_IPC, + "I2C: Failed to byte {} of {} starting at device address {:02x} from {:02x}", i, + count, addr, slave_addr); + Stop(); + return i; + } + data_out[i] = byte.value(); + } + Stop(); + return count; +} + +bool I2CBus::GetSCL() const +{ + return true; // passive pullup - no clock stretching +} + +bool I2CBus::GetSDA() const +{ + switch (state) + { + case State::Inactive: + case State::Activating: + default: + return true; // passive pullup (or NACK) + + case State::SetI2CAddress: + case State::WriteToDevice: + if (bit_counter < 8) + return true; // passive pullup + else + return false; // ACK (if we need to NACK, we set the state to inactive) + + case State::ReadFromDevice: + if (bit_counter < 8) + return ((current_byte << bit_counter) & 0x80) != 0; + else + return true; // passive pullup, receiver needs to ACK or NACK + } +} + +void I2CBus::Start() +{ + if (state != State::Inactive) + INFO_LOG_FMT(WII_IPC, "AVE: Re-start I2C"); + else + INFO_LOG_FMT(WII_IPC, "AVE: Start I2C"); + + if (bit_counter != 0) + WARN_LOG_FMT(WII_IPC, "I2C: Start happened with a nonzero bit counter: {}", bit_counter); + + state = State::Activating; + bit_counter = 0; + current_byte = 0; + // Note: don't reset device_address, as it's re-used for reads +} + +void I2CBus::Stop() +{ + INFO_LOG_FMT(WII_IPC, "AVE: Stop I2C"); + I2CBusBase::Stop(); + + state = State::Inactive; + bit_counter = 0; + current_byte = 0; +} + +void I2CBus::Update(const bool old_scl, const bool new_scl, const bool old_sda, const bool new_sda) +{ + if (old_scl != new_scl && old_sda != new_sda) + { + ERROR_LOG_FMT(WII_IPC, "Both SCL and SDA changed at the same time: SCL {} -> {} SDA {} -> {}", + old_scl, new_scl, old_sda, new_sda); + return; + } + + if (old_scl == new_scl && old_sda == new_sda) + return; // Nothing changed + + if (old_scl && new_scl) + { + // Check for changes to SDA while the clock is high. + if (old_sda && !new_sda) + { + // SDA falling edge (now pulled low) while SCL is high indicates I²C start + Start(); + } + else if (!old_sda && new_sda) + { + // SDA rising edge (now passive pullup) while SCL is high indicates I²C stop + Stop(); + } + } + else if (state != State::Inactive) + { + if (!old_scl && new_scl) + { + SCLRisingEdge(new_sda); + } + else if (old_scl && !new_scl) + { + SCLFallingEdge(new_sda); + } + } +} + +void I2CBus::SCLRisingEdge(const bool sda) +{ + // INFO_LOG_FMT(WII_IPC, "AVE: {} {} rising edge: {}", bit_counter, state, sda); + // SCL rising edge indicates data clocking. For reads, we set up data at this point. + // For writes, we instead process it on the falling edge, to better distinguish + // the start/stop condition. + if (state == State::ReadFromDevice && bit_counter == 0) + { + // Start of a read. + const std::optional byte = ReadByte(); + if (!byte.has_value()) + { + WARN_LOG_FMT(WII_IPC, "No slaves responded to I2C read"); + current_byte = 0xff; + } + else + { + current_byte = byte.value(); + } + } + // Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO); +} + +void I2CBus::SCLFallingEdge(const bool sda) +{ + // INFO_LOG_FMT(WII_IPC, "AVE: {} {} falling edge: {}", bit_counter, state, sda); + // SCL falling edge is used to advance bit_counter/change states and process writes. + if (state == State::SetI2CAddress || state == State::WriteToDevice) + { + if (bit_counter != 8) + { + current_byte <<= 1; + if (sda) + current_byte |= 1; + + if (bit_counter == 7) + { + INFO_LOG_FMT(WII_IPC, "AVE: Byte written: {:02x}", current_byte); + // Write finished. + if (state == State::SetI2CAddress) + { + const u8 slave_addr = current_byte >> 1; + if (current_byte & 1) + { + if (StartRead(slave_addr)) + { + // State transition handled by bit_counter >= 8, as we still need to handle the ACK + INFO_LOG_FMT(WII_IPC, "I2C: Start read from {:02x}", slave_addr); + } + else + { + state = State::Inactive; // NACK + WARN_LOG_FMT(WII_IPC, "I2C: No device responded to read from {:02x}", slave_addr); + } + } + else + { + if (StartWrite(slave_addr)) + { + // State transition handled by bit_counter >= 8, as we still need to handle the ACK + INFO_LOG_FMT(WII_IPC, "I2C: Start write to {:02x}", slave_addr); + } + else + { + state = State::Inactive; // NACK + WARN_LOG_FMT(WII_IPC, "I2C: No device responded to write to {:02x}", slave_addr); + } + } + } + else + { + // Actual write + ASSERT(state == State::WriteToDevice); + if (!WriteByte(current_byte)) + { + state = State::Inactive; + WARN_LOG_FMT(WII_IPC, "I2C: Write of {:02x} NACKed", current_byte); + } + } + } + } + } + + if (state == State::Activating) + { + // This is triggered after the start condition. + state = State::SetI2CAddress; + bit_counter = 0; + } + else if (state != State::Inactive) + { + if (bit_counter >= 8) + { + // Finished a byte and the acknowledge signal. + bit_counter = 0; + if (state == State::SetI2CAddress) + { + // Note: current_byte is known to correspond to a valid device + if ((current_byte & 1) == 0) + { + state = State::WriteToDevice; + } + else + { + state = State::ReadFromDevice; + } + } + else if (state == State::ReadFromDevice) + { + // Acknowledge bit for *reads*. + if (sda) + { + WARN_LOG_FMT(WII_IPC, "Read NACK'd"); + state = State::Inactive; + } + } + } + else + { + bit_counter++; + } + } + // Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO); +} + +void I2CBus::DoState(PointerWrap& p) +{ + p.Do(state); + p.Do(bit_counter); + p.Do(current_byte); + // TODO: verify m_devices is the same so that the state is compatible. + // (We don't take ownership of saving/loading m_devices, though). +} + +bool I2CSlaveAutoIncrementing::StartWrite(u8 slave_addr) +{ + if (DeviceEnabled() && slave_addr == m_slave_addr) + { + INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} write started, previously active: {}", m_slave_addr, + m_active); + m_active = true; + m_device_address.reset(); + return true; + } + else + { + return false; + } +} + +bool I2CSlaveAutoIncrementing::StartRead(u8 slave_addr) +{ + if (DeviceEnabled() && slave_addr == m_slave_addr) + { + INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} read started, previously active: {}", m_slave_addr, + m_active); + if (m_device_address.has_value()) + { + m_active = true; + return true; + } + else + { + WARN_LOG_FMT(WII_IPC, + "I2C Device {:02x}: read attempted without having written device address", + m_slave_addr); + m_active = false; + return false; + } + } + else + { + return false; + } +} + +void I2CSlaveAutoIncrementing::Stop() +{ + m_active = false; + m_device_address.reset(); +} + +std::optional I2CSlaveAutoIncrementing::ReadByte() +{ + if (m_active) + { + ASSERT(m_device_address.has_value()); // enforced by StartRead + const u8 cur_addr = m_device_address.value(); + m_device_address = cur_addr + 1; // wrapping from 255 to 0 is the assumed behavior + return ReadByte(cur_addr); + } + else + { + return std::nullopt; + } +} + +bool I2CSlaveAutoIncrementing::WriteByte(u8 value) +{ + if (m_active) + { + if (m_device_address.has_value()) + { + const u8 cur_addr = m_device_address.value(); + m_device_address = cur_addr + 1; // wrapping from 255 to 0 is the assumed behavior + WriteByte(cur_addr, value); + } + else + { + m_device_address = value; + } + return true; + } + else + { + return false; + } +} + +void I2CSlaveAutoIncrementing::DoState(PointerWrap& p) +{ + u8 slave_addr = m_slave_addr; + p.Do(slave_addr); + if (slave_addr != m_slave_addr && p.IsReadMode()) + { + PanicAlertFmt("State incompatible: Mismatched I2C address: expected {:02x}, was {:02x}. " + "Aborting state load.", + m_slave_addr, slave_addr); + p.SetVerifyMode(); + } + p.Do(m_active); + p.Do(m_device_address); +} + +}; // namespace Common + +template <> +struct fmt::formatter : EnumFormatter +{ + static constexpr array_type names = {"Inactive", "Activating", "Set I2C Address", + "Write To Device", "Read From Device"}; + constexpr formatter() : EnumFormatter(names) {} +}; diff --git a/Source/Core/Common/I2C.h b/Source/Core/Common/I2C.h new file mode 100644 index 000000000000..e2bb96a06b76 --- /dev/null +++ b/Source/Core/Common/I2C.h @@ -0,0 +1,154 @@ +// Copyright 2022 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" + +class PointerWrap; + +namespace Common +{ +class I2CSlave +{ +public: + virtual ~I2CSlave() = default; + + // NOTE: slave_addr is 7 bits, i.e. it has been shifted so the read flag is not included. + virtual bool StartWrite(u8 slave_addr) = 0; + virtual bool StartRead(u8 slave_addr) = 0; + virtual void Stop() = 0; + // NOTE: std::optional is for ease of implementation. I2C does not provide a way for the + // transmitting device to NACK a read; only the receiver can NACK. + virtual std::optional ReadByte() = 0; + virtual bool WriteByte(u8 value) = 0; + +protected: + template + static u8 RawRead(T* reg_data, u8 addr) + { + static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); + static_assert(0x100 == sizeof(T)); + + return reinterpret_cast(reg_data)[addr]; + } + + template + static void RawWrite(T* reg_data, u8 addr, u8 value) + { + static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); + static_assert(0x100 == sizeof(T)); + + reinterpret_cast(reg_data)[addr] = value; + } +}; + +class I2CSlaveAutoIncrementing : public virtual I2CSlave +{ +public: + I2CSlaveAutoIncrementing(u8 slave_addr) : m_slave_addr(slave_addr) {} + virtual ~I2CSlaveAutoIncrementing() = default; + + bool StartWrite(u8 slave_addr) override; + bool StartRead(u8 slave_addr) override; + void Stop() override; + std::optional ReadByte() override; + bool WriteByte(u8 value) override; + + virtual void DoState(PointerWrap& p); + +protected: + // i.e. should the device respond on the bus + virtual bool DeviceEnabled() { return true; } + virtual u8 ReadByte(u8 addr) = 0; + virtual void WriteByte(u8 addr, u8 value) = 0; + +private: + const u8 m_slave_addr; + bool m_active = false; + std::optional m_device_address = std::nullopt; +}; + +class I2CBusBase +{ +public: + virtual ~I2CBusBase() = default; + + void AddSlave(I2CSlave* slave); + void RemoveSlave(I2CSlave* slave); + + void Reset(); + +protected: + // Signals all slaves on the bus + bool StartWrite(u8 slave_addr); + bool StartRead(u8 slave_addr); + void Stop(); + std::optional ReadByte(); + bool WriteByte(u8 value); + +private: + // Pointers are unowned: + std::vector m_slaves; +}; + +class I2CBusSimple : public I2CBusBase +{ +public: + // TODO: change int to u16 or something + int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in); + int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out); +}; + +class I2CBusForwarding : public I2CBusSimple +{ +public: + using I2CBusBase::ReadByte; + using I2CBusBase::StartRead; + using I2CBusBase::StartWrite; + using I2CBusBase::Stop; + using I2CBusBase::WriteByte; +}; + +// An I²C bus implementation accessed via bit-banging. +// A few assumptions and limitations exist: +// - All devices support both writes and reads. +// - Timing is not implemented at all; the clock signal can be changed as fast as needed. +// - Devices are not allowed to stretch the clock signal. (Nintendo's write code does not seem to +// implement clock stretching in any case, though some homebrew does.) +// - 10-bit addressing and other reserved addressing modes are not implemented. +class I2CBus : public I2CBusBase +{ +public: + enum class State + { + Inactive, + Activating, + SetI2CAddress, + WriteToDevice, + ReadFromDevice, + }; + + State state; + u8 bit_counter; + u8 current_byte; + + void Update(const bool old_scl, const bool new_scl, const bool old_sda, const bool new_sda); + bool GetSCL() const; + bool GetSDA() const; + + void DoState(PointerWrap& p); + +private: + void Start(); + void Stop(); + void SCLRisingEdge(const bool sda); + void SCLFallingEdge(const bool sda); + bool WriteExpected() const; +}; + +} // namespace Common diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index 8ce63d7c1f73..15e0c25bd9f6 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -3,8 +3,13 @@ #include "Core/HW/WII_IPC.h" +#include +#include +#include + #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/I2C.h" #include "Common/Logging/Log.h" #include "Core/Core.h" #include "Core/CoreTiming.h" @@ -33,7 +38,8 @@ enum IPC_ARMMSG = 0x08, IPC_ARMCTRL = 0x0c, - PPCSPEED = 0x18, + VI1CFG = 0x18, + VIDIM = 0x1c, VISOLID = 0x24, PPC_IRQFLAG = 0x30, @@ -41,17 +47,31 @@ enum ARM_IRQFLAG = 0x38, ARM_IRQMASK = 0x3c, + SRNPROT = 0x60, + AHBPROT = 0x64, + + // Broadway GPIO access. We don't currently implement the interrupts. GPIOB_OUT = 0xc0, GPIOB_DIR = 0xc4, GPIOB_IN = 0xc8, - + GPIOB_INTLVL = 0xcc, + GPIOB_INTFLAG = 0xd0, + GPIOB_INTMASK = 0xd4, + GPIOB_STRAPS = 0xd8, + // Starlet GPIO access. We emulate some of these for /dev/di. + GPIO_ENABLE = 0xdc, GPIO_OUT = 0xe0, GPIO_DIR = 0xe4, GPIO_IN = 0xe8, + GPIO_INTLVL = 0xec, + GPIO_INTFLAG = 0xf0, + GPIO_INTMASK = 0xf4, + GPIO_STRAPS = 0xf8, + GPIO_OWNER = 0xfc, - HW_RESETS = 0x194, + COMPAT = 0x180, + RESETS = 0x194, - UNK_180 = 0x180, UNK_1CC = 0x1cc, UNK_1D0 = 0x1d0, }; @@ -60,6 +80,143 @@ 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; + +#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); + +std::string 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 - left volume"; + else if (address >= 0x7a && address <= 0x7d) + return "Closed Captioning control"; + else + return fmt::format("Unknown ({:02x})", address); +} + +class AVEDevice : public Common::I2CSlaveAutoIncrementing +{ +public: + AVEDevice() : I2CSlaveAutoIncrementing(0x70) {} + + void Reset() + { + m_registers = {}; + ave_ever_logged = {}; + } + + void DoState(PointerWrap& p) override + { + I2CSlaveAutoIncrementing::DoState(p); + p.Do(m_registers); + } + + AVEState m_registers{}; + +protected: + u8 ReadByte(u8 addr) override + { + const u8 result = RawRead(&m_registers, addr); + INFO_LOG_FMT(WII_IPC, "AVE: Read from {:02x} ({}) -> {:02x}", addr, GetAVERegisterName(addr), + result); + return result; + } + void WriteByte(u8 addr, u8 value) override + { + const u8 old_value = RawRead(&m_registers, addr); + RawWrite(&m_registers, addr, value); + + if (old_value != value || !ave_ever_logged[addr]) + { + INFO_LOG_FMT(WII_IPC, "AVE: Write to {:02x} ({}): {:02x} -> {:02x}", addr, + GetAVERegisterName(addr), old_value, value); + ave_ever_logged[addr] = true; + } + else + { + DEBUG_LOG_FMT(WII_IPC, "AVE: Write to {:02x} ({}): {:02x}", addr, GetAVERegisterName(addr), + value); + } + } + +private: + std::bitset ave_ever_logged{}; // logging only, not saved +}; +AVEDevice ave_state; + +Common::I2CBus i2c_state; + WiiIPC::WiiIPC(Core::System& system) : m_system(system) { } @@ -77,6 +234,8 @@ void WiiIPC::DoState(PointerWrap& p) p.Do(m_arm_irq_masks); p.Do(m_gpio_dir); p.Do(m_gpio_out); + i2c_state.DoState(p); + ave_state.DoState(p); p.Do(m_resets); } @@ -91,8 +250,8 @@ void WiiIPC::InitState() m_arm_irq_flags = 0; m_arm_irq_masks = 0; - // The only inputs are POWER, EJECT_BTN, SLOT_IN, and EEP_MISO; Broadway only has access to - // SLOT_IN + // The only inputs are POWER, EJECT_BTN, SLOT_IN, EEP_MISO, and sometimes AVE_SCL and AVE_SDA; + // Broadway only has access to SLOT_IN, AVE_SCL, and AVE_SDA. m_gpio_dir = { GPIO::POWER, GPIO::SHUTDOWN, GPIO::FAN, GPIO::DC_DC, GPIO::DI_SPIN, GPIO::SLOT_LED, GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::EEP_CS, GPIO::EEP_CLK, GPIO::EEP_MOSI, GPIO::AVE_SCL, @@ -106,6 +265,12 @@ void WiiIPC::InitState() m_resets = 0xffffffff; m_ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY; + + ave_state.Reset(); + i2c_state = {}; + ave_state.Reset(); + ave_state.m_registers.video_output_config = 0x23; + i2c_state.AddSlave(&ave_state); } void WiiIPC::Init() @@ -125,6 +290,44 @@ void WiiIPC::Shutdown() { } +u32 WiiIPC::ReadGPIOIn() +{ + Common::Flags gpio_in; + gpio_in[GPIO::SLOT_IN] = m_system.GetDVDInterface().IsDiscInside(); + gpio_in[GPIO::AVE_SCL] = i2c_state.GetSCL(); + gpio_in[GPIO::AVE_SDA] = i2c_state.GetSDA(); + return gpio_in.m_hex; +} + +u32 WiiIPC::GetGPIOOut() +{ + // In the direction field, a '1' bit for a pin indicates that it will behave as an output (drive), + // while a '0' bit tristates the pin and it becomes a high-impedance input. + // In practice this means that (at least for the AVE I²C pins) a 1 is output when the pin is an + // input. (RVLoader depends on this.) + // https://github.com/Aurelio92/RVLoader/blob/75732f248019f589deb1109bba7b5323a8afaadf/source/i2c.c#L101-L109 + return (m_gpio_out.m_hex | ~(m_gpio_dir.m_hex)) & (ReadGPIOIn() | m_gpio_dir.m_hex); +} + +void WiiIPC::GPIOOutChanged(u32 old_value_hex) +{ + const Common::Flags old_value(old_value_hex); + const Common::Flags new_value(GetGPIOOut()); + + if (new_value[GPIO::DO_EJECT]) + { + INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write"); + m_system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{m_system}, DVD::EjectCause::Software); + } + + // I²C logic for the audio/video encoder (AVE) + i2c_state.Update(old_value[GPIO::AVE_SCL], new_value[GPIO::AVE_SCL], old_value[GPIO::AVE_SDA], + new_value[GPIO::AVE_SDA]); + + // 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,27 +375,22 @@ 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(); + const u32 old_out = wii_ipc.GetGPIOOut(); 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.GPIOOutChanged(old_out); })); mmio->Register(base | GPIOB_DIR, MMIO::DirectRead(&m_gpio_dir.m_hex), MMIO::ComplexWrite([](Core::System& system, u32, u32 val) { auto& wii_ipc = system.GetWiiIPC(); + const u32 old_out = wii_ipc.GetGPIOOut(); wii_ipc.m_gpio_dir.m_hex = (val & gpio_owner.m_hex) | (wii_ipc.m_gpio_dir.m_hex & ~gpio_owner.m_hex); + wii_ipc.GPIOOutChanged(old_out); })); 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; + auto& wii_ipc = system.GetWiiIPC(); + return wii_ipc.ReadGPIOIn(); }), MMIO::Nop()); // Starlet GPIO registers, not normally accessible by PPC (but they can be depending on how @@ -209,31 +407,26 @@ 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(); + const u32 old_out = wii_ipc.GetGPIOOut(); 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 + (val & ~gpio_owner.m_hex) | (wii_ipc.m_gpio_out.m_hex & gpio_owner.m_hex); + wii_ipc.GPIOOutChanged(old_out); })); mmio->Register(base | GPIO_DIR, MMIO::DirectRead(&m_gpio_dir.m_hex), MMIO::ComplexWrite([](Core::System& system, u32, u32 val) { auto& wii_ipc = system.GetWiiIPC(); + const u32 old_out = wii_ipc.GetGPIOOut(); wii_ipc.m_gpio_dir.m_hex = - (wii_ipc.m_gpio_dir.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex); + (val & gpio_owner.m_hex) | (wii_ipc.m_gpio_dir.m_hex & ~gpio_owner.m_hex); + wii_ipc.GPIOOutChanged(old_out); })); 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; + auto& wii_ipc = system.GetWiiIPC(); + return wii_ipc.ReadGPIOIn(); }), MMIO::Nop()); - mmio->Register(base | HW_RESETS, MMIO::DirectRead(&m_resets), + mmio->Register(base | RESETS, MMIO::DirectRead(&m_resets), MMIO::ComplexWrite([](Core::System& system, u32, u32 val) { // A reset occurs when the corresponding bit is cleared auto& wii_ipc = system.GetWiiIPC(); @@ -249,9 +442,10 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base) })); // Register some stubbed/unknown MMIOs required to make Wii games work. - mmio->Register(base | PPCSPEED, MMIO::InvalidRead(), MMIO::Nop()); - mmio->Register(base | VISOLID, MMIO::InvalidRead(), MMIO::Nop()); - mmio->Register(base | UNK_180, MMIO::Constant(0), MMIO::Nop()); + mmio->Register(base | VI1CFG, MMIO::InvalidRead(), MMIO::InvalidWrite()); + mmio->Register(base | VIDIM, MMIO::InvalidRead(), MMIO::InvalidWrite()); + mmio->Register(base | VISOLID, MMIO::InvalidRead(), MMIO::InvalidWrite()); + mmio->Register(base | COMPAT, MMIO::Constant(0), MMIO::Nop()); mmio->Register(base | UNK_1CC, MMIO::Constant(0), MMIO::Nop()); mmio->Register(base | UNK_1D0, MMIO::Constant(0), MMIO::Nop()); } diff --git a/Source/Core/Core/HW/WII_IPC.h b/Source/Core/Core/HW/WII_IPC.h index 64144f43c069..23a67e0eb558 100644 --- a/Source/Core/Core/HW/WII_IPC.h +++ b/Source/Core/Core/HW/WII_IPC.h @@ -140,6 +140,9 @@ class WiiIPC static void UpdateInterruptsCallback(Core::System& system, u64 userdata, s64 cycles_late); void UpdateInterrupts(); + u32 ReadGPIOIn(); + u32 GetGPIOOut(); + void GPIOOutChanged(u32 old_value_hex); u32 m_ppc_msg = 0; u32 m_arm_msg = 0; diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp index 1dcdb7795200..14efaed6b831 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp @@ -24,31 +24,26 @@ void CameraLogic::Reset() void CameraLogic::DoState(PointerWrap& p) { + I2CSlaveAutoIncrementing::DoState(p); + p.Do(m_reg_data); // FYI: m_is_enabled is handled elsewhere. } -int CameraLogic::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +bool CameraLogic::DeviceEnabled() { - if (I2C_ADDR != slave_addr) - return 0; - - if (!m_is_enabled) - return 0; - - return RawRead(&m_reg_data, addr, count, data_out); + return m_is_enabled; } -int CameraLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +u8 CameraLogic::ReadByte(u8 addr) { - if (I2C_ADDR != slave_addr) - return 0; - - if (!m_is_enabled) - return 0; + return RawRead(&m_reg_data, addr); +} - return RawWrite(&m_reg_data, addr, count, data_in); +void CameraLogic::WriteByte(u8 addr, u8 value) +{ + RawWrite(&m_reg_data, addr, value); } std::array diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.h b/Source/Core/Core/HW/WiimoteEmu/Camera.h index 23e21cc84e43..6942cf12d2df 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Camera.h +++ b/Source/Core/Core/HW/WiimoteEmu/Camera.h @@ -5,8 +5,8 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/I2C.h" #include "Core/HW/WiimoteEmu/Dynamics.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" #include "InputCommon/ControllerEmu/ControlGroup/Cursor.h" namespace Common @@ -101,9 +101,11 @@ struct IRFull : IRExtended }; static_assert(sizeof(IRFull) == 9, "Wrong size"); -class CameraLogic : public I2CSlave +class CameraLogic : public Common::I2CSlaveAutoIncrementing { public: + CameraLogic() : I2CSlaveAutoIncrementing(I2C_ADDR) {} + // OEM sensor bar distance between LED clusters in meters. static constexpr float SENSOR_BAR_LED_SEPARATION = 0.2f; @@ -132,7 +134,7 @@ class CameraLogic : public I2CSlave static constexpr int MAX_POINT_SIZE = 15; void Reset(); - void DoState(PointerWrap& p); + void DoState(PointerWrap& p) override; static std::array GetCameraPoints(const Common::Matrix44& transform, Common::Vec2 field_of_view); void Update(const std::array& camera_points); @@ -176,8 +178,9 @@ class CameraLogic : public I2CSlave static const u8 REPORT_DATA_OFFSET = offsetof(Register, camera_data); private: - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; + bool DeviceEnabled() override; + u8 ReadByte(u8 addr) override; + void WriteByte(u8 addr, u8 value) override; Register m_reg_data{}; diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp index fd2b18587276..55e7ebecfa7f 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.cpp @@ -71,14 +71,29 @@ void None::DoState(PointerWrap& p) // Nothing needed. } -int None::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +bool None::StartWrite(u8 slave_addr) { - return 0; + return false; +} + +bool None::StartRead(u8 slave_addr) +{ + return false; +} + +void None::Stop() +{ + // Nothing needed. } -int None::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +std::optional None::ReadByte() { - return 0; + return std::nullopt; +} + +bool None::WriteByte(u8 value) +{ + return false; } bool EncryptedExtension::ReadDeviceDetectPin() const @@ -86,11 +101,8 @@ bool EncryptedExtension::ReadDeviceDetectPin() const return true; } -int EncryptedExtension::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +u8 EncryptedExtension::ReadByte(u8 addr) { - if (I2C_ADDR != slave_addr) - return 0; - if (0x00 == addr) { // This is where real hardware would update controller data @@ -98,7 +110,7 @@ int EncryptedExtension::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) // TAS code fails to sync data reads and such.. } - auto const result = RawRead(&m_reg, addr, count, data_out); + u8 result = RawRead(&m_reg, addr); // Encrypt data read from extension register. if (ENCRYPTION_ENABLED == m_reg.encryption) @@ -109,30 +121,25 @@ int EncryptedExtension::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) m_is_key_dirty = false; } - ext_key.Encrypt(data_out, addr, count); + ext_key.Encrypt(&result, addr, 1); } return result; } -int EncryptedExtension::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +void EncryptedExtension::WriteByte(u8 addr, u8 value) { - if (I2C_ADDR != slave_addr) - return 0; - - auto const result = RawWrite(&m_reg, addr, count, data_in); + RawWrite(&m_reg, addr, value); constexpr u8 ENCRYPTION_KEY_DATA_BEGIN = offsetof(Register, encryption_key_data); constexpr u8 ENCRYPTION_KEY_DATA_END = ENCRYPTION_KEY_DATA_BEGIN + 0x10; - if (addr + count > ENCRYPTION_KEY_DATA_BEGIN && addr < ENCRYPTION_KEY_DATA_END) + if (addr >= ENCRYPTION_KEY_DATA_BEGIN && addr < ENCRYPTION_KEY_DATA_END) { // FYI: Real extensions seem to require the key data written in specifically sized chunks. // We just run the key generation on all writes to the key area. m_is_key_dirty = true; } - - return result; } void EncryptedExtension::Reset() @@ -147,6 +154,8 @@ void EncryptedExtension::Reset() void EncryptedExtension::DoState(PointerWrap& p) { + I2CSlaveAutoIncrementing::DoState(p); + p.Do(m_reg); if (p.IsReadMode()) diff --git a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h index 4a055c597db3..bafab30a0621 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h +++ b/Source/Core/Core/HW/WiimoteEmu/Extension/Extension.h @@ -10,15 +10,19 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" +#include "Common/I2C.h" #include "Core/HW/WiimoteEmu/Encryption.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" #include "InputCommon/ControllerEmu/ControllerEmu.h" +#ifdef _MSC_VER +#pragma warning(disable : 4250) // C4250 inherits via dominance - intended behavior here +#endif + namespace WiimoteEmu { struct DesiredExtensionState; -class Extension : public ControllerEmu::EmulatedController, public I2CSlave +class Extension : public ControllerEmu::EmulatedController, public virtual Common::I2CSlave { public: explicit Extension(const char* name); @@ -56,23 +60,39 @@ class None : public Extension void Reset() override; void DoState(PointerWrap& p) override; - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; + bool StartWrite(u8 slave_addr) override; + bool StartRead(u8 slave_addr) override; + void Stop() override; + std::optional ReadByte() override; + bool WriteByte(u8 value) override; }; // This class provides the encryption and initialization behavior of most extensions. -class EncryptedExtension : public Extension +class EncryptedExtension : public Extension, public Common::I2CSlaveAutoIncrementing { public: static constexpr u8 I2C_ADDR = 0x52; static constexpr int CONTROLLER_DATA_BYTES = 21; - using Extension::Extension; + explicit EncryptedExtension(const char* name) + : Extension(name), I2CSlaveAutoIncrementing(I2C_ADDR) + { + } + EncryptedExtension(const char* config_name, const char* display_name) + : Extension(config_name, display_name), I2CSlaveAutoIncrementing(I2C_ADDR) + { + } // TODO: This is public for TAS reasons. // TODO: TAS handles encryption poorly. EncryptionKey ext_key; + using I2CSlaveAutoIncrementing::ReadByte; + using I2CSlaveAutoIncrementing::StartRead; + using I2CSlaveAutoIncrementing::StartWrite; + using I2CSlaveAutoIncrementing::Stop; + using I2CSlaveAutoIncrementing::WriteByte; + static constexpr int CALIBRATION_CHECKSUM_BYTES = 2; #pragma pack(push, 1) @@ -117,8 +137,8 @@ class EncryptedExtension : public Extension bool ReadDeviceDetectPin() const override; - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; + u8 ReadByte(u8 addr) override; + void WriteByte(u8 addr, u8 value) override; }; class Extension1stParty : public EncryptedExtension diff --git a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp index 97e73ea4bc90..2b152686b8c2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.cpp @@ -7,7 +7,7 @@ namespace WiimoteEmu { -ExtensionPort::ExtensionPort(I2CBus* i2c_bus) : m_i2c_bus(*i2c_bus) +ExtensionPort::ExtensionPort(Common::I2CBusBase* i2c_bus) : m_i2c_bus(*i2c_bus) { } diff --git a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h index 86b1fd31f962..bb2f0ec01210 100644 --- a/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h +++ b/Source/Core/Core/HW/WiimoteEmu/ExtensionPort.h @@ -4,8 +4,8 @@ #pragma once #include "Common/ChunkFile.h" +#include "Common/I2C.h" #include "Core/HW/WiimoteEmu/Extension/Extension.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" namespace WiimoteEmu { @@ -35,14 +35,14 @@ class ExtensionPort static constexpr u8 REPORT_I2C_SLAVE = 0x52; static constexpr u8 REPORT_I2C_ADDR = 0x00; - explicit ExtensionPort(I2CBus* i2c_bus); + explicit ExtensionPort(Common::I2CBusBase* i2c_bus); bool IsDeviceConnected() const; void AttachExtension(Extension* dev); private: Extension* m_extension = nullptr; - I2CBus& m_i2c_bus; + Common::I2CBusBase& m_i2c_bus; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/I2CBus.cpp b/Source/Core/Core/HW/WiimoteEmu/I2CBus.cpp index 1c231f794446..e69de29bb2d1 100644 --- a/Source/Core/Core/HW/WiimoteEmu/I2CBus.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/I2CBus.cpp @@ -1,53 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/HW/WiimoteEmu/I2CBus.h" - -#include - -namespace WiimoteEmu -{ -void I2CBus::AddSlave(I2CSlave* slave) -{ - m_slaves.emplace_back(slave); -} - -void I2CBus::RemoveSlave(I2CSlave* slave) -{ - std::erase(m_slaves, slave); -} - -void I2CBus::Reset() -{ - m_slaves.clear(); -} - -int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) -{ - for (auto& slave : m_slaves) - { - auto const bytes_read = slave->BusRead(slave_addr, addr, count, data_out); - - // A slave responded, we are done. - if (bytes_read) - return bytes_read; - } - - return 0; -} - -int I2CBus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) -{ - for (auto& slave : m_slaves) - { - auto const bytes_written = slave->BusWrite(slave_addr, addr, count, data_in); - - // A slave responded, we are done. - if (bytes_written) - return bytes_written; - } - - return 0; -} - -} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/I2CBus.h b/Source/Core/Core/HW/WiimoteEmu/I2CBus.h index 3a1e0a67d361..e69de29bb2d1 100644 --- a/Source/Core/Core/HW/WiimoteEmu/I2CBus.h +++ b/Source/Core/Core/HW/WiimoteEmu/I2CBus.h @@ -1,75 +0,0 @@ -// Copyright 2019 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "Common/CommonTypes.h" - -namespace WiimoteEmu -{ -class I2CBus; - -class I2CSlave -{ - friend I2CBus; - -protected: - virtual ~I2CSlave() = default; - - virtual int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) = 0; - virtual int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) = 0; - - template - static int RawRead(T* reg_data, u8 addr, int count, u8* data_out) - { - static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); - static_assert(0x100 == sizeof(T)); - - // TODO: addr wraps around after 0xff - - u8* src = reinterpret_cast(reg_data) + addr; - count = std::min(count, int(reinterpret_cast(reg_data + 1) - src)); - - std::copy_n(src, count, data_out); - - return count; - } - - template - static int RawWrite(T* reg_data, u8 addr, int count, const u8* data_in) - { - static_assert(std::is_standard_layout_v && std::is_trivially_copyable_v); - static_assert(0x100 == sizeof(T)); - - // TODO: addr wraps around after 0xff - - u8* dst = reinterpret_cast(reg_data) + addr; - count = std::min(count, int(reinterpret_cast(reg_data + 1) - dst)); - - std::copy_n(data_in, count, dst); - - return count; - } -}; - -class I2CBus -{ -public: - void AddSlave(I2CSlave* slave); - void RemoveSlave(I2CSlave* slave); - - void Reset(); - - // TODO: change int to u16 or something - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out); - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in); - -private: - // Pointers are unowned: - std::vector m_slaves; -}; - -} // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp index 9dc3b2ad1cfc..f7c5ab50a0e7 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.cpp @@ -189,135 +189,187 @@ ExtensionPort& MotionPlus::GetExtPort() return m_extension_port; } -int MotionPlus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +bool MotionPlus::StartWrite(u8 slave_addr) { switch (GetActivationStatus()) { case ActivationStatus::Inactive: - if (INACTIVE_DEVICE_ADDR != slave_addr) - { - // Passthrough to the connected extension. (if any) - return m_i2c_bus.BusRead(slave_addr, addr, count, data_out); - } - - // Perform a normal read of the M+ register. - return RawRead(&m_reg_data, addr, count, data_out); + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + return m_i2c_bus.StartWrite(slave_addr); case ActivationStatus::Active: // FYI: Motion plus does not respond to 0x53 when activated. - if (ACTIVE_DEVICE_ADDR != slave_addr) - { - // No i2c passthrough when activated. - return 0; - } - - // Perform a normal read of the M+ register. - return RawRead(&m_reg_data, addr, count, data_out); + // No i2c passthrough when activated, so *only* target 0x52. + return m_active_wrapper.StartWrite(slave_addr); default: case ActivationStatus::Activating: case ActivationStatus::Deactivating: // The extension port is completely unresponsive here. - return 0; + return false; } } -int MotionPlus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +bool MotionPlus::StartRead(u8 slave_addr) { switch (GetActivationStatus()) { case ActivationStatus::Inactive: - { - if (INACTIVE_DEVICE_ADDR != slave_addr) - { - // Passthrough to the connected extension. (if any) - return m_i2c_bus.BusWrite(slave_addr, addr, count, data_in); - } + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + return m_i2c_bus.StartRead(slave_addr); - DEBUG_LOG_FMT(WIIMOTE, "Inactive M+ write {:#x} : {}", addr, ArrayToString(data_in, count)); + case ActivationStatus::Active: + // FYI: Motion plus does not respond to 0x53 when activated. + // No i2c passthrough when activated, so *only* target 0x52. + return m_active_wrapper.StartRead(slave_addr); - auto const result = RawWrite(&m_reg_data, addr, count, data_in); + default: + case ActivationStatus::Activating: + case ActivationStatus::Deactivating: + // The extension port is completely unresponsive here. + return false; + } +} - if (PASSTHROUGH_MODE_OFFSET == addr) - { - OnPassthroughModeWrite(); - } +void MotionPlus::Stop() +{ + switch (GetActivationStatus()) + { + case ActivationStatus::Inactive: + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + m_i2c_bus.Stop(); + break; + + case ActivationStatus::Active: + // FYI: Motion plus does not respond to 0x53 when activated. + // No i2c passthrough when activated, so *only* target 0x52. + m_active_wrapper.Stop(); + break; - return result; + default: + case ActivationStatus::Activating: + case ActivationStatus::Deactivating: + // The extension port is completely unresponsive here. + break; } +} + +std::optional MotionPlus::ReadByte() +{ + switch (GetActivationStatus()) + { + case ActivationStatus::Inactive: + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + return m_i2c_bus.ReadByte(); case ActivationStatus::Active: + // FYI: Motion plus does not respond to 0x53 when activated. + // No i2c passthrough when activated, so *only* target 0x52. + return m_active_wrapper.ReadByte(); + + default: + case ActivationStatus::Activating: + case ActivationStatus::Deactivating: + // The extension port is completely unresponsive here. + return std::nullopt; + } +} + +bool MotionPlus::WriteByte(u8 value) +{ + switch (GetActivationStatus()) { + case ActivationStatus::Inactive: + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + return m_i2c_bus.WriteByte(value); + + case ActivationStatus::Active: // FYI: Motion plus does not respond to 0x53 when activated. - if (ACTIVE_DEVICE_ADDR != slave_addr) - { - // No i2c passthrough when activated. - return 0; - } + // No i2c passthrough when activated, so *only* target 0x52. + return m_active_wrapper.WriteByte(value); + + default: + case ActivationStatus::Activating: + case ActivationStatus::Deactivating: + // The extension port is completely unresponsive here. + return false; + } +} - DEBUG_LOG_FMT(WIIMOTE, "Active M+ write {:#x} : {}", addr, ArrayToString(data_in, count)); +u8 MotionPlus::RegisterWrapper::ReadByte(u8 addr) +{ + return RawRead(&m_owner->m_reg_data, addr); +} - auto const result = RawWrite(&m_reg_data, addr, count, data_in); +void MotionPlus::InactiveRegisterWrapper::WriteByte(u8 addr, u8 value) +{ + DEBUG_LOG_FMT(WIIMOTE, "Inactive M+ write {:02x} = {:02x}", addr, value); - switch (addr) - { - case offsetof(Register, init_trigger): - // It seems a write of any value here triggers deactivation on a real M+. - Deactivate(); + RawWrite(&m_owner->m_reg_data, addr, value); - // Passthrough the write to the attached extension. - // The M+ deactivation signal is cleverly the same as EXT initialization. - m_i2c_bus.BusWrite(slave_addr, addr, count, data_in); - break; + if (PASSTHROUGH_MODE_OFFSET == addr) + { + m_owner->OnPassthroughModeWrite(); + } +} + +void MotionPlus::ActiveRegisterWrapper::WriteByte(u8 addr, u8 value) +{ + DEBUG_LOG_FMT(WIIMOTE, "Inactive M+ write {:02x} = {:02x}", addr, value); + + RawWrite(&m_owner->m_reg_data, addr, value); + + switch (addr) + { + case offsetof(Register, init_trigger): + // It seems a write of any value here triggers deactivation on a real M+. + m_owner->Deactivate(); + + // Passthrough the write to the attached extension. + // The M+ deactivation signal is cleverly the same as EXT initialization. + // TODO + // m_i2c_bus.BusWrite(slave_addr, addr, count, data_in); + break; + + case offsetof(Register, challenge_type): + if (ChallengeState::ParameterXReady == m_owner->m_reg_data.challenge_state) + { + DEBUG_LOG_FMT(WIIMOTE, "M+ challenge: {:#x}", m_owner->m_reg_data.challenge_type); - case offsetof(Register, challenge_type): - if (ChallengeState::ParameterXReady == m_reg_data.challenge_state) + // After games read parameter x they write here to request y0 or y1. + if (0 == m_owner->m_reg_data.challenge_type) { - DEBUG_LOG_FMT(WIIMOTE, "M+ challenge: {:#x}", m_reg_data.challenge_type); - - // After games read parameter x they write here to request y0 or y1. - if (0 == m_reg_data.challenge_type) - { - // Preparing y0 on the real M+ is almost instant (30ms maybe). - constexpr int PREPARE_Y0_MS = 30; - m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y0_MS / 1000; - } - else - { - // A real M+ takes about 1200ms to prepare y1. - // Games seem to not care that we don't take that long. - constexpr int PREPARE_Y1_MS = 500; - m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y1_MS / 1000; - } - - // Games give the M+ a bit of time to compute the value. - // y0 gets about half a second. - // y1 gets at about 9.5 seconds. - // After this the M+ will fail the "challenge". - - m_reg_data.challenge_state = ChallengeState::PreparingY; + // Preparing y0 on the real M+ is almost instant (30ms maybe). + constexpr int PREPARE_Y0_MS = 30; + m_owner->m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y0_MS / 1000; + } + else + { + // A real M+ takes about 1200ms to prepare y1. + // Games seem to not care that we don't take that long. + constexpr int PREPARE_Y1_MS = 500; + m_owner->m_progress_timer = ::Wiimote::UPDATE_FREQ * PREPARE_Y1_MS / 1000; } - break; - case offsetof(Register, calibration_trigger): - // Games seem to invoke this to start and stop calibration. Exact consequences unknown. - DEBUG_LOG_FMT(WIIMOTE, "M+ calibration trigger: {:#x}", m_reg_data.calibration_trigger); - break; + // Games give the M+ a bit of time to compute the value. + // y0 gets about half a second. + // y1 gets at about 9.5 seconds. + // After this the M+ will fail the "challenge". - case PASSTHROUGH_MODE_OFFSET: - // Games sometimes (not often) write zero here to deactivate the M+. - OnPassthroughModeWrite(); - break; + m_owner->m_reg_data.challenge_state = ChallengeState::PreparingY; } + break; - return result; - } + case offsetof(Register, calibration_trigger): + // Games seem to invoke this to start and stop calibration. Exact consequences unknown. + DEBUG_LOG_FMT(WIIMOTE, "M+ calibration trigger: {:#x}", + m_owner->m_reg_data.calibration_trigger); + break; - default: - case ActivationStatus::Activating: - case ActivationStatus::Deactivating: - // The extension port is completely unresponsive here. - return 0; + case PASSTHROUGH_MODE_OFFSET: + // Games sometimes (not often) write zero here to deactivate the M+. + m_owner->OnPassthroughModeWrite(); + break; } } diff --git a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h index 02ddfb7ec2f0..db6bcb089b18 100644 --- a/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h +++ b/Source/Core/Core/HW/WiimoteEmu/MotionPlus.h @@ -6,15 +6,15 @@ #include #include "Common/CommonTypes.h" +#include "Common/I2C.h" #include "Common/MathUtil.h" #include "Common/Swap.h" #include "Core/HW/WiimoteEmu/Dynamics.h" #include "Core/HW/WiimoteEmu/ExtensionPort.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" namespace WiimoteEmu { -struct MotionPlus : public Extension +class MotionPlus : public Extension { public: enum class PassthroughMode : u8 @@ -241,6 +241,43 @@ struct MotionPlus : public Extension #pragma pack(pop) static_assert(0x100 == sizeof(Register), "Wrong size"); + class RegisterWrapper : public Common::I2CSlaveAutoIncrementing + { + protected: + RegisterWrapper(MotionPlus* owner, u8 slave_addr) + : I2CSlaveAutoIncrementing(slave_addr), m_owner(owner) + { + } + + u8 ReadByte(u8 addr) override; + + MotionPlus* const m_owner; + }; + + class InactiveRegisterWrapper : public RegisterWrapper + { + public: + InactiveRegisterWrapper(MotionPlus* owner) : RegisterWrapper(owner, INACTIVE_DEVICE_ADDR) {} + + using Common::I2CSlaveAutoIncrementing::ReadByte; + using Common::I2CSlaveAutoIncrementing::WriteByte; + + protected: + void WriteByte(u8 addr, u8 value) override; + }; + + class ActiveRegisterWrapper : public RegisterWrapper + { + public: + ActiveRegisterWrapper(MotionPlus* owner) : RegisterWrapper(owner, ACTIVE_DEVICE_ADDR) {} + + using Common::I2CSlaveAutoIncrementing::ReadByte; + using Common::I2CSlaveAutoIncrementing::WriteByte; + + protected: + void WriteByte(u8 addr, u8 value) override; + }; + void Activate(); void Deactivate(); void OnPassthroughModeWrite(); @@ -248,8 +285,11 @@ struct MotionPlus : public Extension ActivationStatus GetActivationStatus() const; PassthroughMode GetPassthroughMode() const; - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; + bool StartWrite(u8 slave_addr) override; + bool StartRead(u8 slave_addr) override; + void Stop() override; + std::optional ReadByte() override; + bool WriteByte(u8 value) override; bool ReadDeviceDetectPin() const override; @@ -259,7 +299,10 @@ struct MotionPlus : public Extension u8 m_progress_timer = {}; // The port on the end of the motion plus: - I2CBus m_i2c_bus; + Common::I2CBusForwarding m_i2c_bus; ExtensionPort m_extension_port{&m_i2c_bus}; + + InactiveRegisterWrapper m_inactive_wrapper{this}; // connected to m_i2c_bus + ActiveRegisterWrapper m_active_wrapper{this}; // *not* connected to m_i2c_bus }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp index 23cba6bfd741..6fe6cfb0e7fd 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.cpp @@ -146,6 +146,8 @@ void SpeakerLogic::DoState(PointerWrap& p) { p.Do(adpcm_state); p.Do(reg_data); + p.Do(m_i2c_active); + p.Do(m_device_address); } void SpeakerLogic::SetSpeakerEnabled(bool enabled) @@ -153,29 +155,104 @@ void SpeakerLogic::SetSpeakerEnabled(bool enabled) m_speaker_enabled = enabled; } -int SpeakerLogic::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +bool SpeakerLogic::StartWrite(u8 slave_addr) { - if (I2C_ADDR != slave_addr) - return 0; + if (slave_addr == I2C_ADDR) + { + INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} write started, previously active: {}", I2C_ADDR, + m_i2c_active); + m_i2c_active = true; + m_device_address.reset(); + return true; + } + else + { + return false; + } +} - return RawRead(®_data, addr, count, data_out); +bool SpeakerLogic::StartRead(u8 slave_addr) +{ + if (slave_addr == I2C_ADDR) + { + INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} read started, previously active: {}", I2C_ADDR, + m_i2c_active); + if (m_device_address.has_value()) + { + m_i2c_active = true; + return true; + } + else + { + WARN_LOG_FMT(WII_IPC, + "I2C Device {:02x}: read attempted without having written device address", + I2C_ADDR); + m_i2c_active = false; + return false; + } + } + else + { + return false; + } } -int SpeakerLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +void SpeakerLogic::Stop() { - if (I2C_ADDR != slave_addr) - return 0; + m_i2c_active = false; + m_device_address.reset(); +} + +std::optional SpeakerLogic::ReadByte() +{ + if (m_i2c_active) + { + // TODO: It seems reading address 0x00 should always return 0xff. - if (0x00 == addr) + ASSERT(m_device_address.has_value()); // enforced by StartRead + const u8 cur_addr = m_device_address.value(); + // Wrapping from 255 to 0 is the assumed behavior; this may not make sense here + m_device_address = cur_addr + 1; + return RawRead(®_data, cur_addr); + } + else { - SpeakerData(data_in, count, m_speaker_pan_setting.GetValue() / 100); - return count; + return std::nullopt; + } +} + +bool SpeakerLogic::WriteByte(u8 value) +{ + if (m_i2c_active) + { + if (m_device_address.has_value()) + { + const u8 cur_addr = m_device_address.value(); + + if (cur_addr == SPEAKER_DATA_OFFSET) // == 0 + { + // Note: No auto-incrementation in this case + SpeakerData(&value, 1, m_speaker_pan_setting.GetValue() / 100); + } + else + { + // Wrapping from 255 to 0 is the assumed behavior; this may not make sense here + m_device_address = cur_addr + 1; + + // TODO: Does writing immediately change the decoder config even when active + // or does a write to 0x08 activate the new configuration or something? + RawWrite(®_data, cur_addr, value); + } + } + else + { + m_device_address = value; + } + return true; } else { - // TODO: Does writing immediately change the decoder config even when active - // or does a write to 0x08 activate the new configuration or something? - return RawWrite(®_data, addr, count, data_in); + return false; } } diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.h b/Source/Core/Core/HW/WiimoteEmu/Speaker.h index 9a6a652bb1fa..dcb93bca4946 100644 --- a/Source/Core/Core/HW/WiimoteEmu/Speaker.h +++ b/Source/Core/Core/HW/WiimoteEmu/Speaker.h @@ -5,7 +5,7 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" +#include "Common/I2C.h" #include "InputCommon/ControllerEmu/Setting/NumericSetting.h" namespace WiimoteEmu @@ -17,7 +17,7 @@ struct ADPCMState class Wiimote; -class SpeakerLogic : public I2CSlave +class SpeakerLogic : public Common::I2CSlave { friend class Wiimote; @@ -63,8 +63,11 @@ class SpeakerLogic : public I2CSlave static_assert(0x100 == sizeof(Register)); - int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) override; - int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) override; + bool StartWrite(u8 slave_addr) override; + bool StartRead(u8 slave_addr) override; + void Stop() override; + std::optional ReadByte() override; + bool WriteByte(u8 value) override; Register reg_data{}; @@ -75,6 +78,9 @@ class SpeakerLogic : public I2CSlave ControllerEmu::SettingValue m_speaker_pan_setting; bool m_speaker_enabled = false; + + bool m_i2c_active = false; + std::optional m_device_address = std::nullopt; }; } // namespace WiimoteEmu diff --git a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h index e364ccdb6c91..8468e4dc60a2 100644 --- a/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h +++ b/Source/Core/Core/HW/WiimoteEmu/WiimoteEmu.h @@ -10,6 +10,7 @@ #include "Common/Common.h" #include "Common/Config/Config.h" +#include "Common/I2C.h" #include "Core/HW/WiimoteCommon/WiimoteReport.h" @@ -17,7 +18,6 @@ #include "Core/HW/WiimoteEmu/Dynamics.h" #include "Core/HW/WiimoteEmu/Encryption.h" #include "Core/HW/WiimoteEmu/ExtensionPort.h" -#include "Core/HW/WiimoteEmu/I2CBus.h" #include "Core/HW/WiimoteEmu/MotionPlus.h" #include "Core/HW/WiimoteEmu/Speaker.h" @@ -316,7 +316,7 @@ class Wiimote : public ControllerEmu::EmulatedController, public WiimoteCommon:: MotionPlus m_motion_plus; CameraLogic m_camera_logic; - I2CBus m_i2c_bus; + Common::I2CBusSimple m_i2c_bus; ExtensionPort m_extension_port{&m_i2c_bus}; diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index f9964c0fa812..daf41f4362c4 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 diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index a61f0d822ac1..b6967694eeef 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -120,6 +120,7 @@ + @@ -812,6 +813,7 @@ +