mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-20 19:44:57 +00:00
Move I2CBus logic into I2C.cpp (without refactoring for devices)
This commit is contained in:
parent
5f9648417c
commit
669cff6571
3 changed files with 334 additions and 317 deletions
|
@ -4,25 +4,43 @@
|
|||
#include "Common/I2C.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#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<u8*>(&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<u8*>(&IOS::ave_state)[device_address.value()];
|
||||
reinterpret_cast<u8*>(&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<Common::I2CBus::State> : EnumFormatter<Common::I2CBus::State::ReadFromDevice>
|
||||
{
|
||||
static constexpr array_type names = {"Inactive", "Activating",
|
||||
"Set I2C Address", "Write Device Address",
|
||||
"Write To Device", "Read From Device"};
|
||||
constexpr formatter() : EnumFormatter(names) {}
|
||||
};
|
||||
|
|
|
@ -4,17 +4,18 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#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<I2CSlave*> 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<u8> i2c_address; // Not shifted; includes the read flag
|
||||
std::optional<u8> 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
|
||||
|
|
|
@ -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<sizeof(AVEState)> ave_ever_logged; // For logging only; not saved
|
||||
AVEState ave_state;
|
||||
std::bitset<sizeof(AVEState)> 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<u8> i2c_address; // Not shifted; includes the read flag
|
||||
std::optional<u8> 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<u8*>(&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<u8*>(&ave_state)[device_address.value()];
|
||||
reinterpret_cast<u8*>(&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<u32>(), MMIO::DirectWrite<u32>(&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<IOS::I2CBus::State> : EnumFormatter<IOS::I2CBus::State::ReadFromDevice>
|
||||
{
|
||||
static constexpr array_type names = {"Inactive", "Activating",
|
||||
"Set I2C Address", "Write Device Address",
|
||||
"Write To Device", "Read From Device"};
|
||||
constexpr formatter() : EnumFormatter(names) {}
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue