mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-04-20 11:35:54 +00:00
Merge 612ed106cd
into 93fc5c02ac
This commit is contained in:
commit
2e585d6d66
21 changed files with 1309 additions and 344 deletions
|
@ -218,6 +218,12 @@ class Flags
|
|||
{
|
||||
public:
|
||||
constexpr Flags() = default;
|
||||
constexpr ~Flags() = default;
|
||||
constexpr Flags(const Flags<T>& other) = default;
|
||||
constexpr Flags(Flags<T>&& other) noexcept = default;
|
||||
constexpr Flags<T>& operator=(const Flags<T>& other) = default;
|
||||
constexpr Flags<T>& operator=(Flags<T>&& other) noexcept = default;
|
||||
|
||||
constexpr Flags(std::initializer_list<T> bits)
|
||||
{
|
||||
for (auto bit : bits)
|
||||
|
@ -225,7 +231,13 @@ public:
|
|||
m_hex |= static_cast<std::underlying_type_t<T>>(bit);
|
||||
}
|
||||
}
|
||||
constexpr Flags(std::underlying_type_t<T> hex) : m_hex(hex) {}
|
||||
|
||||
FlagBit<T> operator[](T bit) { return FlagBit(m_hex, bit); }
|
||||
constexpr bool operator[](T bit) const
|
||||
{
|
||||
return (m_hex & static_cast<std::underlying_type_t<T>>(bit)) != 0;
|
||||
}
|
||||
|
||||
std::underlying_type_t<T> m_hex = 0;
|
||||
};
|
||||
|
|
|
@ -80,6 +80,8 @@ add_library(common
|
|||
HostDisassembler.h
|
||||
HttpRequest.cpp
|
||||
HttpRequest.h
|
||||
I2C.cpp
|
||||
I2C.h
|
||||
Image.cpp
|
||||
Image.h
|
||||
IniFile.cpp
|
||||
|
|
521
Source/Core/Common/I2C.cpp
Normal file
521
Source/Core/Common/I2C.cpp
Normal file
|
@ -0,0 +1,521 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Common/I2C.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/EnumFormatter.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Debugger/Debugger_SymbolMap.h"
|
||||
|
||||
namespace Common
|
||||
{
|
||||
void I2CBusBase::AddSlave(I2CSlave* slave)
|
||||
{
|
||||
m_slaves.emplace_back(slave);
|
||||
}
|
||||
|
||||
void I2CBusBase::RemoveSlave(I2CSlave* slave)
|
||||
{
|
||||
m_slaves.erase(std::remove(m_slaves.begin(), m_slaves.end(), slave), m_slaves.end());
|
||||
}
|
||||
|
||||
void I2CBusBase::Reset()
|
||||
{
|
||||
m_slaves.clear();
|
||||
}
|
||||
|
||||
bool I2CBusBase::StartWrite(u8 slave_addr)
|
||||
{
|
||||
bool got_ack = false;
|
||||
for (I2CSlave* slave : m_slaves)
|
||||
{
|
||||
if (slave->StartWrite(slave_addr))
|
||||
{
|
||||
if (got_ack)
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed starting write for I2C addr {:02x}",
|
||||
slave_addr);
|
||||
}
|
||||
got_ack = true;
|
||||
}
|
||||
}
|
||||
return got_ack;
|
||||
}
|
||||
|
||||
bool I2CBusBase::StartRead(u8 slave_addr)
|
||||
{
|
||||
bool got_ack = false;
|
||||
for (I2CSlave* slave : m_slaves)
|
||||
{
|
||||
if (slave->StartRead(slave_addr))
|
||||
{
|
||||
if (got_ack)
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed starting read for I2C addr {:02x}",
|
||||
slave_addr);
|
||||
}
|
||||
got_ack = true;
|
||||
}
|
||||
}
|
||||
return got_ack;
|
||||
}
|
||||
|
||||
void I2CBusBase::Stop()
|
||||
{
|
||||
for (I2CSlave* slave : m_slaves)
|
||||
slave->Stop();
|
||||
}
|
||||
|
||||
std::optional<u8> I2CBusBase::ReadByte()
|
||||
{
|
||||
std::optional<u8> byte = std::nullopt;
|
||||
for (I2CSlave* slave : m_slaves)
|
||||
{
|
||||
std::optional<u8> byte2 = slave->ReadByte();
|
||||
if (byte.has_value() && byte2.has_value())
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "Multiple slaves responded to read: {:02x} vs {:02x}", *byte, *byte2);
|
||||
}
|
||||
else if (byte2.has_value())
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "I2C: Read {:02x}", byte2.value());
|
||||
byte = byte2;
|
||||
}
|
||||
}
|
||||
return byte;
|
||||
}
|
||||
|
||||
bool I2CBusBase::WriteByte(u8 value)
|
||||
{
|
||||
bool got_ack = false;
|
||||
for (I2CSlave* slave : m_slaves)
|
||||
{
|
||||
if (slave->WriteByte(value))
|
||||
{
|
||||
if (got_ack)
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "Multiple I2C slaves ACKed write of {:02x}", value);
|
||||
}
|
||||
got_ack = true;
|
||||
}
|
||||
}
|
||||
return got_ack;
|
||||
}
|
||||
|
||||
int I2CBusSimple::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
|
||||
{
|
||||
if (!StartWrite(slave_addr))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Failed to start write to {:02x} ({:02x}, {:02x})", slave_addr, addr,
|
||||
count);
|
||||
Stop();
|
||||
return 0;
|
||||
}
|
||||
if (!WriteByte(addr))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Failed to write device address {:02x} to {:02x} ({:02x})", addr,
|
||||
slave_addr, count);
|
||||
Stop();
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (!WriteByte(data_in[i]))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC,
|
||||
"I2C: Failed to byte {} of {} starting at device address {:02x} to {:02x}", i,
|
||||
count, addr, slave_addr);
|
||||
Stop();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
Stop();
|
||||
return count;
|
||||
}
|
||||
|
||||
int I2CBusSimple::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
|
||||
{
|
||||
if (!StartWrite(slave_addr))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Failed to start write for read from {:02x} ({:02x}, {:02x})",
|
||||
slave_addr, addr, count);
|
||||
Stop();
|
||||
return 0;
|
||||
}
|
||||
if (!WriteByte(addr))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Failed to write device address {:02x} to {:02x} ({:02x})", addr,
|
||||
slave_addr, count);
|
||||
Stop();
|
||||
return 0;
|
||||
}
|
||||
// Note: No Stop() call before StartRead.
|
||||
if (!StartRead(slave_addr))
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Failed to start read from {:02x} ({:02x}, {:02x})", slave_addr,
|
||||
addr, count);
|
||||
Stop();
|
||||
return 0;
|
||||
}
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
const std::optional<u8> byte = ReadByte();
|
||||
if (!byte.has_value())
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC,
|
||||
"I2C: Failed to byte {} of {} starting at device address {:02x} from {:02x}", i,
|
||||
count, addr, slave_addr);
|
||||
Stop();
|
||||
return i;
|
||||
}
|
||||
data_out[i] = byte.value();
|
||||
}
|
||||
Stop();
|
||||
return count;
|
||||
}
|
||||
|
||||
bool I2CBus::GetSCL() const
|
||||
{
|
||||
return true; // passive pullup - no clock stretching
|
||||
}
|
||||
|
||||
bool I2CBus::GetSDA() const
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case State::Inactive:
|
||||
case State::Activating:
|
||||
default:
|
||||
return true; // passive pullup (or NACK)
|
||||
|
||||
case State::SetI2CAddress:
|
||||
case State::WriteToDevice:
|
||||
if (bit_counter < 8)
|
||||
return true; // passive pullup
|
||||
else
|
||||
return false; // ACK (if we need to NACK, we set the state to inactive)
|
||||
|
||||
case State::ReadFromDevice:
|
||||
if (bit_counter < 8)
|
||||
return ((current_byte << bit_counter) & 0x80) != 0;
|
||||
else
|
||||
return true; // passive pullup, receiver needs to ACK or NACK
|
||||
}
|
||||
}
|
||||
|
||||
void I2CBus::Start()
|
||||
{
|
||||
if (state != State::Inactive)
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Re-start I2C");
|
||||
else
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Start I2C");
|
||||
|
||||
if (bit_counter != 0)
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Start happened with a nonzero bit counter: {}", bit_counter);
|
||||
|
||||
state = State::Activating;
|
||||
bit_counter = 0;
|
||||
current_byte = 0;
|
||||
// Note: don't reset device_address, as it's re-used for reads
|
||||
}
|
||||
|
||||
void I2CBus::Stop()
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Stop I2C");
|
||||
I2CBusBase::Stop();
|
||||
|
||||
state = State::Inactive;
|
||||
bit_counter = 0;
|
||||
current_byte = 0;
|
||||
}
|
||||
|
||||
void I2CBus::Update(const bool old_scl, const bool new_scl, const bool old_sda, const bool new_sda)
|
||||
{
|
||||
if (old_scl != new_scl && old_sda != new_sda)
|
||||
{
|
||||
ERROR_LOG_FMT(WII_IPC, "Both SCL and SDA changed at the same time: SCL {} -> {} SDA {} -> {}",
|
||||
old_scl, new_scl, old_sda, new_sda);
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_scl == new_scl && old_sda == new_sda)
|
||||
return; // Nothing changed
|
||||
|
||||
if (old_scl && new_scl)
|
||||
{
|
||||
// Check for changes to SDA while the clock is high.
|
||||
if (old_sda && !new_sda)
|
||||
{
|
||||
// SDA falling edge (now pulled low) while SCL is high indicates I²C start
|
||||
Start();
|
||||
}
|
||||
else if (!old_sda && new_sda)
|
||||
{
|
||||
// SDA rising edge (now passive pullup) while SCL is high indicates I²C stop
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
else if (state != State::Inactive)
|
||||
{
|
||||
if (!old_scl && new_scl)
|
||||
{
|
||||
SCLRisingEdge(new_sda);
|
||||
}
|
||||
else if (old_scl && !new_scl)
|
||||
{
|
||||
SCLFallingEdge(new_sda);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void I2CBus::SCLRisingEdge(const bool sda)
|
||||
{
|
||||
// INFO_LOG_FMT(WII_IPC, "AVE: {} {} rising edge: {}", bit_counter, state, sda);
|
||||
// SCL rising edge indicates data clocking. For reads, we set up data at this point.
|
||||
// For writes, we instead process it on the falling edge, to better distinguish
|
||||
// the start/stop condition.
|
||||
if (state == State::ReadFromDevice && bit_counter == 0)
|
||||
{
|
||||
// Start of a read.
|
||||
const std::optional<u8> byte = ReadByte();
|
||||
if (!byte.has_value())
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "No slaves responded to I2C read");
|
||||
current_byte = 0xff;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_byte = byte.value();
|
||||
}
|
||||
}
|
||||
// Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO);
|
||||
}
|
||||
|
||||
void I2CBus::SCLFallingEdge(const bool sda)
|
||||
{
|
||||
// INFO_LOG_FMT(WII_IPC, "AVE: {} {} falling edge: {}", bit_counter, state, sda);
|
||||
// SCL falling edge is used to advance bit_counter/change states and process writes.
|
||||
if (state == State::SetI2CAddress || state == State::WriteToDevice)
|
||||
{
|
||||
if (bit_counter != 8)
|
||||
{
|
||||
current_byte <<= 1;
|
||||
if (sda)
|
||||
current_byte |= 1;
|
||||
|
||||
if (bit_counter == 7)
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Byte written: {:02x}", current_byte);
|
||||
// Write finished.
|
||||
if (state == State::SetI2CAddress)
|
||||
{
|
||||
const u8 slave_addr = current_byte >> 1;
|
||||
if (current_byte & 1)
|
||||
{
|
||||
if (StartRead(slave_addr))
|
||||
{
|
||||
// State transition handled by bit_counter >= 8, as we still need to handle the ACK
|
||||
INFO_LOG_FMT(WII_IPC, "I2C: Start read from {:02x}", slave_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State::Inactive; // NACK
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: No device responded to read from {:02x}", slave_addr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (StartWrite(slave_addr))
|
||||
{
|
||||
// State transition handled by bit_counter >= 8, as we still need to handle the ACK
|
||||
INFO_LOG_FMT(WII_IPC, "I2C: Start write to {:02x}", slave_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State::Inactive; // NACK
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: No device responded to write to {:02x}", slave_addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Actual write
|
||||
ASSERT(state == State::WriteToDevice);
|
||||
if (!WriteByte(current_byte))
|
||||
{
|
||||
state = State::Inactive;
|
||||
WARN_LOG_FMT(WII_IPC, "I2C: Write of {:02x} NACKed", current_byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state == State::Activating)
|
||||
{
|
||||
// This is triggered after the start condition.
|
||||
state = State::SetI2CAddress;
|
||||
bit_counter = 0;
|
||||
}
|
||||
else if (state != State::Inactive)
|
||||
{
|
||||
if (bit_counter >= 8)
|
||||
{
|
||||
// Finished a byte and the acknowledge signal.
|
||||
bit_counter = 0;
|
||||
if (state == State::SetI2CAddress)
|
||||
{
|
||||
// Note: current_byte is known to correspond to a valid device
|
||||
if ((current_byte & 1) == 0)
|
||||
{
|
||||
state = State::WriteToDevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
state = State::ReadFromDevice;
|
||||
}
|
||||
}
|
||||
else if (state == State::ReadFromDevice)
|
||||
{
|
||||
// Acknowledge bit for *reads*.
|
||||
if (sda)
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC, "Read NACK'd");
|
||||
state = State::Inactive;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bit_counter++;
|
||||
}
|
||||
}
|
||||
// Dolphin_Debugger::PrintCallstack(Common::Log::LogType::WII_IPC, Common::Log::LogLevel::LINFO);
|
||||
}
|
||||
|
||||
void I2CBus::DoState(PointerWrap& p)
|
||||
{
|
||||
p.Do(state);
|
||||
p.Do(bit_counter);
|
||||
p.Do(current_byte);
|
||||
// TODO: verify m_devices is the same so that the state is compatible.
|
||||
// (We don't take ownership of saving/loading m_devices, though).
|
||||
}
|
||||
|
||||
bool I2CSlaveAutoIncrementing::StartWrite(u8 slave_addr)
|
||||
{
|
||||
if (DeviceEnabled() && slave_addr == m_slave_addr)
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} write started, previously active: {}", m_slave_addr,
|
||||
m_active);
|
||||
m_active = true;
|
||||
m_device_address.reset();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool I2CSlaveAutoIncrementing::StartRead(u8 slave_addr)
|
||||
{
|
||||
if (DeviceEnabled() && slave_addr == m_slave_addr)
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "I2C Device {:02x} read started, previously active: {}", m_slave_addr,
|
||||
m_active);
|
||||
if (m_device_address.has_value())
|
||||
{
|
||||
m_active = true;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(WII_IPC,
|
||||
"I2C Device {:02x}: read attempted without having written device address",
|
||||
m_slave_addr);
|
||||
m_active = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void I2CSlaveAutoIncrementing::Stop()
|
||||
{
|
||||
m_active = false;
|
||||
m_device_address.reset();
|
||||
}
|
||||
|
||||
std::optional<u8> I2CSlaveAutoIncrementing::ReadByte()
|
||||
{
|
||||
if (m_active)
|
||||
{
|
||||
ASSERT(m_device_address.has_value()); // enforced by StartRead
|
||||
const u8 cur_addr = m_device_address.value();
|
||||
m_device_address = cur_addr + 1; // wrapping from 255 to 0 is the assumed behavior
|
||||
return ReadByte(cur_addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
bool I2CSlaveAutoIncrementing::WriteByte(u8 value)
|
||||
{
|
||||
if (m_active)
|
||||
{
|
||||
if (m_device_address.has_value())
|
||||
{
|
||||
const u8 cur_addr = m_device_address.value();
|
||||
m_device_address = cur_addr + 1; // wrapping from 255 to 0 is the assumed behavior
|
||||
WriteByte(cur_addr, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_device_address = value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void I2CSlaveAutoIncrementing::DoState(PointerWrap& p)
|
||||
{
|
||||
u8 slave_addr = m_slave_addr;
|
||||
p.Do(slave_addr);
|
||||
if (slave_addr != m_slave_addr && p.IsReadMode())
|
||||
{
|
||||
PanicAlertFmt("State incompatible: Mismatched I2C address: expected {:02x}, was {:02x}. "
|
||||
"Aborting state load.",
|
||||
m_slave_addr, slave_addr);
|
||||
p.SetVerifyMode();
|
||||
}
|
||||
p.Do(m_active);
|
||||
p.Do(m_device_address);
|
||||
}
|
||||
|
||||
}; // namespace Common
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Common::I2CBus::State> : EnumFormatter<Common::I2CBus::State::ReadFromDevice>
|
||||
{
|
||||
static constexpr array_type names = {"Inactive", "Activating", "Set I2C Address",
|
||||
"Write To Device", "Read From Device"};
|
||||
constexpr formatter() : EnumFormatter(names) {}
|
||||
};
|
154
Source/Core/Common/I2C.h
Normal file
154
Source/Core/Common/I2C.h
Normal file
|
@ -0,0 +1,154 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class PointerWrap;
|
||||
|
||||
namespace Common
|
||||
{
|
||||
class I2CSlave
|
||||
{
|
||||
public:
|
||||
virtual ~I2CSlave() = default;
|
||||
|
||||
// NOTE: slave_addr is 7 bits, i.e. it has been shifted so the read flag is not included.
|
||||
virtual bool StartWrite(u8 slave_addr) = 0;
|
||||
virtual bool StartRead(u8 slave_addr) = 0;
|
||||
virtual void Stop() = 0;
|
||||
// NOTE: std::optional is for ease of implementation. I2C does not provide a way for the
|
||||
// transmitting device to NACK a read; only the receiver can NACK.
|
||||
virtual std::optional<u8> ReadByte() = 0;
|
||||
virtual bool WriteByte(u8 value) = 0;
|
||||
|
||||
protected:
|
||||
template <typename T>
|
||||
static u8 RawRead(T* reg_data, u8 addr)
|
||||
{
|
||||
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T>);
|
||||
static_assert(0x100 == sizeof(T));
|
||||
|
||||
return reinterpret_cast<u8*>(reg_data)[addr];
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static void RawWrite(T* reg_data, u8 addr, u8 value)
|
||||
{
|
||||
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T>);
|
||||
static_assert(0x100 == sizeof(T));
|
||||
|
||||
reinterpret_cast<u8*>(reg_data)[addr] = value;
|
||||
}
|
||||
};
|
||||
|
||||
class I2CSlaveAutoIncrementing : public virtual I2CSlave
|
||||
{
|
||||
public:
|
||||
I2CSlaveAutoIncrementing(u8 slave_addr) : m_slave_addr(slave_addr) {}
|
||||
virtual ~I2CSlaveAutoIncrementing() = default;
|
||||
|
||||
bool StartWrite(u8 slave_addr) override;
|
||||
bool StartRead(u8 slave_addr) override;
|
||||
void Stop() override;
|
||||
std::optional<u8> ReadByte() override;
|
||||
bool WriteByte(u8 value) override;
|
||||
|
||||
virtual void DoState(PointerWrap& p);
|
||||
|
||||
protected:
|
||||
// i.e. should the device respond on the bus
|
||||
virtual bool DeviceEnabled() { return true; }
|
||||
virtual u8 ReadByte(u8 addr) = 0;
|
||||
virtual void WriteByte(u8 addr, u8 value) = 0;
|
||||
|
||||
private:
|
||||
const u8 m_slave_addr;
|
||||
bool m_active = false;
|
||||
std::optional<u8> m_device_address = std::nullopt;
|
||||
};
|
||||
|
||||
class I2CBusBase
|
||||
{
|
||||
public:
|
||||
virtual ~I2CBusBase() = default;
|
||||
|
||||
void AddSlave(I2CSlave* slave);
|
||||
void RemoveSlave(I2CSlave* slave);
|
||||
|
||||
void Reset();
|
||||
|
||||
protected:
|
||||
// Signals all slaves on the bus
|
||||
bool StartWrite(u8 slave_addr);
|
||||
bool StartRead(u8 slave_addr);
|
||||
void Stop();
|
||||
std::optional<u8> ReadByte();
|
||||
bool WriteByte(u8 value);
|
||||
|
||||
private:
|
||||
// Pointers are unowned:
|
||||
std::vector<I2CSlave*> m_slaves;
|
||||
};
|
||||
|
||||
class I2CBusSimple : public I2CBusBase
|
||||
{
|
||||
public:
|
||||
// TODO: change int to u16 or something
|
||||
int BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in);
|
||||
int BusRead(u8 slave_addr, u8 addr, int count, u8* data_out);
|
||||
};
|
||||
|
||||
class I2CBusForwarding : public I2CBusSimple
|
||||
{
|
||||
public:
|
||||
using I2CBusBase::ReadByte;
|
||||
using I2CBusBase::StartRead;
|
||||
using I2CBusBase::StartWrite;
|
||||
using I2CBusBase::Stop;
|
||||
using I2CBusBase::WriteByte;
|
||||
};
|
||||
|
||||
// An I²C bus implementation accessed via bit-banging.
|
||||
// A few assumptions and limitations exist:
|
||||
// - All devices support both writes and reads.
|
||||
// - Timing is not implemented at all; the clock signal can be changed as fast as needed.
|
||||
// - Devices are not allowed to stretch the clock signal. (Nintendo's write code does not seem to
|
||||
// implement clock stretching in any case, though some homebrew does.)
|
||||
// - 10-bit addressing and other reserved addressing modes are not implemented.
|
||||
class I2CBus : public I2CBusBase
|
||||
{
|
||||
public:
|
||||
enum class State
|
||||
{
|
||||
Inactive,
|
||||
Activating,
|
||||
SetI2CAddress,
|
||||
WriteToDevice,
|
||||
ReadFromDevice,
|
||||
};
|
||||
|
||||
State state;
|
||||
u8 bit_counter;
|
||||
u8 current_byte;
|
||||
|
||||
void Update(const bool old_scl, const bool new_scl, const bool old_sda, const bool new_sda);
|
||||
bool GetSCL() const;
|
||||
bool GetSDA() const;
|
||||
|
||||
void DoState(PointerWrap& p);
|
||||
|
||||
private:
|
||||
void Start();
|
||||
void Stop();
|
||||
void SCLRisingEdge(const bool sda);
|
||||
void SCLFallingEdge(const bool sda);
|
||||
bool WriteExpected() const;
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -3,8 +3,13 @@
|
|||
|
||||
#include "Core/HW/WII_IPC.h"
|
||||
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <string>
|
||||
|
||||
#include "Common/ChunkFile.h"
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/I2C.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/CoreTiming.h"
|
||||
|
@ -33,7 +38,8 @@ enum
|
|||
IPC_ARMMSG = 0x08,
|
||||
IPC_ARMCTRL = 0x0c,
|
||||
|
||||
PPCSPEED = 0x18,
|
||||
VI1CFG = 0x18,
|
||||
VIDIM = 0x1c,
|
||||
VISOLID = 0x24,
|
||||
|
||||
PPC_IRQFLAG = 0x30,
|
||||
|
@ -41,17 +47,31 @@ enum
|
|||
ARM_IRQFLAG = 0x38,
|
||||
ARM_IRQMASK = 0x3c,
|
||||
|
||||
SRNPROT = 0x60,
|
||||
AHBPROT = 0x64,
|
||||
|
||||
// Broadway GPIO access. We don't currently implement the interrupts.
|
||||
GPIOB_OUT = 0xc0,
|
||||
GPIOB_DIR = 0xc4,
|
||||
GPIOB_IN = 0xc8,
|
||||
|
||||
GPIOB_INTLVL = 0xcc,
|
||||
GPIOB_INTFLAG = 0xd0,
|
||||
GPIOB_INTMASK = 0xd4,
|
||||
GPIOB_STRAPS = 0xd8,
|
||||
// Starlet GPIO access. We emulate some of these for /dev/di.
|
||||
GPIO_ENABLE = 0xdc,
|
||||
GPIO_OUT = 0xe0,
|
||||
GPIO_DIR = 0xe4,
|
||||
GPIO_IN = 0xe8,
|
||||
GPIO_INTLVL = 0xec,
|
||||
GPIO_INTFLAG = 0xf0,
|
||||
GPIO_INTMASK = 0xf4,
|
||||
GPIO_STRAPS = 0xf8,
|
||||
GPIO_OWNER = 0xfc,
|
||||
|
||||
HW_RESETS = 0x194,
|
||||
COMPAT = 0x180,
|
||||
RESETS = 0x194,
|
||||
|
||||
UNK_180 = 0x180,
|
||||
UNK_1CC = 0x1cc,
|
||||
UNK_1D0 = 0x1d0,
|
||||
};
|
||||
|
@ -60,6 +80,143 @@ enum
|
|||
static constexpr Common::Flags<GPIO> gpio_owner = {GPIO::SLOT_LED, GPIO::SLOT_IN, GPIO::SENSOR_BAR,
|
||||
GPIO::DO_EJECT, GPIO::AVE_SCL, GPIO::AVE_SDA};
|
||||
|
||||
static u32 resets;
|
||||
|
||||
#pragma pack(1)
|
||||
struct AVEState
|
||||
{
|
||||
// See https://wiibrew.org/wiki/Hardware/AV_Encoder#Registers_description
|
||||
// (note that the code snippet indicates that values are big-endian)
|
||||
u8 timings; // 0x00
|
||||
u8 video_output_config; // 0x01
|
||||
u8 vertical_blanking_interval_control; // 0x02
|
||||
u8 composite_trap_filter_control; // 0x03
|
||||
u8 audio_video_output_control; // 0x04
|
||||
u8 cgms_high, cgms_low; // 0x05-0x06
|
||||
u8 pad1; // 0x07
|
||||
u8 wss_high, wss_low; // 0x08-0x09, Widescreen signaling?
|
||||
u8 rgb_color_output_control; // 0x0A, only used when video_output_config is DEBUG (3)?
|
||||
std::array<u8, 5> pad2; // 0x0B-0x0F
|
||||
std::array<u8, 33> gamma_coefficients; // 0x10-0x30
|
||||
std::array<u8, 15> pad3; // 0x31-0x3F
|
||||
std::array<u8, 26> macrovision_code; // 0x40-0x59, analog copy protection
|
||||
std::array<u8, 8> pad4; // 0x5A-0x61
|
||||
u8 rgb_switch; // 0x62, swap blue and red channels
|
||||
std::array<u8, 2> pad5; // 0x63-0x64
|
||||
u8 color_dac; // 0x65
|
||||
u8 pad6; // 0x66
|
||||
u8 color_test; // 0x67, display a color test pattern
|
||||
std::array<u8, 2> pad7; // 0x68-0x69
|
||||
u8 ccsel; // 0x6A
|
||||
std::array<u8, 2> pad8; // 0x6B-0x6C
|
||||
u8 mute; // 0x6D
|
||||
u8 rgb_output_filter; // 0x6E
|
||||
std::array<u8, 2> pad9; // 0x6F-0x70
|
||||
u8 right_volume; // 0x71
|
||||
u8 left_volume; // 0x72
|
||||
std::array<u8, 7> pad10; // 0x73-0x79
|
||||
std::array<u8, 4> closed_captioning; // 0x7A-0x7D
|
||||
std::array<u8, 130> pad11; // 0x7E-0xFF
|
||||
};
|
||||
#pragma pack()
|
||||
static_assert(sizeof(AVEState) == 0x100);
|
||||
|
||||
std::string GetAVERegisterName(u8 address)
|
||||
{
|
||||
if (address == 0x00)
|
||||
return "A/V Timings";
|
||||
else if (address == 0x01)
|
||||
return "Video Output configuration";
|
||||
else if (address == 0x02)
|
||||
return "Vertical blanking interval (VBI) control";
|
||||
else if (address == 0x03)
|
||||
return "Composite Video Trap Filter control";
|
||||
else if (address == 0x04)
|
||||
return "A/V output control";
|
||||
else if (address == 0x05 || address == 0x06)
|
||||
return "CGMS protection";
|
||||
else if (address == 0x08 || address == 0x09)
|
||||
return "WSS (Widescreen signaling)";
|
||||
else if (address == 0x0A)
|
||||
return "RGB color output control";
|
||||
else if (address >= 0x10 && address <= 0x30)
|
||||
return "Gamma coefficients";
|
||||
else if (address >= 0x40 && address <= 0x59)
|
||||
return "Macrovision code";
|
||||
else if (address == 0x62)
|
||||
return "RGB switch control";
|
||||
else if (address == 0x65)
|
||||
return "Color DAC control";
|
||||
else if (address == 0x67)
|
||||
return "Color Test";
|
||||
else if (address == 0x6A)
|
||||
return "CCSEL";
|
||||
else if (address == 0x6D)
|
||||
return "Audio mute control";
|
||||
else if (address == 0x6E)
|
||||
return "RGB output filter";
|
||||
else if (address == 0x71)
|
||||
return "Audio stereo output control - right volume";
|
||||
else if (address == 0x72)
|
||||
return "Audio stereo output control - left volume";
|
||||
else if (address >= 0x7a && address <= 0x7d)
|
||||
return "Closed Captioning control";
|
||||
else
|
||||
return fmt::format("Unknown ({:02x})", address);
|
||||
}
|
||||
|
||||
class AVEDevice : public Common::I2CSlaveAutoIncrementing
|
||||
{
|
||||
public:
|
||||
AVEDevice() : I2CSlaveAutoIncrementing(0x70) {}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_registers = {};
|
||||
ave_ever_logged = {};
|
||||
}
|
||||
|
||||
void DoState(PointerWrap& p) override
|
||||
{
|
||||
I2CSlaveAutoIncrementing::DoState(p);
|
||||
p.Do(m_registers);
|
||||
}
|
||||
|
||||
AVEState m_registers{};
|
||||
|
||||
protected:
|
||||
u8 ReadByte(u8 addr) override
|
||||
{
|
||||
const u8 result = RawRead(&m_registers, addr);
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Read from {:02x} ({}) -> {:02x}", addr, GetAVERegisterName(addr),
|
||||
result);
|
||||
return result;
|
||||
}
|
||||
void WriteByte(u8 addr, u8 value) override
|
||||
{
|
||||
const u8 old_value = RawRead(&m_registers, addr);
|
||||
RawWrite(&m_registers, addr, value);
|
||||
|
||||
if (old_value != value || !ave_ever_logged[addr])
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "AVE: Write to {:02x} ({}): {:02x} -> {:02x}", addr,
|
||||
GetAVERegisterName(addr), old_value, value);
|
||||
ave_ever_logged[addr] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
DEBUG_LOG_FMT(WII_IPC, "AVE: Write to {:02x} ({}): {:02x}", addr, GetAVERegisterName(addr),
|
||||
value);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::bitset<sizeof(AVEState)> ave_ever_logged{}; // logging only, not saved
|
||||
};
|
||||
AVEDevice ave_state;
|
||||
|
||||
Common::I2CBus i2c_state;
|
||||
|
||||
WiiIPC::WiiIPC(Core::System& system) : m_system(system)
|
||||
{
|
||||
}
|
||||
|
@ -77,6 +234,8 @@ void WiiIPC::DoState(PointerWrap& p)
|
|||
p.Do(m_arm_irq_masks);
|
||||
p.Do(m_gpio_dir);
|
||||
p.Do(m_gpio_out);
|
||||
i2c_state.DoState(p);
|
||||
ave_state.DoState(p);
|
||||
p.Do(m_resets);
|
||||
}
|
||||
|
||||
|
@ -91,8 +250,8 @@ void WiiIPC::InitState()
|
|||
m_arm_irq_flags = 0;
|
||||
m_arm_irq_masks = 0;
|
||||
|
||||
// The only inputs are POWER, EJECT_BTN, SLOT_IN, and EEP_MISO; Broadway only has access to
|
||||
// SLOT_IN
|
||||
// The only inputs are POWER, EJECT_BTN, SLOT_IN, EEP_MISO, and sometimes AVE_SCL and AVE_SDA;
|
||||
// Broadway only has access to SLOT_IN, AVE_SCL, and AVE_SDA.
|
||||
m_gpio_dir = {
|
||||
GPIO::POWER, GPIO::SHUTDOWN, GPIO::FAN, GPIO::DC_DC, GPIO::DI_SPIN, GPIO::SLOT_LED,
|
||||
GPIO::SENSOR_BAR, GPIO::DO_EJECT, GPIO::EEP_CS, GPIO::EEP_CLK, GPIO::EEP_MOSI, GPIO::AVE_SCL,
|
||||
|
@ -106,6 +265,12 @@ void WiiIPC::InitState()
|
|||
m_resets = 0xffffffff;
|
||||
|
||||
m_ppc_irq_masks |= INT_CAUSE_IPC_BROADWAY;
|
||||
|
||||
ave_state.Reset();
|
||||
i2c_state = {};
|
||||
ave_state.Reset();
|
||||
ave_state.m_registers.video_output_config = 0x23;
|
||||
i2c_state.AddSlave(&ave_state);
|
||||
}
|
||||
|
||||
void WiiIPC::Init()
|
||||
|
@ -125,6 +290,44 @@ void WiiIPC::Shutdown()
|
|||
{
|
||||
}
|
||||
|
||||
u32 WiiIPC::ReadGPIOIn()
|
||||
{
|
||||
Common::Flags<GPIO> gpio_in;
|
||||
gpio_in[GPIO::SLOT_IN] = m_system.GetDVDInterface().IsDiscInside();
|
||||
gpio_in[GPIO::AVE_SCL] = i2c_state.GetSCL();
|
||||
gpio_in[GPIO::AVE_SDA] = i2c_state.GetSDA();
|
||||
return gpio_in.m_hex;
|
||||
}
|
||||
|
||||
u32 WiiIPC::GetGPIOOut()
|
||||
{
|
||||
// In the direction field, a '1' bit for a pin indicates that it will behave as an output (drive),
|
||||
// while a '0' bit tristates the pin and it becomes a high-impedance input.
|
||||
// In practice this means that (at least for the AVE I²C pins) a 1 is output when the pin is an
|
||||
// input. (RVLoader depends on this.)
|
||||
// https://github.com/Aurelio92/RVLoader/blob/75732f248019f589deb1109bba7b5323a8afaadf/source/i2c.c#L101-L109
|
||||
return (m_gpio_out.m_hex | ~(m_gpio_dir.m_hex)) & (ReadGPIOIn() | m_gpio_dir.m_hex);
|
||||
}
|
||||
|
||||
void WiiIPC::GPIOOutChanged(u32 old_value_hex)
|
||||
{
|
||||
const Common::Flags<GPIO> old_value(old_value_hex);
|
||||
const Common::Flags<GPIO> new_value(GetGPIOOut());
|
||||
|
||||
if (new_value[GPIO::DO_EJECT])
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
|
||||
m_system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{m_system}, DVD::EjectCause::Software);
|
||||
}
|
||||
|
||||
// I²C logic for the audio/video encoder (AVE)
|
||||
i2c_state.Update(old_value[GPIO::AVE_SCL], new_value[GPIO::AVE_SCL], old_value[GPIO::AVE_SDA],
|
||||
new_value[GPIO::AVE_SDA]);
|
||||
|
||||
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
|
||||
// TODO: SLOT_LED
|
||||
}
|
||||
|
||||
void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
||||
{
|
||||
mmio->Register(base | IPC_PPCMSG, MMIO::InvalidRead<u32>(), MMIO::DirectWrite<u32>(&m_ppc_msg));
|
||||
|
@ -172,27 +375,22 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
mmio->Register(base | GPIOB_OUT, MMIO::DirectRead<u32>(&m_gpio_out.m_hex),
|
||||
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
const u32 old_out = wii_ipc.GetGPIOOut();
|
||||
wii_ipc.m_gpio_out.m_hex =
|
||||
(val & gpio_owner.m_hex) | (wii_ipc.m_gpio_out.m_hex & ~gpio_owner.m_hex);
|
||||
if (wii_ipc.m_gpio_out[GPIO::DO_EJECT])
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
|
||||
system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{system},
|
||||
DVD::EjectCause::Software);
|
||||
}
|
||||
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
|
||||
// TODO: AVE, SLOT_LED
|
||||
wii_ipc.GPIOOutChanged(old_out);
|
||||
}));
|
||||
mmio->Register(base | GPIOB_DIR, MMIO::DirectRead<u32>(&m_gpio_dir.m_hex),
|
||||
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
const u32 old_out = wii_ipc.GetGPIOOut();
|
||||
wii_ipc.m_gpio_dir.m_hex =
|
||||
(val & gpio_owner.m_hex) | (wii_ipc.m_gpio_dir.m_hex & ~gpio_owner.m_hex);
|
||||
wii_ipc.GPIOOutChanged(old_out);
|
||||
}));
|
||||
mmio->Register(base | GPIOB_IN, MMIO::ComplexRead<u32>([](Core::System& system, u32) {
|
||||
Common::Flags<GPIO> gpio_in;
|
||||
gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside();
|
||||
return gpio_in.m_hex;
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
return wii_ipc.ReadGPIOIn();
|
||||
}),
|
||||
MMIO::Nop<u32>());
|
||||
// Starlet GPIO registers, not normally accessible by PPC (but they can be depending on how
|
||||
|
@ -209,31 +407,26 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
mmio->Register(base | GPIO_OUT, MMIO::DirectRead<u32>(&m_gpio_out.m_hex),
|
||||
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
const u32 old_out = wii_ipc.GetGPIOOut();
|
||||
wii_ipc.m_gpio_out.m_hex =
|
||||
(wii_ipc.m_gpio_out.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex);
|
||||
if (wii_ipc.m_gpio_out[GPIO::DO_EJECT])
|
||||
{
|
||||
INFO_LOG_FMT(WII_IPC, "Ejecting disc due to GPIO write");
|
||||
system.GetDVDInterface().EjectDisc(Core::CPUThreadGuard{system},
|
||||
DVD::EjectCause::Software);
|
||||
}
|
||||
// SENSOR_BAR is checked by WiimoteEmu::CameraLogic
|
||||
// TODO: AVE, SLOT_LED
|
||||
(val & ~gpio_owner.m_hex) | (wii_ipc.m_gpio_out.m_hex & gpio_owner.m_hex);
|
||||
wii_ipc.GPIOOutChanged(old_out);
|
||||
}));
|
||||
mmio->Register(base | GPIO_DIR, MMIO::DirectRead<u32>(&m_gpio_dir.m_hex),
|
||||
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
const u32 old_out = wii_ipc.GetGPIOOut();
|
||||
wii_ipc.m_gpio_dir.m_hex =
|
||||
(wii_ipc.m_gpio_dir.m_hex & gpio_owner.m_hex) | (val & ~gpio_owner.m_hex);
|
||||
(val & gpio_owner.m_hex) | (wii_ipc.m_gpio_dir.m_hex & ~gpio_owner.m_hex);
|
||||
wii_ipc.GPIOOutChanged(old_out);
|
||||
}));
|
||||
mmio->Register(base | GPIO_IN, MMIO::ComplexRead<u32>([](Core::System& system, u32) {
|
||||
Common::Flags<GPIO> gpio_in;
|
||||
gpio_in[GPIO::SLOT_IN] = system.GetDVDInterface().IsDiscInside();
|
||||
return gpio_in.m_hex;
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
return wii_ipc.ReadGPIOIn();
|
||||
}),
|
||||
MMIO::Nop<u32>());
|
||||
|
||||
mmio->Register(base | HW_RESETS, MMIO::DirectRead<u32>(&m_resets),
|
||||
mmio->Register(base | RESETS, MMIO::DirectRead<u32>(&m_resets),
|
||||
MMIO::ComplexWrite<u32>([](Core::System& system, u32, u32 val) {
|
||||
// A reset occurs when the corresponding bit is cleared
|
||||
auto& wii_ipc = system.GetWiiIPC();
|
||||
|
@ -249,9 +442,10 @@ void WiiIPC::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
|
|||
}));
|
||||
|
||||
// Register some stubbed/unknown MMIOs required to make Wii games work.
|
||||
mmio->Register(base | PPCSPEED, MMIO::InvalidRead<u32>(), MMIO::Nop<u32>());
|
||||
mmio->Register(base | VISOLID, MMIO::InvalidRead<u32>(), MMIO::Nop<u32>());
|
||||
mmio->Register(base | UNK_180, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
|
||||
mmio->Register(base | VI1CFG, MMIO::InvalidRead<u32>(), MMIO::InvalidWrite<u32>());
|
||||
mmio->Register(base | VIDIM, MMIO::InvalidRead<u32>(), MMIO::InvalidWrite<u32>());
|
||||
mmio->Register(base | VISOLID, MMIO::InvalidRead<u32>(), MMIO::InvalidWrite<u32>());
|
||||
mmio->Register(base | COMPAT, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
|
||||
mmio->Register(base | UNK_1CC, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
|
||||
mmio->Register(base | UNK_1D0, MMIO::Constant<u32>(0), MMIO::Nop<u32>());
|
||||
}
|
||||
|
|
|
@ -140,6 +140,9 @@ private:
|
|||
|
||||
static void UpdateInterruptsCallback(Core::System& system, u64 userdata, s64 cycles_late);
|
||||
void UpdateInterrupts();
|
||||
u32 ReadGPIOIn();
|
||||
u32 GetGPIOOut();
|
||||
void GPIOOutChanged(u32 old_value_hex);
|
||||
|
||||
u32 m_ppc_msg = 0;
|
||||
u32 m_arm_msg = 0;
|
||||
|
|
|
@ -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<CameraPoint, CameraLogic::NUM_POINTS>
|
||||
|
|
|
@ -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<CameraPoint, NUM_POINTS> GetCameraPoints(const Common::Matrix44& transform,
|
||||
Common::Vec2 field_of_view);
|
||||
void Update(const std::array<CameraPoint, NUM_POINTS>& 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{};
|
||||
|
||||
|
|
|
@ -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<u8> 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())
|
||||
|
|
|
@ -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<u8> 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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/HW/WiimoteEmu/I2CBus.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace WiimoteEmu
|
||||
{
|
||||
void I2CBus::AddSlave(I2CSlave* slave)
|
||||
{
|
||||
m_slaves.emplace_back(slave);
|
||||
}
|
||||
|
||||
void I2CBus::RemoveSlave(I2CSlave* slave)
|
||||
{
|
||||
std::erase(m_slaves, slave);
|
||||
}
|
||||
|
||||
void I2CBus::Reset()
|
||||
{
|
||||
m_slaves.clear();
|
||||
}
|
||||
|
||||
int I2CBus::BusRead(u8 slave_addr, u8 addr, int count, u8* data_out)
|
||||
{
|
||||
for (auto& slave : m_slaves)
|
||||
{
|
||||
auto const bytes_read = slave->BusRead(slave_addr, addr, count, data_out);
|
||||
|
||||
// A slave responded, we are done.
|
||||
if (bytes_read)
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int I2CBus::BusWrite(u8 slave_addr, u8 addr, int count, const u8* data_in)
|
||||
{
|
||||
for (auto& slave : m_slaves)
|
||||
{
|
||||
auto const bytes_written = slave->BusWrite(slave_addr, addr, count, data_in);
|
||||
|
||||
// A slave responded, we are done.
|
||||
if (bytes_written)
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace WiimoteEmu
|
|
@ -1,75 +0,0 @@
|
|||
// Copyright 2019 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#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 <typename T>
|
||||
static int RawRead(T* reg_data, u8 addr, int count, u8* data_out)
|
||||
{
|
||||
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T>);
|
||||
static_assert(0x100 == sizeof(T));
|
||||
|
||||
// TODO: addr wraps around after 0xff
|
||||
|
||||
u8* src = reinterpret_cast<u8*>(reg_data) + addr;
|
||||
count = std::min(count, int(reinterpret_cast<u8*>(reg_data + 1) - src));
|
||||
|
||||
std::copy_n(src, count, data_out);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static int RawWrite(T* reg_data, u8 addr, int count, const u8* data_in)
|
||||
{
|
||||
static_assert(std::is_standard_layout_v<T> && std::is_trivially_copyable_v<T>);
|
||||
static_assert(0x100 == sizeof(T));
|
||||
|
||||
// TODO: addr wraps around after 0xff
|
||||
|
||||
u8* dst = reinterpret_cast<u8*>(reg_data) + addr;
|
||||
count = std::min(count, int(reinterpret_cast<u8*>(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<I2CSlave*> m_slaves;
|
||||
};
|
||||
|
||||
} // namespace WiimoteEmu
|
|
@ -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<u8> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
#include <array>
|
||||
|
||||
#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<u8> 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
|
||||
|
|
|
@ -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<u8> 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u8> ReadByte() override;
|
||||
bool WriteByte(u8 value) override;
|
||||
|
||||
Register reg_data{};
|
||||
|
||||
|
@ -75,6 +78,9 @@ private:
|
|||
ControllerEmu::SettingValue<double> m_speaker_pan_setting;
|
||||
|
||||
bool m_speaker_enabled = false;
|
||||
|
||||
bool m_i2c_active = false;
|
||||
std::optional<u8> m_device_address = std::nullopt;
|
||||
};
|
||||
|
||||
} // namespace WiimoteEmu
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ static size_t s_state_writes_in_queue;
|
|||
static std::condition_variable s_state_write_queue_is_empty;
|
||||
|
||||
// Don't forget to increase this after doing changes on the savestate system
|
||||
constexpr u32 STATE_VERSION = 169; // Last changed in PR 13074
|
||||
constexpr u32 STATE_VERSION = 170; // Last changed in PR 10863
|
||||
|
||||
// Increase this if the StateExtendedHeader definition changes
|
||||
constexpr u32 EXTENDED_HEADER_VERSION = 1; // Last changed in PR 12217
|
||||
|
|
|
@ -121,6 +121,7 @@
|
|||
<ClInclude Include="Common\HostDisassembler.h" />
|
||||
<ClInclude Include="Common\HRWrap.h" />
|
||||
<ClInclude Include="Common\HttpRequest.h" />
|
||||
<ClInclude Include="Common\I2C.h" />
|
||||
<ClInclude Include="Common\Image.h" />
|
||||
<ClInclude Include="Common\IniFile.h" />
|
||||
<ClInclude Include="Common\Inline.h" />
|
||||
|
@ -813,6 +814,7 @@
|
|||
<ClCompile Include="Common\HostDisassembler.cpp" />
|
||||
<ClCompile Include="Common\HRWrap.cpp" />
|
||||
<ClCompile Include="Common\HttpRequest.cpp" />
|
||||
<ClCompile Include="Common\I2C.cpp" />
|
||||
<ClCompile Include="Common\Image.cpp" />
|
||||
<ClCompile Include="Common\IniFile.cpp" />
|
||||
<ClCompile Include="Common\IOFile.cpp" />
|
||||
|
|
Loading…
Add table
Reference in a new issue