diff --git a/Source/Core/Common/I2C.cpp b/Source/Core/Common/I2C.cpp index aab3f7cb92..c8acb3511c 100644 --- a/Source/Core/Common/I2C.cpp +++ b/Source/Core/Common/I2C.cpp @@ -412,7 +412,7 @@ void I2CBus::DoState(PointerWrap& p) bool I2CSlaveAutoIncrementing::StartWrite(u8 slave_addr) { - if (slave_addr == m_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); @@ -428,7 +428,7 @@ bool I2CSlaveAutoIncrementing::StartWrite(u8 slave_addr) bool I2CSlaveAutoIncrementing::StartRead(u8 slave_addr) { - if (slave_addr == m_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); @@ -508,8 +508,6 @@ void I2CSlaveAutoIncrementing::DoState(PointerWrap& p) } p.Do(m_active); p.Do(m_device_address); - - DoDeviceState(p); } }; // namespace Common diff --git a/Source/Core/Common/I2C.h b/Source/Core/Common/I2C.h index 61b3c518bd..e2bb96a06b 100644 --- a/Source/Core/Common/I2C.h +++ b/Source/Core/Common/I2C.h @@ -47,7 +47,7 @@ protected: } }; -class I2CSlaveAutoIncrementing : public I2CSlave +class I2CSlaveAutoIncrementing : public virtual I2CSlave { public: I2CSlaveAutoIncrementing(u8 slave_addr) : m_slave_addr(slave_addr) {} @@ -59,14 +59,14 @@ public: std::optional ReadByte() override; bool WriteByte(u8 value) override; - void DoState(PointerWrap& p); + 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; - virtual void DoDeviceState(PointerWrap& p) = 0; - private: const u8 m_slave_addr; bool m_active = false; @@ -104,6 +104,16 @@ public: 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. diff --git a/Source/Core/Core/HW/WII_IPC.cpp b/Source/Core/Core/HW/WII_IPC.cpp index 817261780b..5cf7ae22cb 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -176,6 +176,12 @@ public: ave_ever_logged = {}; } + void DoState(PointerWrap& p) override + { + I2CSlaveAutoIncrementing::DoState(p); + p.Do(m_registers); + } + AVEState m_registers{}; protected: @@ -203,7 +209,6 @@ protected: value); } } - void DoDeviceState(PointerWrap& p) override { p.Do(m_registers); } private: std::bitset ave_ever_logged{}; // logging only, not saved diff --git a/Source/Core/Core/HW/WiimoteEmu/Camera.cpp b/Source/Core/Core/HW/WiimoteEmu/Camera.cpp index 1dcdb77952..14efaed6b8 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; + return RawRead(&m_reg_data, addr); +} - if (!m_is_enabled) - return 0; - - 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 23e21cc84e..6942cf12d2 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 @@ public: 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 @@ public: 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 fd2b185872..55e7ebecfa 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; } -int None::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +bool None::StartRead(u8 slave_addr) { - return 0; + return false; +} + +void None::Stop() +{ + // Nothing needed. +} + +std::optional None::ReadByte() +{ + 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 4a055c597d..bafab30a06 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 @@ private: 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 @@ private: 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 97e73ea4bc..2b152686b8 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 86b1fd31f9..bb2f0ec012 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 @@ public: 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 19202350fd..e69de29bb2 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) -{ - m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end()); -} - -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 3a1e0a67d3..e69de29bb2 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 9dc3b2ad1c..f7c5ab50a0 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); - } - - DEBUG_LOG_FMT(WIIMOTE, "Inactive M+ write {:#x} : {}", addr, ArrayToString(data_in, count)); - - auto const result = RawWrite(&m_reg_data, addr, count, data_in); - - if (PASSTHROUGH_MODE_OFFSET == addr) - { - OnPassthroughModeWrite(); - } - - return result; - } + // m_inactive_wrapper is connected to the bus, so it will respond to 0x53 when inactive. + return m_i2c_bus.StartRead(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; - } - - DEBUG_LOG_FMT(WIIMOTE, "Active M+ write {:#x} : {}", addr, ArrayToString(data_in, count)); - - auto const result = RawWrite(&m_reg_data, addr, count, data_in); - - switch (addr) - { - case offsetof(Register, init_trigger): - // It seems a write of any value here triggers deactivation on a real M+. - Deactivate(); - - // 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; - - case offsetof(Register, challenge_type): - if (ChallengeState::ParameterXReady == m_reg_data.challenge_state) - { - 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; - } - 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; - - case PASSTHROUGH_MODE_OFFSET: - // Games sometimes (not often) write zero here to deactivate the M+. - OnPassthroughModeWrite(); - break; - } - - return result; - } + // No i2c passthrough when activated, so *only* target 0x52. + return m_active_wrapper.StartRead(slave_addr); default: case ActivationStatus::Activating: case ActivationStatus::Deactivating: // The extension port is completely unresponsive here. - return 0; + return false; + } +} + +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; + + 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. + // 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; + } +} + +u8 MotionPlus::RegisterWrapper::ReadByte(u8 addr) +{ + return RawRead(&m_owner->m_reg_data, addr); +} + +void MotionPlus::InactiveRegisterWrapper::WriteByte(u8 addr, u8 value) +{ + DEBUG_LOG_FMT(WIIMOTE, "Inactive M+ write {:02x} = {:02x}", addr, value); + + RawWrite(&m_owner->m_reg_data, addr, value); + + 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); + + // After games read parameter x they write here to request y0 or y1. + if (0 == m_owner->m_reg_data.challenge_type) + { + // 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; + } + + // 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_owner->m_reg_data.challenge_state = ChallengeState::PreparingY; + } + 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_owner->m_reg_data.calibration_trigger); + break; + + 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 02ddfb7ec2..db6bcb089b 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 @@ private: #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 @@ private: 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 @@ private: 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 23cba6bfd7..6fe6cfb0e7 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; - - return RawRead(®_data, addr, count, data_out); -} - -int SpeakerLogic::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) -{ - if (I2C_ADDR != slave_addr) - return 0; - - if (0x00 == addr) + if (slave_addr == I2C_ADDR) { - SpeakerData(data_in, count, m_speaker_pan_setting.GetValue() / 100); - return count; + 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 { - // 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; + } +} + +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; + } +} + +void SpeakerLogic::Stop() +{ + 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. + + 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 + { + 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 + { + return false; } } diff --git a/Source/Core/Core/HW/WiimoteEmu/Speaker.h b/Source/Core/Core/HW/WiimoteEmu/Speaker.h index 9a6a652bb1..dcb93bca49 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 @@ private: 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 @@ private: 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 e364ccdb6c..8468e4dc60 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 @@ private: MotionPlus m_motion_plus; CameraLogic m_camera_logic; - I2CBus m_i2c_bus; + Common::I2CBusSimple m_i2c_bus; ExtensionPort m_extension_port{&m_i2c_bus};