mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-04 15:19:09 +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 "Common/I2C.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#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
|
namespace Common
|
||||||
{
|
{
|
||||||
void I2CBus::AddSlave(I2CSlave* slave)
|
void I2CBusOld::AddSlave(I2CSlave* slave)
|
||||||
{
|
{
|
||||||
m_slaves.emplace_back(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());
|
m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
void I2CBus::Reset()
|
void I2CBusOld::Reset()
|
||||||
{
|
{
|
||||||
m_slaves.clear();
|
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)
|
for (auto& slave : m_slaves)
|
||||||
{
|
{
|
||||||
|
@ -36,7 +54,7 @@ int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
|
||||||
return 0;
|
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)
|
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;
|
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
|
}; // 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
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
|
||||||
namespace Common
|
namespace Common
|
||||||
{
|
{
|
||||||
class I2CBus;
|
class I2CBusOld;
|
||||||
|
|
||||||
class I2CSlave
|
class I2CSlave
|
||||||
{
|
{
|
||||||
friend I2CBus;
|
friend I2CBusOld;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual ~I2CSlave() = default;
|
virtual ~I2CSlave() = default;
|
||||||
|
@ -55,7 +56,7 @@ protected:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class I2CBus
|
class I2CBusOld
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
void AddSlave(I2CSlave* slave);
|
void AddSlave(I2CSlave* slave);
|
||||||
|
@ -72,4 +73,53 @@ private:
|
||||||
std::vector<I2CSlave*> m_slaves;
|
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
|
} // namespace Common
|
||||||
|
|
|
@ -9,11 +9,10 @@
|
||||||
|
|
||||||
#include "Common/ChunkFile.h"
|
#include "Common/ChunkFile.h"
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
#include "Common/EnumFormatter.h"
|
#include "Common/I2C.h"
|
||||||
#include "Common/Logging/Log.h"
|
#include "Common/Logging/Log.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/Debugger/Debugger_SymbolMap.h"
|
|
||||||
#include "Core/HW/DVD/DVDInterface.h"
|
#include "Core/HW/DVD/DVDInterface.h"
|
||||||
#include "Core/HW/MMIO.h"
|
#include "Core/HW/MMIO.h"
|
||||||
#include "Core/HW/ProcessorInterface.h"
|
#include "Core/HW/ProcessorInterface.h"
|
||||||
|
@ -121,59 +120,10 @@ struct AVEState
|
||||||
};
|
};
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
static_assert(sizeof(AVEState) == 0x100);
|
static_assert(sizeof(AVEState) == 0x100);
|
||||||
static AVEState ave_state;
|
AVEState ave_state;
|
||||||
static std::bitset<sizeof(AVEState)> ave_ever_logged; // For logging only; not saved
|
std::bitset<sizeof(AVEState)> ave_ever_logged;
|
||||||
|
|
||||||
// An I²C bus implementation accessed via bit-banging.
|
Common::I2CBus i2c_state;
|
||||||
// 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;
|
|
||||||
|
|
||||||
static CoreTiming::EventType* updateInterrupts;
|
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)
|
if (address == 0x00)
|
||||||
return "A/V Timings";
|
return "A/V Timings";
|
||||||
|
@ -300,36 +250,6 @@ static u32 ReadGPIOIn(Core::System& system)
|
||||||
return gpio_in.m_hex;
|
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()
|
u32 WiiIPC::GetGPIOOut()
|
||||||
{
|
{
|
||||||
// In the direction field, a '1' bit for a pin indicates that it will behave as an output (drive),
|
// 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)
|
// I²C logic for the audio/video encoder (AVE)
|
||||||
i2c_state.Update(m_system, old_value[GPIO::AVE_SCL], new_value[GPIO::AVE_SCL],
|
i2c_state.Update(old_value[GPIO::AVE_SCL], new_value[GPIO::AVE_SCL], old_value[GPIO::AVE_SDA],
|
||||||
old_value[GPIO::AVE_SDA], new_value[GPIO::AVE_SDA]);
|
new_value[GPIO::AVE_SDA]);
|
||||||
|
|
||||||
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
|
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
|
||||||
// TODO: SLOT_LED
|
// 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)
|
void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||||
{
|
{
|
||||||
mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead<u32>(), MMIO::DirectWrite<u32>(&m_ppc_msg));
|
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));
|
((m_ppc_irq_flags & INT_CAUSE_IPC_BROADWAY) == 0));
|
||||||
}
|
}
|
||||||
} // namespace IOS
|
} // 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
Add a link
Reference in a new issue