From 669cff6571f49be9e0567b8332a9e5fd60fe65d1 Mon Sep 17 00:00:00 2001 From: Pokechu22 Date: Sat, 3 Sep 2022 16:21:23 -0700 Subject: [PATCH] Move I2CBus logic into I2C.cpp (without refactoring for devices) --- Source/Core/Common/I2C.cpp | 279 +++++++++++++++++++++++++++- Source/Core/Common/I2C.h | 56 +++++- Source/Core/Core/HW/WII_IPC.cpp | 316 +------------------------------- 3 files changed, 334 insertions(+), 317 deletions(-) diff --git a/Source/Core/Common/I2C.cpp b/Source/Core/Common/I2C.cpp index d44f98dffb..843110e2fb 100644 --- a/Source/Core/Common/I2C.cpp +++ b/Source/Core/Common/I2C.cpp @@ -4,25 +4,43 @@ #include "Common/I2C.h" #include +#include +#include + +#include + +#include "Common/Assert.h" +#include "Common/EnumFormatter.h" +#include "Common/Logging/Log.h" +#include "Core/Debugger/Debugger_SymbolMap.h" + +namespace IOS +{ +struct AVEState; +extern AVEState ave_state; +extern std::bitset<0x100> ave_ever_logged; // For logging only; not saved + +std::string_view GetAVERegisterName(u8 address); +} // namespace IOS namespace Common { -void I2CBus::AddSlave(I2CSlave* slave) +void I2CBusOld::AddSlave(I2CSlave* slave) { m_slaves.emplace_back(slave); } -void I2CBus::RemoveSlave(I2CSlave* slave) +void I2CBusOld::RemoveSlave(I2CSlave* slave) { m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end()); } -void I2CBus::Reset() +void I2CBusOld::Reset() { m_slaves.clear(); } -int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) +int I2CBusOld::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) { for (auto& slave : m_slaves) { @@ -36,7 +54,7 @@ int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out) return 0; } -int I2CBus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) +int I2CBusOld::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) { for (auto& slave : m_slaves) { @@ -50,4 +68,255 @@ int I2CBus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in) return 0; } +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::WriteDeviceAddress: + 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; + i2c_address.reset(); + // Note: don't reset device_address, as it's re-used for reads +} + +void I2CBus::Stop() +{ + INFO_LOG_FMT(WII_IPC, "AVE: Stop I2C"); + state = State::Inactive; + bit_counter = 0; + current_byte = 0; + i2c_address.reset(); + device_address.reset(); +} + +bool I2CBus::WriteExpected() const +{ + // If we don't have an I²C address, it needs to be written (even if the address that is later + // written is a read). + // Otherwise, check the least significant bit; it being *clear* indicates a write. + const bool is_write = !i2c_address.has_value() || ((i2c_address.value() & 1) == 0); + // The device that is otherwise recieving instead transmits an acknowledge bit after each byte. + const bool acknowledge_expected = (bit_counter == 8); + + return is_write ^ acknowledge_expected; +} + +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: {} (write expected: {})", bit_counter, state, + // sda, WriteExpected()); + // 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. + ASSERT(device_address.has_value()); // Implied by the state transition in falling edge + current_byte = reinterpret_cast(&IOS::ave_state)[device_address.value()]; + INFO_LOG_FMT(WII_IPC, "AVE: Read from {:02x} ({}) -> {:02x}", device_address.value(), + IOS::GetAVERegisterName(device_address.value()), current_byte); + } + // 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: {} (write expected: {})", bit_counter, state, + // sda, WriteExpected()); + // SCL falling edge is used to advance bit_counter/change states and process writes. + if (state == State::SetI2CAddress || state == State::WriteDeviceAddress || + state == State::WriteToDevice) + { + if (bit_counter == 8) + { + // Acknowledge bit for *reads*. + if (sda) + { + WARN_LOG_FMT(WII_IPC, "Read NACK'd"); + state = State::Inactive; + } + } + else + { + 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) + { + if ((current_byte >> 1) != 0x70) + { + state = State::Inactive; // NACK + WARN_LOG_FMT(WII_IPC, "AVE: Unknown I2C address {:02x}", current_byte); + } + else + { + INFO_LOG_FMT(WII_IPC, "AVE: I2C address is {:02x}", current_byte); + } + } + else if (state == State::WriteDeviceAddress) + { + device_address = current_byte; + INFO_LOG_FMT(WII_IPC, "AVE: Device address is {:02x}", current_byte); + } + else + { + // Actual write + ASSERT(state == State::WriteToDevice); + ASSERT(device_address.has_value()); // implied by state transition + const u8 old_ave_value = reinterpret_cast(&IOS::ave_state)[device_address.value()]; + reinterpret_cast(&IOS::ave_state)[device_address.value()] = current_byte; + if (!IOS::ave_ever_logged[device_address.value()] || old_ave_value != current_byte) + { + INFO_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", current_byte, + device_address.value(), IOS::GetAVERegisterName(device_address.value())); + IOS::ave_ever_logged[device_address.value()] = true; + } + else + { + DEBUG_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", current_byte, + device_address.value(), IOS::GetAVERegisterName(device_address.value())); + } + device_address = device_address.value() + 1; + } + } + } + } + + 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; + switch (state) + { + case State::SetI2CAddress: + i2c_address = current_byte; + // Note: i2c_address is known to correspond to a valid device + if ((current_byte & 1) == 0) + { + state = State::WriteDeviceAddress; + device_address.reset(); + } + else + { + if (device_address.has_value()) + { + state = State::ReadFromDevice; + } + else + { + state = State::Inactive; // NACK - required for 8-bit internal addresses + ERROR_LOG_FMT(WII_IPC, "AVE: Attempted to read device without having a read address!"); + } + } + break; + case State::WriteDeviceAddress: + state = State::WriteToDevice; + break; + } + } + else + { + bit_counter++; + } + } + // Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO); +} + }; // namespace Common + +template <> +struct fmt::formatter : EnumFormatter +{ + static constexpr array_type names = {"Inactive", "Activating", + "Set I2C Address", "Write Device 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 index 1ea1c8198d..fcccb4f322 100644 --- a/Source/Core/Common/I2C.h +++ b/Source/Core/Common/I2C.h @@ -4,17 +4,18 @@ #pragma once #include +#include #include #include "Common/CommonTypes.h" namespace Common { -class I2CBus; +class I2CBusOld; class I2CSlave { - friend I2CBus; + friend I2CBusOld; protected: virtual ~I2CSlave() = default; @@ -55,7 +56,7 @@ protected: } }; -class I2CBus +class I2CBusOld { public: void AddSlave(I2CSlave* slave); @@ -72,4 +73,53 @@ private: std::vector m_slaves; }; +// 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.) +// - All devices use a 1-byte auto-incrementing address which wraps around from 255 to 0. +// - The device address is handled by this I2CBus class, instead of the device itself. +// - The device address is set on writes, and re-used for reads; writing an address and data and +// then switching to reading uses the incremented address. Every write must specify the address. +// - Reading without setting the device address beforehand is disallowed; the I²C specification +// allows such reads but does not specify how they behave (or anything about the behavior of the +// device address). +// - Switching between multiple devices using a restart does not reset the device address; the +// device address is only reset on stopping. This means that a write to one device followed by a +// read from a different device would result in reading from the last used device address (without +// any warning). +// - 10-bit addressing and other reserved addressing modes are not implemented. +class I2CBus +{ +public: + enum class State + { + Inactive, + Activating, + SetI2CAddress, + WriteDeviceAddress, + WriteToDevice, + ReadFromDevice, + }; + + State state; + u8 bit_counter; + u8 current_byte; + std::optional i2c_address; // Not shifted; includes the read flag + std::optional device_address; + + void Update(const bool old_scl, const bool new_scl, const bool old_sda, const bool new_sda); + bool GetSCL() const; + bool GetSDA() const; + +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 95a6880dda..e8be5abd5c 100644 --- a/Source/Core/Core/HW/WII_IPC.cpp +++ b/Source/Core/Core/HW/WII_IPC.cpp @@ -9,11 +9,10 @@ #include "Common/ChunkFile.h" #include "Common/CommonTypes.h" -#include "Common/EnumFormatter.h" +#include "Common/I2C.h" #include "Common/Logging/Log.h" #include "Core/Core.h" #include "Core/CoreTiming.h" -#include "Core/Debugger/Debugger_SymbolMap.h" #include "Core/HW/DVD/DVDInterface.h" #include "Core/HW/MMIO.h" #include "Core/HW/ProcessorInterface.h" @@ -121,59 +120,10 @@ struct AVEState }; #pragma pack() static_assert(sizeof(AVEState) == 0x100); -static AVEState ave_state; -static std::bitset ave_ever_logged; // For logging only; not saved +AVEState ave_state; +std::bitset ave_ever_logged; -// 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.) -// - All devices use a 1-byte auto-incrementing address which wraps around from 255 to 0. -// - The device address is handled by this I2CBus class, instead of the device itself. -// - The device address is set on writes, and re-used for reads; writing an address and data and -// then switching to reading uses the incremented address. Every write must specify the address. -// - Reading without setting the device address beforehand is disallowed; the I²C specification -// allows such reads but does not specify how they behave (or anything about the behavior of the -// device address). -// - Switching between multiple devices using a restart does not reset the device address; the -// device address is only reset on stopping. This means that a write to one device followed by a -// read from a different device would result in reading from the last used device address (without -// any warning). -// - 10-bit addressing and other reserved addressing modes are not implemented. -class I2CBus -{ -public: - enum class State - { - Inactive, - Activating, - SetI2CAddress, - WriteDeviceAddress, - WriteToDevice, - ReadFromDevice, - }; - - State state; - u8 bit_counter; - u8 current_byte; - std::optional i2c_address; // Not shifted; includes the read flag - std::optional device_address; - - void Update(Core::System& system, const bool old_scl, const bool new_scl, const bool old_sda, - const bool new_sda); - bool GetSCL() const; - bool GetSDA() const; - -private: - void Start(); - void Stop(); - void SCLRisingEdge(const bool sda); - void SCLFallingEdge(const bool sda); - bool WriteExpected() const; -}; -I2CBus i2c_state; +Common::I2CBus i2c_state; static CoreTiming::EventType* updateInterrupts; @@ -247,7 +197,7 @@ void WiiIPC::Shutdown() { } -static std::string_view GetAVERegisterName(u8 address) +std::string_view GetAVERegisterName(u8 address) { if (address == 0x00) return "A/V Timings"; @@ -300,36 +250,6 @@ static u32 ReadGPIOIn(Core::System& system) return gpio_in.m_hex; } -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::WriteDeviceAddress: - 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 - } -} - u32 WiiIPC::GetGPIOOut() { // In the direction field, a '1' bit for a pin indicates that it will behave as an output (drive), @@ -352,226 +272,13 @@ void WiiIPC::GPIOOutChanged(u32 old_value_hex) } // I²C logic for the audio/video encoder (AVE) - i2c_state.Update(m_system, old_value[GPIO::AVE_SCL], new_value[GPIO::AVE_SCL], - old_value[GPIO::AVE_SDA], new_value[GPIO::AVE_SDA]); + 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 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; - i2c_address.reset(); - // Note: don't reset device_address, as it's re-used for reads -} - -void I2CBus::Stop() -{ - INFO_LOG_FMT(WII_IPC, "AVE: Stop I2C"); - state = State::Inactive; - bit_counter = 0; - current_byte = 0; - i2c_address.reset(); - device_address.reset(); -} - -bool I2CBus::WriteExpected() const -{ - // If we don't have an I²C address, it needs to be written (even if the address that is later - // written is a read). - // Otherwise, check the least significant bit; it being *clear* indicates a write. - const bool is_write = !i2c_address.has_value() || ((i2c_address.value() & 1) == 0); - // The device that is otherwise recieving instead transmits an acknowledge bit after each byte. - const bool acknowledge_expected = (bit_counter == 8); - - return is_write ^ acknowledge_expected; -} - -void I2CBus::Update(Core::System& system, 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: {} (write expected: {})", bit_counter, state, - // sda, WriteExpected()); - // 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. - ASSERT(device_address.has_value()); // Implied by the state transition in falling edge - current_byte = reinterpret_cast(&ave_state)[device_address.value()]; - INFO_LOG_FMT(WII_IPC, "AVE: Read from {:02x} ({}) -> {:02x}", device_address.value(), - GetAVERegisterName(device_address.value()), current_byte); - } - // 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: {} (write expected: {})", bit_counter, state, - // sda, WriteExpected()); - // SCL falling edge is used to advance bit_counter/change states and process writes. - if (state == State::SetI2CAddress || state == State::WriteDeviceAddress || - state == State::WriteToDevice) - { - if (bit_counter == 8) - { - // Acknowledge bit for *reads*. - if (sda) - { - WARN_LOG_FMT(WII_IPC, "Read NACK'd"); - state = State::Inactive; - } - } - else - { - 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) - { - if ((current_byte >> 1) != 0x70) - { - state = State::Inactive; // NACK - WARN_LOG_FMT(WII_IPC, "AVE: Unknown I2C address {:02x}", current_byte); - } - else - { - INFO_LOG_FMT(WII_IPC, "AVE: I2C address is {:02x}", current_byte); - } - } - else if (state == State::WriteDeviceAddress) - { - device_address = current_byte; - INFO_LOG_FMT(WII_IPC, "AVE: Device address is {:02x}", current_byte); - } - else - { - // Actual write - ASSERT(state == State::WriteToDevice); - ASSERT(device_address.has_value()); // implied by state transition - const u8 old_ave_value = reinterpret_cast(&ave_state)[device_address.value()]; - reinterpret_cast(&ave_state)[device_address.value()] = current_byte; - if (!ave_ever_logged[device_address.value()] || old_ave_value != current_byte) - { - INFO_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", current_byte, - device_address.value(), GetAVERegisterName(device_address.value())); - ave_ever_logged[device_address.value()] = true; - } - else - { - DEBUG_LOG_FMT(WII_IPC, "AVE: Wrote {:02x} to {:02x} ({})", current_byte, - device_address.value(), GetAVERegisterName(device_address.value())); - } - device_address = device_address.value() + 1; - } - } - } - } - - 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; - switch (state) - { - case State::SetI2CAddress: - i2c_address = current_byte; - // Note: i2c_address is known to correspond to a valid device - if ((current_byte & 1) == 0) - { - state = State::WriteDeviceAddress; - device_address.reset(); - } - else - { - if (device_address.has_value()) - { - state = State::ReadFromDevice; - } - else - { - state = State::Inactive; // NACK - required for 8-bit internal addresses - ERROR_LOG_FMT(WII_IPC, "AVE: Attempted to read device without having a read address!"); - } - } - break; - case State::WriteDeviceAddress: - state = State::WriteToDevice; - break; - } - } - else - { - bit_counter++; - } - } - // Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO); -} - void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base) { mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead(), MMIO::DirectWrite(&m_ppc_msg)); @@ -746,12 +453,3 @@ bool WiiIPC::IsReady() const ((m_ppc_irq_flags & INT_CAUSE_IPC_BROADWAY) == 0)); } } // namespace IOS - -template <> -struct fmt::formatter : EnumFormatter -{ - static constexpr array_type names = {"Inactive", "Activating", - "Set I2C Address", "Write Device Address", - "Write To Device", "Read From Device"}; - constexpr formatter() : EnumFormatter(names) {} -};