Revert "hoc-clk: add live vdd2, live boost clock and basic pwm dimming"
This reverts commit 15b7df8ef1.
This commit is contained in:
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* NOTE: This file is auto-generated by i2cgen.py, do not edit manually. */
|
||||
|
||||
constexpr inline const I2cDeviceDefinition I2c1DeviceList[] = {
|
||||
{ DeviceCode_ClassicController, 0x52 },
|
||||
{ DeviceCode_Nct72, 0x4C },
|
||||
{ DeviceCode_Alc5639, 0x1C },
|
||||
{ DeviceCode_Bq24193, 0x6B },
|
||||
{ DeviceCode_Max17050, 0x36 },
|
||||
{ DeviceCode_Bm92t30mwv, 0x18 },
|
||||
};
|
||||
|
||||
constexpr inline const I2cDeviceDefinition I2c2DeviceList[] = {
|
||||
{ DeviceCode_Ina226Vdd15v0Hb, 0x40 },
|
||||
{ DeviceCode_Ina226VsysCpuDs, 0x41 },
|
||||
{ DeviceCode_Ina226VsysGpuDs, 0x44 },
|
||||
{ DeviceCode_Ina226VsysDdrDs, 0x45 },
|
||||
{ DeviceCode_Ina226VsysAp, 0x46 },
|
||||
{ DeviceCode_Ina226VsysBlDs, 0x47 },
|
||||
{ DeviceCode_Bh1730, 0x29 },
|
||||
{ DeviceCode_Ina226VsysCore, 0x48 },
|
||||
{ DeviceCode_Ina226Soc1V8, 0x49 },
|
||||
{ DeviceCode_Ina226Lpddr1V8, 0x4A },
|
||||
{ DeviceCode_Ina226Reg1V32, 0x4B },
|
||||
{ DeviceCode_Ina226Vdd3V3Sys, 0x4D },
|
||||
{ DeviceCode_Ina226VddDdr0V6, 0x4E },
|
||||
{ DeviceCode_HoagNfcIc, 0x08 },
|
||||
};
|
||||
|
||||
constexpr inline const I2cDeviceDefinition I2c3DeviceList[] = {
|
||||
{ DeviceCode_Ftm3bd56, 0x49 },
|
||||
};
|
||||
|
||||
constexpr inline const I2cDeviceDefinition I2c4DeviceList[] = {
|
||||
{ DeviceCode_HdmiDdc, 0x50 },
|
||||
{ DeviceCode_HdmiScdc, 0x54 },
|
||||
{ DeviceCode_HdmiHdcp, 0x3A },
|
||||
};
|
||||
|
||||
constexpr inline const I2cDeviceDefinition I2c5DeviceList[] = {
|
||||
{ DeviceCode_Max77620Rtc, 0x68 },
|
||||
{ DeviceCode_Max77620Pmic, 0x3C },
|
||||
{ DeviceCode_Max77621Cpu, 0x1B },
|
||||
{ DeviceCode_Max77621Gpu, 0x1C },
|
||||
{ DeviceCode_Fan53528, 0x52 },
|
||||
{ DeviceCode_Max77812_3, 0x31 },
|
||||
{ DeviceCode_Max77812_2, 0x33 },
|
||||
{ DeviceCode_PmicUnknownAula_4_18, 0x18 },
|
||||
};
|
||||
|
||||
constexpr inline const I2cBusDefinition I2cBusList[] = {
|
||||
{ DeviceCode_I2c1, 0x7000C000, 0x100, SpeedMode_Standard, 70, I2c1DeviceList, util::size(I2c1DeviceList) },
|
||||
{ DeviceCode_I2c2, 0x7000C400, 0x100, SpeedMode_Fast, 116, I2c2DeviceList, util::size(I2c2DeviceList) },
|
||||
{ DeviceCode_I2c3, 0x7000C500, 0x100, SpeedMode_Fast, 124, I2c3DeviceList, util::size(I2c3DeviceList) },
|
||||
{ DeviceCode_I2c4, 0x7000C700, 0x100, SpeedMode_Standard, 152, I2c4DeviceList, util::size(I2c4DeviceList) },
|
||||
{ DeviceCode_I2c5, 0x7000D000, 0x100, SpeedMode_Fast, 85, I2c5DeviceList, util::size(I2c5DeviceList) },
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "impl/i2c_bus_manager.hpp"
|
||||
#include "impl/i2c_device_property_manager.hpp"
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx {
|
||||
|
||||
namespace {
|
||||
|
||||
struct I2cDeviceDefinition {
|
||||
DeviceCode device_code;
|
||||
u8 slave_address;
|
||||
};
|
||||
|
||||
struct I2cBusDefinition {
|
||||
DeviceCode device_code;
|
||||
dd::PhysicalAddress registers_phys_addr;
|
||||
size_t registers_size;
|
||||
SpeedMode speed_mode;
|
||||
os::InterruptName interrupt_name;
|
||||
const I2cDeviceDefinition *devices;
|
||||
size_t num_devices;
|
||||
|
||||
constexpr bool IsPowerBus() const {
|
||||
return this->device_code == DeviceCode_I2c5;
|
||||
}
|
||||
};
|
||||
|
||||
#include "i2c_bus_device_map.inc"
|
||||
|
||||
void CheckSpeedMode(SpeedMode speed_mode) {
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_Standard: break;
|
||||
case SpeedMode_Fast: break;
|
||||
case SpeedMode_FastPlus: break;
|
||||
case SpeedMode_HighSpeed: break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize(impl::I2cBusAccessorManager &bus_manager, impl::I2cDevicePropertyManager &device_manager) {
|
||||
/* Create an accessor for each bus. */
|
||||
for (const auto &bus_def : I2cBusList) {
|
||||
/* Check that the speed mode is valid. */
|
||||
CheckSpeedMode(bus_def.speed_mode);
|
||||
|
||||
/* Find the bus. */
|
||||
auto *bus = bus_manager.Find([&bus_def](const auto &it) {
|
||||
return it.GetRegistersPhysicalAddress() == bus_def.registers_phys_addr;
|
||||
});
|
||||
|
||||
/* If the bus doesn't exist, create it. */
|
||||
if (bus == nullptr) {
|
||||
/* Allocate the bus. */
|
||||
bus = bus_manager.Allocate();
|
||||
|
||||
/* Initialize the bus. */
|
||||
bus->Initialize(bus_def.registers_phys_addr, bus_def.registers_size, bus_def.interrupt_name, bus_def.IsPowerBus(), bus_def.speed_mode);
|
||||
|
||||
/* Register the bus. */
|
||||
i2c::driver::RegisterDriver(bus);
|
||||
}
|
||||
|
||||
/* Set the bus's device code. */
|
||||
bus->RegisterDeviceCode(bus_def.device_code);
|
||||
|
||||
/* Allocate and register the devices for the bus. */
|
||||
for (size_t i = 0; i < bus_def.num_devices; ++i) {
|
||||
/* Get the device definition. */
|
||||
const auto &entry = bus_def.devices[i];
|
||||
|
||||
/* Allocate the device. */
|
||||
I2cDeviceProperty *device = device_manager.Allocate(entry.slave_address, AddressingMode_SevenBit);
|
||||
|
||||
/* Register the device with our bus. */
|
||||
bus->RegisterDevice(device);
|
||||
|
||||
/* Register the device code with our driver. */
|
||||
R_ABORT_UNLESS(i2c::driver::RegisterDeviceCode(entry.device_code, device));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constinit util::TypedStorage<impl::I2cBusAccessorManager> g_bus_accessor_manager = {};
|
||||
constinit util::TypedStorage<impl::I2cDevicePropertyManager> g_device_manager = {};
|
||||
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
/* Initialize managers. */
|
||||
util::ConstructAt(g_bus_accessor_manager, ddsf::GetMemoryResource());
|
||||
util::ConstructAt(g_device_manager, ddsf::GetMemoryResource());
|
||||
|
||||
return Initialize(util::GetReference(g_bus_accessor_manager), util::GetReference(g_device_manager));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,773 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline TimeSpan Timeout = TimeSpan::FromMilliSeconds(100);
|
||||
|
||||
#define IO_PACKET_BITS_MASK(NAME) REG_NAMED_BITS_MASK (_IMPL_IO_PACKET_, NAME)
|
||||
#define IO_PACKET_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (_IMPL_IO_PACKET_, NAME, VALUE)
|
||||
#define IO_PACKET_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (_IMPL_IO_PACKET_, NAME, ENUM)
|
||||
#define IO_PACKET_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(_IMPL_IO_PACKET_, NAME, __COND__, TRUE_ENUM, FALSE_ENUM)
|
||||
|
||||
#define DEFINE_IO_PACKET_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (_IMPL_IO_PACKET_, NAME, __OFFSET__, __WIDTH__)
|
||||
#define DEFINE_IO_PACKET_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE)
|
||||
#define DEFINE_IO_PACKET_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE)
|
||||
#define DEFINE_IO_PACKET_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN)
|
||||
#define DEFINE_IO_PACKET_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (_IMPL_IO_PACKET_, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN)
|
||||
|
||||
DEFINE_IO_PACKET_REG_THREE_BIT_ENUM(HEADER_WORD0_PKT_TYPE, 0, REQUEST, RESPONSE, INTERRUPT, STOP, RSVD4, RSVD5, RSVD6, RSVD7);
|
||||
DEFINE_IO_PACKET_REG_FOUR_BIT_ENUM(HEADER_WORD0_PROTOCOL, 4, RSVD0, I2C, RSVD2, RSVD3, RSVD4, RSVD5, RSVD6, RSVD7, RSVD8, RSVD9, RSVD10, RSVD11, RSVD12, RSVD13, RSVD14, RSVD15)
|
||||
DEFINE_IO_PACKET_REG(HEADER_WORD0_CONTROLLER_ID, 12, 4);
|
||||
DEFINE_IO_PACKET_REG(HEADER_WORD0_PKT_ID, 16, 8);
|
||||
DEFINE_IO_PACKET_REG_TWO_BIT_ENUM(HEADER_WORD0_PROT_HDR_SZ, 28, 1_WORD, 2_WORD, 3_WORD, 4_WORD);
|
||||
|
||||
DEFINE_IO_PACKET_REG(HEADER_WORD1_PAYLOAD_SIZE, 0, 12);
|
||||
|
||||
DEFINE_IO_PACKET_REG(PROTOCOL_HEADER_SLAVE_ADDR, 0, 10);
|
||||
DEFINE_IO_PACKET_REG(PROTOCOL_HEADER_HS_MASTER_ADDR, 12, 3);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_CONTINUE_XFER, 15, USE_REPEAT_START_TOP, CONTINUE);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_REPEAT_START_STOP, 16, STOP_CONDITION, REPEAT_START_CONDITION);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_IE, 17, DISABLE, ENABLE);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_ADDRESS_MODE, 18, SEVEN_BIT, TEN_BIT);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_READ_WRITE, 19, WRITE, READ);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_SEND_START_BYTE, 20, DISABLE, ENABLE);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_CONTINUE_ON_NACK, 21, DISABLE, ENABLE);
|
||||
DEFINE_IO_PACKET_REG_BIT_ENUM(PROTOCOL_HEADER_HS_MODE, 22, DISABLE, ENABLE);
|
||||
|
||||
}
|
||||
|
||||
void I2cBusAccessor::Initialize(dd::PhysicalAddress reg_paddr, size_t reg_size, os::InterruptName intr, bool pb, SpeedMode sm) {
|
||||
AMS_ASSERT(m_state == State::NotInitialized);
|
||||
|
||||
m_is_power_bus = pb;
|
||||
m_speed_mode = sm;
|
||||
m_interrupt_name = intr;
|
||||
m_registers_phys_addr = reg_paddr;
|
||||
m_registers_size = reg_size;
|
||||
m_state = State::Initializing;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::RegisterDeviceCode(DeviceCode dc) {
|
||||
AMS_ASSERT(m_state == State::Initializing);
|
||||
|
||||
m_device_code = dc;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::InitializeDriver() {
|
||||
AMS_ASSERT(m_state == State::Initializing);
|
||||
|
||||
m_registers = reinterpret_cast<volatile I2cRegisters *>(dd::QueryIoMapping(m_registers_phys_addr, m_registers_size));
|
||||
AMS_ABORT_UNLESS(m_registers != nullptr);
|
||||
|
||||
m_state = State::Initialized;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::FinalizeDriver() {
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
m_state = State::Initializing;
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::InitializeDevice(I2cDeviceProperty *device) {
|
||||
/* Check that the device is valid. */
|
||||
AMS_ASSERT(device != nullptr);
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
AMS_UNUSED(device);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* Increment our user count -- if we're already open, we're done. */
|
||||
AMS_ASSERT(m_user_count >= 0);
|
||||
++m_user_count;
|
||||
R_SUCCEED_IF(m_user_count > 1);
|
||||
|
||||
/* Initialize our interrupt event. */
|
||||
os::InitializeInterruptEvent(std::addressof(m_interrupt_event), m_interrupt_name, os::EventClearMode_ManualClear);
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
|
||||
/* If we're not power bus, perform power management init. */
|
||||
if (!m_is_power_bus) {
|
||||
/* Initialize regulator library. */
|
||||
regulator::Initialize();
|
||||
|
||||
/* Try to open regulator session. */
|
||||
R_TRY(this->TryOpenRegulatorSession());
|
||||
|
||||
/* If we have a regulator session, set voltage to 2.9V. */
|
||||
if (m_has_regulator_session) {
|
||||
/* NOTE: Nintendo does not check the result, here. */
|
||||
regulator::SetVoltageValue(std::addressof(m_regulator_session), 2'900'000u);
|
||||
}
|
||||
|
||||
/* Initialize clock/reset library. */
|
||||
clkrst::Initialize();
|
||||
}
|
||||
|
||||
/* Execute initial config. */
|
||||
this->ExecuteInitialConfig();
|
||||
|
||||
/* If we have a regulator session, enable voltage. */
|
||||
if (!m_is_power_bus && m_has_regulator_session) {
|
||||
/* Check whether voltage was already enabled. */
|
||||
const bool was_enabled = regulator::GetVoltageEnabled(std::addressof(m_regulator_session));
|
||||
|
||||
/* NOTE: Nintendo does not check the result of this call. */
|
||||
regulator::SetVoltageEnabled(std::addressof(m_regulator_session), true);
|
||||
|
||||
/* If we enabled voltage, delay to give our enable time to take. */
|
||||
if (!was_enabled) {
|
||||
os::SleepThread(TimeSpan::FromMicroSeconds(560));
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void I2cBusAccessor::FinalizeDevice(I2cDeviceProperty *device) {
|
||||
/* Check that the device is valid. */
|
||||
AMS_ASSERT(device != nullptr);
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
AMS_UNUSED(device);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* Increment our user count -- if we're not the last user, we're done. */
|
||||
AMS_ASSERT(m_user_count > 0);
|
||||
--m_user_count;
|
||||
if (m_user_count > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Finalize our interrupt event. */
|
||||
os::FinalizeInterruptEvent(std::addressof(m_interrupt_event));
|
||||
|
||||
/* If we have a regulator session, disable voltage. */
|
||||
if (m_has_regulator_session) {
|
||||
/* NOTE: Nintendo does not check the result of this call. */
|
||||
regulator::SetVoltageEnabled(std::addressof(m_regulator_session), false);
|
||||
}
|
||||
|
||||
/* Finalize the clock/reset library. */
|
||||
clkrst::Finalize();
|
||||
|
||||
/* If we have a regulator session, close it. */
|
||||
if (m_has_regulator_session) {
|
||||
regulator::CloseSession(std::addressof(m_regulator_session));
|
||||
m_has_regulator_session = false;
|
||||
}
|
||||
|
||||
/* Finalize the regulator library. */
|
||||
regulator::Finalize();
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Send(I2cDeviceProperty *device, const void *src, size_t src_size, TransactionOption option) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(device != nullptr);
|
||||
AMS_ASSERT(src != nullptr);
|
||||
AMS_ASSERT(src_size > 0);
|
||||
|
||||
if (m_is_power_bus) {
|
||||
AMS_ASSERT(m_state == State::Initialized || m_state == State::Suspended);
|
||||
} else {
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
}
|
||||
|
||||
/* Send the data. */
|
||||
R_RETURN(this->Send(static_cast<const u8 *>(src), src_size, option, device->GetAddress(), device->GetAddressingMode()));
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Receive(void *dst, size_t dst_size, I2cDeviceProperty *device, TransactionOption option) {
|
||||
/* Check pre-conditions. */
|
||||
AMS_ASSERT(device != nullptr);
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
AMS_ASSERT(dst_size > 0);
|
||||
|
||||
if (m_is_power_bus) {
|
||||
AMS_ASSERT(m_state == State::Initialized || m_state == State::Suspended);
|
||||
} else {
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
}
|
||||
|
||||
/* Send the data. */
|
||||
R_RETURN(this->Receive(static_cast<u8 *>(dst), dst_size, option, device->GetAddress(), device->GetAddressingMode()));
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SuspendBus() {
|
||||
/* Check that state is valid. */
|
||||
AMS_ASSERT(m_state == State::Initialized);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* If we need to, disable clock/voltage appropriately. */
|
||||
if (!m_is_power_bus && m_user_count > 0) {
|
||||
/* Disable clock. */
|
||||
{
|
||||
/* Open a clkrst session. */
|
||||
clkrst::ClkRstSession clkrst_session;
|
||||
R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code));
|
||||
ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); };
|
||||
|
||||
/* Set clock disabled for the session. */
|
||||
clkrst::SetClockDisabled(std::addressof(clkrst_session));
|
||||
}
|
||||
|
||||
/* Disable voltage. */
|
||||
if (m_has_regulator_session) {
|
||||
regulator::SetVoltageEnabled(std::addressof(m_regulator_session), false);
|
||||
}
|
||||
}
|
||||
|
||||
/* Update state. */
|
||||
m_state = State::Suspended;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SuspendPowerBus() {
|
||||
/* Check that state is valid. */
|
||||
AMS_ASSERT(m_state == State::Suspended);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* If we need to, disable clock/voltage appropriately. */
|
||||
if (m_is_power_bus && m_user_count > 0) {
|
||||
/* Nothing should actually be done here. */
|
||||
}
|
||||
|
||||
/* Update state. */
|
||||
m_state = State::PowerBusSuspended;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ResumeBus() {
|
||||
/* Check that state is valid. */
|
||||
AMS_ASSERT(m_state == State::Suspended);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* If we need to, enable clock/voltage appropriately. */
|
||||
if (!m_is_power_bus && m_user_count > 0) {
|
||||
/* Enable voltage. */
|
||||
if (m_has_regulator_session) {
|
||||
/* Check whether voltage was already enabled. */
|
||||
const bool was_enabled = regulator::GetVoltageEnabled(std::addressof(m_regulator_session));
|
||||
|
||||
/* NOTE: Nintendo does not check the result of this call. */
|
||||
regulator::SetVoltageEnabled(std::addressof(m_regulator_session), true);
|
||||
|
||||
/* If we enabled voltage, delay to give our enable time to take. */
|
||||
if (!was_enabled) {
|
||||
os::SleepThread(TimeSpan::FromMicroSeconds(560));
|
||||
}
|
||||
}
|
||||
|
||||
/* Execute initial config, which will enable clock as relevant. */
|
||||
this->ExecuteInitialConfig();
|
||||
}
|
||||
|
||||
/* Update state. */
|
||||
m_state = State::Initialized;
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ResumePowerBus() {
|
||||
/* Check that state is valid. */
|
||||
AMS_ASSERT(m_state == State::PowerBusSuspended);
|
||||
|
||||
/* Acquire exclusive access. */
|
||||
std::scoped_lock lk(m_user_count_mutex);
|
||||
|
||||
/* If we need to, enable clock/voltage appropriately. */
|
||||
if (m_is_power_bus && m_user_count > 0) {
|
||||
/* Execute initial config, which will enable clock as relevant. */
|
||||
this->ExecuteInitialConfig();
|
||||
}
|
||||
|
||||
/* Update state. */
|
||||
m_state = State::Suspended;
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::TryOpenRegulatorSession() {
|
||||
/* Ensure we track the session. */
|
||||
m_has_regulator_session = true;
|
||||
auto s_guard = SCOPE_GUARD { m_has_regulator_session = false; };
|
||||
|
||||
/* Try to open the session. */
|
||||
R_TRY_CATCH(regulator::OpenSession(std::addressof(m_regulator_session), m_device_code)) {
|
||||
R_CATCH(ddsf::ResultDeviceCodeNotFound) {
|
||||
/* It's okay if the device isn't found, but we don't have a session if so. */
|
||||
m_has_regulator_session = false;
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
/* We opened (or not). */
|
||||
s_guard.Cancel();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ExecuteInitialConfig() {
|
||||
/* Lock exclusive access to registers. */
|
||||
std::scoped_lock lk(m_register_mutex);
|
||||
|
||||
/* Reset the controller. */
|
||||
this->ResetController();
|
||||
|
||||
/* Set clock registers. */
|
||||
this->SetClockRegisters(m_speed_mode);
|
||||
|
||||
/* Set packet mode registers. */
|
||||
this->SetPacketModeRegisters();
|
||||
|
||||
/* Flush fifos. */
|
||||
this->FlushFifos();
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Send(const u8 *src, size_t src_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) {
|
||||
/* Acquire exclusive access to the registers. */
|
||||
std::scoped_lock lk(m_register_mutex);
|
||||
|
||||
/* Configure interrupt mask, clear interrupt status. */
|
||||
reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_TFIFO_DATA_REQ_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE));
|
||||
|
||||
reg::Write(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_UNF, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_OVF, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ALL_PACKETS_XFER_COMPLETE, SET));
|
||||
|
||||
/* Write the header. */
|
||||
this->WriteHeader(Xfer_Write, src_size, option, slave_address, addressing_mode);
|
||||
|
||||
/* Setup tracking variables for the data. */
|
||||
const u8 *cur = src;
|
||||
size_t remaining = src_size;
|
||||
|
||||
while (true) {
|
||||
/* Get the number of empty bytes in the fifo status. */
|
||||
const u32 empty = reg::GetValue(m_registers->fifo_status, I2C_REG_BITS_MASK(FIFO_STATUS_TX_FIFO_EMPTY_CNT));
|
||||
|
||||
/* Write up to (empty) bytes to the fifo. */
|
||||
for (u32 i = 0; remaining > 0 && i < empty; ++i) {
|
||||
/* Build the data word to send. */
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
|
||||
u32 word = 0;
|
||||
for (size_t j = 0; j < cur_bytes; ++j) {
|
||||
word |= cur[j] << (BITSIZEOF(u8) * j);
|
||||
}
|
||||
|
||||
/* Write the data word. */
|
||||
reg::Write(m_registers->tx_packet_fifo, word);
|
||||
|
||||
/* Advance. */
|
||||
cur += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
|
||||
/* If we're done, break. */
|
||||
if (remaining == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait for our current data to send. */
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) {
|
||||
/* We timed out. */
|
||||
this->HandleTransactionError(i2c::ResultBusBusy());
|
||||
|
||||
this->DisableInterruptMask();
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
R_THROW(i2c::ResultTimeout());
|
||||
}
|
||||
|
||||
/* Check and handle any errors. */
|
||||
R_TRY(this->CheckAndHandleError());
|
||||
}
|
||||
|
||||
/* Configure interrupt mask to not care about tfifo data req. */
|
||||
reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE));
|
||||
|
||||
/* Wait for the packet transfer to complete. */
|
||||
while (true) {
|
||||
/* Check and handle any errors. */
|
||||
R_TRY(this->CheckAndHandleError());
|
||||
|
||||
/* Check if packet transfer is done. */
|
||||
if (reg::HasValue(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Wait for our the packet to transfer. */
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) {
|
||||
/* We timed out. */
|
||||
this->HandleTransactionError(i2c::ResultBusBusy());
|
||||
|
||||
this->DisableInterruptMask();
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
R_THROW(i2c::ResultTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
/* Check and handle any errors. */
|
||||
R_TRY(this->CheckAndHandleError());
|
||||
|
||||
/* We're done. */
|
||||
this->DisableInterruptMask();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::Receive(u8 *dst, size_t dst_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) {
|
||||
/* Acquire exclusive access to the registers. */
|
||||
std::scoped_lock lk(m_register_mutex);
|
||||
|
||||
/* Configure interrupt mask, clear interrupt status. */
|
||||
reg::Write(m_registers->interrupt_mask_register, I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_RFIFO_DATA_REQ_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_ARB_LOST_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_NOACK_INT_EN, ENABLE),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_MASK_REGISTER_PACKET_XFER_COMPLETE_INT_EN, ENABLE));
|
||||
|
||||
reg::Write(m_registers->interrupt_status_register, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_RFIFO_UNF, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_TFIFO_OVF, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_PACKET_XFER_COMPLETE, SET),
|
||||
I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ALL_PACKETS_XFER_COMPLETE, SET));
|
||||
|
||||
/* Write the header. */
|
||||
this->WriteHeader(Xfer_Read, dst_size, option, slave_address, addressing_mode);
|
||||
|
||||
/* Setup tracking variables for the data. */
|
||||
u8 *cur = dst;
|
||||
size_t remaining = dst_size;
|
||||
|
||||
while (remaining > 0) {
|
||||
/* Wait for data to come in. */
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
if (!os::TimedWaitInterruptEvent(std::addressof(m_interrupt_event), Timeout)) {
|
||||
/* We timed out. */
|
||||
this->HandleTransactionError(i2c::ResultBusBusy());
|
||||
|
||||
this->DisableInterruptMask();
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
R_THROW(i2c::ResultTimeout());
|
||||
}
|
||||
|
||||
/* Check and handle any errors. */
|
||||
R_TRY(this->CheckAndHandleError());
|
||||
|
||||
/* Get the number of full bytes in the fifo status. */
|
||||
const u32 full = reg::GetValue(m_registers->fifo_status, I2C_REG_BITS_MASK(FIFO_STATUS_RX_FIFO_FULL_CNT));
|
||||
|
||||
/* Determine how many words we can read. */
|
||||
const size_t cur_words = std::min(util::DivideUp(remaining, sizeof(u32)), static_cast<size_t>(full));
|
||||
|
||||
/* Read the correct number of words from the fifo. */
|
||||
for (size_t i = 0; i < cur_words; ++i) {
|
||||
/* Read the word from the fifo. */
|
||||
const u32 word = reg::Read(m_registers->rx_fifo);
|
||||
|
||||
/* Copy bytes from the word. */
|
||||
const size_t cur_bytes = std::min(remaining, sizeof(u32));
|
||||
for (size_t j = 0; j < cur_bytes; ++j) {
|
||||
cur[j] = (word >> (BITSIZEOF(u8) * j)) & 0xFF;
|
||||
}
|
||||
|
||||
/* Advance. */
|
||||
cur += cur_bytes;
|
||||
remaining -= cur_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
/* We're done. */
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void I2cBusAccessor::WriteHeader(Xfer xfer, size_t size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode) {
|
||||
/* Parse interesting values from our arguments. */
|
||||
const bool is_read = xfer == Xfer_Read;
|
||||
const bool is_7_bit = addressing_mode == AddressingMode_SevenBit;
|
||||
const bool is_stop = (option & TransactionOption_StopCondition) != 0;
|
||||
const bool is_hs = m_speed_mode == SpeedMode_HighSpeed;
|
||||
const u32 slave_addr = ((static_cast<u32>(slave_address) & 0x7F) << 1) | (is_read ? 1 : 0);
|
||||
|
||||
/* Flush fifos. */
|
||||
this->FlushFifos();
|
||||
|
||||
/* Enqueue the first header word. */
|
||||
reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_ENUM (HEADER_WORD0_PROT_HDR_SZ, 1_WORD),
|
||||
IO_PACKET_BITS_VALUE(HEADER_WORD0_PKT_ID, 0),
|
||||
IO_PACKET_BITS_VALUE(HEADER_WORD0_CONTROLLER_ID, 0),
|
||||
IO_PACKET_BITS_ENUM (HEADER_WORD0_PROTOCOL, I2C),
|
||||
IO_PACKET_BITS_ENUM (HEADER_WORD0_PKT_TYPE, REQUEST));
|
||||
|
||||
/* Enqueue the second header word. */
|
||||
reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_VALUE(HEADER_WORD1_PAYLOAD_SIZE, static_cast<u32>(size - 1)));
|
||||
|
||||
/* Enqueue the protocol header word. */
|
||||
reg::Write(m_registers->tx_packet_fifo, IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_HS_MODE, is_hs, ENABLE, DISABLE),
|
||||
IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_CONTINUE_ON_NACK, DISABLE),
|
||||
IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_SEND_START_BYTE, DISABLE),
|
||||
IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_READ_WRITE, is_read, READ, WRITE),
|
||||
IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_ADDRESS_MODE, is_7_bit, SEVEN_BIT, TEN_BIT),
|
||||
IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_IE, ENABLE),
|
||||
IO_PACKET_BITS_ENUM_SEL(PROTOCOL_HEADER_REPEAT_START_STOP, is_stop, STOP_CONDITION, REPEAT_START_CONDITION),
|
||||
IO_PACKET_BITS_ENUM (PROTOCOL_HEADER_CONTINUE_XFER, USE_REPEAT_START_TOP),
|
||||
IO_PACKET_BITS_VALUE (PROTOCOL_HEADER_HS_MASTER_ADDR, 0),
|
||||
IO_PACKET_BITS_VALUE (PROTOCOL_HEADER_SLAVE_ADDR, slave_addr));
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ResetController() const {
|
||||
/* Reset the controller. */
|
||||
if (!m_is_power_bus) {
|
||||
/* Open a clkrst session. */
|
||||
clkrst::ClkRstSession clkrst_session;
|
||||
R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code));
|
||||
ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); };
|
||||
|
||||
/* Reset the controller, setting clock rate to 408 MHz / 5 (to account for clock divisor). */
|
||||
/* NOTE: Nintendo does not check result for any of these calls. */
|
||||
clkrst::SetResetAsserted(std::addressof(clkrst_session));
|
||||
clkrst::SetClockRate(std::addressof(clkrst_session), 408'000'000 / (4 + 1));
|
||||
clkrst::SetResetDeasserted(std::addressof(clkrst_session));
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::ClearBus() const {
|
||||
/* Try to clear the bus up to three times. */
|
||||
constexpr int MaxRetryCount = 3;
|
||||
constexpr int BusyLoopMicroSeconds = 1000;
|
||||
|
||||
int try_count = 0;
|
||||
bool need_retry;
|
||||
do {
|
||||
/* Update trackers. */
|
||||
++try_count;
|
||||
need_retry = false;
|
||||
|
||||
/* Reset the controller. */
|
||||
this->ResetController();
|
||||
|
||||
/* Configure the sclk threshold for bus clear config. */
|
||||
reg::Write(m_registers->bus_clear_config, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9));
|
||||
|
||||
/* Set stop cond and terminate in bus clear config. */
|
||||
reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_STOP_COND, STOP));
|
||||
reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE));
|
||||
|
||||
/* Set master config load, busy loop up to 1ms for it to take. */
|
||||
reg::ReadWrite(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE));
|
||||
|
||||
const os::Tick start_tick_a = os::GetSystemTick();
|
||||
while (reg::HasValue(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE))) {
|
||||
if ((os::GetSystemTick() - start_tick_a).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) {
|
||||
need_retry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_retry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Set bus clear enable, wait up to 1ms for it to take. */
|
||||
reg::ReadWrite(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE));
|
||||
|
||||
const os::Tick start_tick_b = os::GetSystemTick();
|
||||
while (reg::HasValue(m_registers->bus_clear_config, I2C_REG_BITS_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE))) {
|
||||
if ((os::GetSystemTick() - start_tick_b).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) {
|
||||
need_retry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_retry) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Wait up to 1ms for the bus clear to complete. */
|
||||
const os::Tick start_tick_c = os::GetSystemTick();
|
||||
while (reg::HasValue(m_registers->bus_clear_status, I2C_REG_BITS_ENUM(BUS_CLEAR_STATUS_BC_STATUS, NOT_CLEARED))) {
|
||||
if ((os::GetSystemTick() - start_tick_c).ToTimeSpan().GetMicroSeconds() > BusyLoopMicroSeconds) {
|
||||
need_retry = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_retry) {
|
||||
continue;
|
||||
}
|
||||
} while (try_count < MaxRetryCount && need_retry);
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SetClockRegisters(SpeedMode speed_mode) {
|
||||
/* Determine parameters for the speed mode. */
|
||||
u32 t_high, t_low, clk_div, debounce, src_div;
|
||||
bool high_speed = false;
|
||||
|
||||
if (m_is_power_bus) {
|
||||
t_high = 0x02;
|
||||
t_low = 0x04;
|
||||
clk_div = 0x05;
|
||||
debounce = 0x02;
|
||||
src_div = 0; /* unused */
|
||||
} else {
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_Standard:
|
||||
t_high = 0x02;
|
||||
t_low = 0x04;
|
||||
clk_div = 0x19;
|
||||
debounce = 0x02;
|
||||
src_div = 0x13;
|
||||
break;
|
||||
case SpeedMode_Fast:
|
||||
t_high = 0x02;
|
||||
t_low = 0x04;
|
||||
clk_div = 0x19;
|
||||
debounce = 0x02;
|
||||
src_div = 0x04;
|
||||
break;
|
||||
case SpeedMode_FastPlus:
|
||||
t_high = 0x02;
|
||||
t_low = 0x04;
|
||||
clk_div = 0x10;
|
||||
debounce = 0x00;
|
||||
src_div = 0x02;
|
||||
break;
|
||||
case SpeedMode_HighSpeed:
|
||||
t_high = 0x03;
|
||||
t_low = 0x08;
|
||||
clk_div = 0x02;
|
||||
debounce = 0x00;
|
||||
src_div = 0x02;
|
||||
high_speed = true;
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
/* Write the clock divisors. */
|
||||
if (high_speed) {
|
||||
reg::Write(m_registers->hs_interface_timing_0, I2C_REG_BITS_VALUE(HS_INTERFACE_TIMING_0_HS_THIGH, t_high),
|
||||
I2C_REG_BITS_VALUE(HS_INTERFACE_TIMING_0_HS_TLOW, t_low));
|
||||
|
||||
reg::Write(m_registers->clk_divisor_register, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, clk_div));
|
||||
} else {
|
||||
reg::Write(m_registers->interface_timing_0, I2C_REG_BITS_VALUE(INTERFACE_TIMING_0_THIGH, t_high),
|
||||
I2C_REG_BITS_VALUE(INTERFACE_TIMING_0_TLOW, t_low));
|
||||
|
||||
reg::Write(m_registers->clk_divisor_register, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, clk_div));
|
||||
}
|
||||
|
||||
/* Configure debounce. */
|
||||
reg::Write(m_registers->cnfg, I2C_REG_BITS_VALUE(I2C_CNFG_DEBOUNCE_CNT, debounce));
|
||||
reg::Read(m_registers->cnfg);
|
||||
|
||||
/* Set the clock rate, if we should. */
|
||||
if (!m_is_power_bus) {
|
||||
/* Open a clkrst session. */
|
||||
clkrst::ClkRstSession clkrst_session;
|
||||
R_ABORT_UNLESS(clkrst::OpenSession(std::addressof(clkrst_session), m_device_code));
|
||||
ON_SCOPE_EXIT { clkrst::CloseSession(std::addressof(clkrst_session)); };
|
||||
|
||||
/* Reset the controller, setting clock rate to 408 MHz / (src_div + 1). */
|
||||
/* NOTE: Nintendo does not check result for any of these calls. */
|
||||
clkrst::SetResetAsserted(std::addressof(clkrst_session));
|
||||
clkrst::SetClockRate(std::addressof(clkrst_session), 408'000'000 / (src_div + 1));
|
||||
clkrst::SetResetDeasserted(std::addressof(clkrst_session));
|
||||
}
|
||||
}
|
||||
|
||||
void I2cBusAccessor::SetPacketModeRegisters() {
|
||||
/* Set packet mode enable. */
|
||||
reg::ReadWrite(m_registers->cnfg, I2C_REG_BITS_ENUM(I2C_CNFG_PACKET_MODE_EN, GO));
|
||||
|
||||
/* Set master config load. */
|
||||
reg::ReadWrite(m_registers->config_load, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE));
|
||||
|
||||
/* Set tx/fifo triggers to default (maximum values). */
|
||||
reg::Write(m_registers->fifo_control, I2C_REG_BITS_VALUE(FIFO_CONTROL_RX_FIFO_TRIG, 7),
|
||||
I2C_REG_BITS_VALUE(FIFO_CONTROL_TX_FIFO_TRIG, 7));
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::FlushFifos() {
|
||||
/* Flush the fifo. */
|
||||
reg::Write(m_registers->fifo_control, I2C_REG_BITS_VALUE(FIFO_CONTROL_RX_FIFO_TRIG, 7),
|
||||
I2C_REG_BITS_VALUE(FIFO_CONTROL_TX_FIFO_TRIG, 7),
|
||||
I2C_REG_BITS_ENUM (FIFO_CONTROL_RX_FIFO_FLUSH, SET),
|
||||
I2C_REG_BITS_ENUM (FIFO_CONTROL_TX_FIFO_FLUSH, SET));
|
||||
|
||||
/* Wait up to 5 ms for the flush to complete. */
|
||||
int count = 0;
|
||||
while (!reg::HasValue(m_registers->fifo_control, I2C_REG_BITS_ENUM(FIFO_CONTROL_FIFO_FLUSH, RX_UNSET_TX_UNSET))) {
|
||||
R_UNLESS((++count < 5), i2c::ResultBusBusy());
|
||||
os::SleepThread(TimeSpan::FromMilliSeconds(1));
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cBusAccessor::GetTransactionResult() const {
|
||||
/* Get packet status/interrupt status. */
|
||||
volatile u32 packet_status = reg::Read(m_registers->packet_transfer_status);
|
||||
volatile u32 interrupt_status = reg::Read(m_registers->interrupt_status_register);
|
||||
|
||||
/* Check for ack. */
|
||||
R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_DATA, UNSET)), i2c::ResultNoAck());
|
||||
R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_NOACK_FOR_ADDR, UNSET)), i2c::ResultNoAck());
|
||||
R_UNLESS(reg::HasValue(interrupt_status, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_NOACK, UNSET)), i2c::ResultNoAck());
|
||||
|
||||
/* If we lost arbitration, we'll need to clear the bus. */
|
||||
auto clear_guard = SCOPE_GUARD { this->ClearBus(); };
|
||||
|
||||
/* Check for arb lost. */
|
||||
R_UNLESS(reg::HasValue(packet_status, I2C_REG_BITS_ENUM(PACKET_TRANSFER_STATUS_ARB_LOST, UNSET)), i2c::ResultBusBusy());
|
||||
R_UNLESS(reg::HasValue(interrupt_status, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_ARB_LOST, UNSET)), i2c::ResultBusBusy());
|
||||
|
||||
clear_guard.Cancel();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void I2cBusAccessor::HandleTransactionError(Result result) {
|
||||
R_TRY_CATCH(result) {
|
||||
R_CATCH(i2c::ResultNoAck, i2c::ResultBusBusy) {
|
||||
/* Reset the controller. */
|
||||
this->ResetController();
|
||||
|
||||
/* Set clock registers. */
|
||||
this->SetClockRegisters(m_speed_mode);
|
||||
|
||||
/* Set packet mode registers. */
|
||||
this->SetPacketModeRegisters();
|
||||
|
||||
/* Flush fifos. */
|
||||
this->FlushFifos();
|
||||
}
|
||||
} R_END_TRY_CATCH_WITH_ABORT_UNLESS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_i2c_registers.hpp"
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
class I2cBusAccessor : public ::ams::i2c::driver::II2cDriver {
|
||||
NON_COPYABLE(I2cBusAccessor);
|
||||
NON_MOVEABLE(I2cBusAccessor);
|
||||
AMS_DDSF_CASTABLE_TRAITS(ams::i2c::driver::board::nintendo::nx::impl::I2cBusAccessor, ::ams::i2c::driver::II2cDriver);
|
||||
private:
|
||||
enum class State {
|
||||
NotInitialized = 0,
|
||||
Initializing = 1,
|
||||
Initialized = 2,
|
||||
Suspended = 3,
|
||||
PowerBusSuspended = 4,
|
||||
};
|
||||
|
||||
enum Xfer {
|
||||
Xfer_Write = 0,
|
||||
Xfer_Read = 1,
|
||||
};
|
||||
private:
|
||||
volatile I2cRegisters *m_registers;
|
||||
SpeedMode m_speed_mode;
|
||||
os::InterruptEventType m_interrupt_event;
|
||||
int m_user_count;
|
||||
os::SdkMutex m_user_count_mutex;
|
||||
os::SdkMutex m_register_mutex;
|
||||
regulator::RegulatorSession m_regulator_session;
|
||||
bool m_has_regulator_session;
|
||||
State m_state;
|
||||
os::SdkMutex m_transaction_order_mutex;
|
||||
bool m_is_power_bus;
|
||||
dd::PhysicalAddress m_registers_phys_addr;
|
||||
size_t m_registers_size;
|
||||
os::InterruptName m_interrupt_name;
|
||||
DeviceCode m_device_code;
|
||||
util::IntrusiveListNode m_bus_accessor_list_node;
|
||||
public:
|
||||
using BusAccessorListTraits = util::IntrusiveListMemberTraits<&I2cBusAccessor::m_bus_accessor_list_node>;
|
||||
using BusAccessorList = typename BusAccessorListTraits::ListType;
|
||||
friend class util::IntrusiveList<I2cBusAccessor, util::IntrusiveListMemberTraits<&I2cBusAccessor::m_bus_accessor_list_node>>;
|
||||
public:
|
||||
I2cBusAccessor()
|
||||
: m_registers(nullptr), m_speed_mode(SpeedMode_Fast), m_user_count(0), m_user_count_mutex(),
|
||||
m_register_mutex(), m_has_regulator_session(false), m_state(State::NotInitialized), m_transaction_order_mutex(),
|
||||
m_is_power_bus(false), m_registers_phys_addr(0), m_registers_size(0), m_interrupt_name(), m_device_code(-1), m_bus_accessor_list_node()
|
||||
{
|
||||
/* ... */
|
||||
}
|
||||
|
||||
void Initialize(dd::PhysicalAddress reg_paddr, size_t reg_size, os::InterruptName intr, bool pb, SpeedMode sm);
|
||||
void RegisterDeviceCode(DeviceCode device_code);
|
||||
|
||||
SpeedMode GetSpeedMode() const { return m_speed_mode; }
|
||||
dd::PhysicalAddress GetRegistersPhysicalAddress() const { return m_registers_phys_addr; }
|
||||
size_t GetRegistersSize() const { return m_registers_size; }
|
||||
os::InterruptName GetInterruptName() const { return m_interrupt_name; }
|
||||
private:
|
||||
Result TryOpenRegulatorSession();
|
||||
|
||||
void ExecuteInitialConfig();
|
||||
|
||||
Result Send(const u8 *src, size_t src_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode);
|
||||
Result Receive(u8 *dst, size_t dst_size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode);
|
||||
|
||||
void WriteHeader(Xfer xfer, size_t size, TransactionOption option, u16 slave_address, AddressingMode addressing_mode);
|
||||
|
||||
void ResetController() const;
|
||||
void ClearBus() const;
|
||||
void SetClockRegisters(SpeedMode speed_mode);
|
||||
void SetPacketModeRegisters();
|
||||
|
||||
Result FlushFifos();
|
||||
|
||||
Result GetTransactionResult() const;
|
||||
void HandleTransactionError(Result result);
|
||||
|
||||
void DisableInterruptMask() {
|
||||
reg::Write(m_registers->interrupt_mask_register, 0);
|
||||
reg::Read(m_registers->interrupt_mask_register);
|
||||
}
|
||||
|
||||
Result CheckAndHandleError() {
|
||||
const Result result = this->GetTransactionResult();
|
||||
this->HandleTransactionError(result);
|
||||
|
||||
if (R_FAILED(result)) {
|
||||
this->DisableInterruptMask();
|
||||
os::ClearInterruptEvent(std::addressof(m_interrupt_event));
|
||||
}
|
||||
|
||||
R_RETURN(result);
|
||||
}
|
||||
public:
|
||||
virtual void InitializeDriver() override;
|
||||
virtual void FinalizeDriver() override;
|
||||
|
||||
virtual Result InitializeDevice(I2cDeviceProperty *device) override;
|
||||
virtual void FinalizeDevice(I2cDeviceProperty *device) override;
|
||||
|
||||
virtual Result Send(I2cDeviceProperty *device, const void *src, size_t src_size, TransactionOption option) override;
|
||||
virtual Result Receive(void *dst, size_t dst_size, I2cDeviceProperty *device, TransactionOption option) override;
|
||||
|
||||
virtual os::SdkMutex &GetTransactionOrderMutex() override {
|
||||
return m_transaction_order_mutex;
|
||||
}
|
||||
|
||||
virtual void SuspendBus() override;
|
||||
virtual void SuspendPowerBus() override;
|
||||
|
||||
virtual void ResumeBus() override;
|
||||
virtual void ResumePowerBus() override;
|
||||
|
||||
virtual const DeviceCode &GetDeviceCode() const override {
|
||||
return m_device_code;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_bus_accessor.hpp"
|
||||
#include "i2c_i_allocator.hpp"
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
class I2cBusAccessorManager : public IAllocator<I2cBusAccessor::BusAccessorList> {
|
||||
public:
|
||||
using IAllocator<I2cBusAccessor::BusAccessorList>::IAllocator;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_i_allocator.hpp"
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
class I2cDevicePropertyManager : public IAllocator<I2cDeviceProperty::DevicePropertyList> {
|
||||
public:
|
||||
using IAllocator<I2cDeviceProperty::DevicePropertyList>::IAllocator;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
struct I2cRegisters {
|
||||
volatile u32 cnfg;
|
||||
volatile u32 cmd_addr0;
|
||||
volatile u32 cmd_addr1;
|
||||
volatile u32 cmd_data1;
|
||||
volatile u32 cmd_data2;
|
||||
volatile u32 _14;
|
||||
volatile u32 _18;
|
||||
volatile u32 status;
|
||||
volatile u32 sl_cnfg;
|
||||
volatile u32 sl_rcvd;
|
||||
volatile u32 sl_status;
|
||||
volatile u32 sl_addr1;
|
||||
volatile u32 sl_addr2;
|
||||
volatile u32 tlow_sext;
|
||||
volatile u32 _38;
|
||||
volatile u32 sl_delay_count;
|
||||
volatile u32 sl_int_mask;
|
||||
volatile u32 sl_int_source;
|
||||
volatile u32 sl_int_set;
|
||||
volatile u32 _4c;
|
||||
volatile u32 tx_packet_fifo;
|
||||
volatile u32 rx_fifo;
|
||||
volatile u32 packet_transfer_status;
|
||||
volatile u32 fifo_control;
|
||||
volatile u32 fifo_status;
|
||||
volatile u32 interrupt_mask_register;
|
||||
volatile u32 interrupt_status_register;
|
||||
volatile u32 clk_divisor_register;
|
||||
volatile u32 interrupt_source_register;
|
||||
volatile u32 interrupt_set_register;
|
||||
volatile u32 slv_tx_packet_fifo;
|
||||
volatile u32 slv_rx_fifo;
|
||||
volatile u32 slv_packet_status;
|
||||
volatile u32 bus_clear_config;
|
||||
volatile u32 bus_clear_status;
|
||||
volatile u32 config_load;
|
||||
volatile u32 _90;
|
||||
volatile u32 interface_timing_0;
|
||||
volatile u32 interface_timing_1;
|
||||
volatile u32 hs_interface_timing_0;
|
||||
volatile u32 hs_interface_timing_1;
|
||||
};
|
||||
static_assert(sizeof(I2cRegisters) == 0xA4);
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::i2c::driver::board::nintendo::nx::impl {
|
||||
|
||||
template<typename ListType>
|
||||
class IAllocator {
|
||||
NON_COPYABLE(IAllocator);
|
||||
NON_MOVEABLE(IAllocator);
|
||||
private:
|
||||
using T = typename ListType::value_type;
|
||||
private:
|
||||
ams::MemoryResource *m_memory_resource;
|
||||
ListType m_list;
|
||||
mutable os::SdkMutex m_list_lock;
|
||||
public:
|
||||
IAllocator(ams::MemoryResource *mr) : m_memory_resource(mr), m_list(), m_list_lock() { /* ... */ }
|
||||
|
||||
~IAllocator() {
|
||||
std::scoped_lock lk(m_list_lock);
|
||||
|
||||
/* Remove all entries. */
|
||||
auto it = m_list.begin();
|
||||
while (it != m_list.end()) {
|
||||
T *obj = std::addressof(*it);
|
||||
it = m_list.erase(it);
|
||||
|
||||
std::destroy_at(obj);
|
||||
m_memory_resource->Deallocate(obj, sizeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
template<typename ...Args>
|
||||
T *Allocate(Args &&...args) {
|
||||
std::scoped_lock lk(m_list_lock);
|
||||
|
||||
/* Allocate space for the object. */
|
||||
void *storage = m_memory_resource->Allocate(sizeof(T), alignof(T));
|
||||
AMS_ABORT_UNLESS(storage != nullptr);
|
||||
|
||||
/* Construct the object. */
|
||||
T *t = std::construct_at(static_cast<T *>(storage), std::forward<Args>(args)...);
|
||||
|
||||
/* Link the object into our list. */
|
||||
m_list.push_back(*t);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
template<typename F>
|
||||
T *Find(F f) {
|
||||
std::scoped_lock lk(m_list_lock);
|
||||
|
||||
for (T &it : m_list) {
|
||||
if (f(static_cast<const T &>(it))) {
|
||||
return std::addressof(it);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* TODO: Support free */
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/i2c_driver_core.hpp"
|
||||
|
||||
namespace ams::i2c::driver {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline int DefaultRetryCount = 3;
|
||||
constexpr inline TimeSpan DefaultRetryInterval = TimeSpan::FromMilliSeconds(5);
|
||||
|
||||
Result OpenSessionImpl(I2cSession *out, I2cDeviceProperty *device) {
|
||||
/* Construct the session. */
|
||||
auto *session = std::construct_at(std::addressof(impl::GetI2cSessionImpl(*out)), DefaultRetryCount, DefaultRetryInterval);
|
||||
ON_RESULT_FAILURE { std::destroy_at(session); };
|
||||
|
||||
/* Open the session. */
|
||||
R_RETURN(session->Open(device, ddsf::AccessMode_ReadWrite));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result OpenSession(I2cSession *out, DeviceCode device_code) {
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
/* Find the device. */
|
||||
I2cDeviceProperty *device = nullptr;
|
||||
R_TRY(impl::FindDevice(std::addressof(device), device_code));
|
||||
AMS_ASSERT(device != nullptr);
|
||||
|
||||
/* Open the session. */
|
||||
R_RETURN(OpenSessionImpl(out, device));
|
||||
}
|
||||
|
||||
void CloseSession(I2cSession &session) {
|
||||
std::destroy_at(std::addressof(impl::GetOpenI2cSessionImpl(session)));
|
||||
}
|
||||
|
||||
Result Send(I2cSession &session, const void *src, size_t src_size, TransactionOption option) {
|
||||
AMS_ASSERT(src != nullptr);
|
||||
AMS_ABORT_UNLESS(src_size > 0);
|
||||
|
||||
R_RETURN(impl::GetOpenI2cSessionImpl(session).Send(src, src_size, option));
|
||||
}
|
||||
|
||||
Result Receive(void *dst, size_t dst_size, I2cSession &session, TransactionOption option) {
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
AMS_ABORT_UNLESS(dst_size > 0);
|
||||
|
||||
R_RETURN(impl::GetOpenI2cSessionImpl(session).Receive(dst, dst_size, option));
|
||||
}
|
||||
|
||||
Result ExecuteCommandList(void *dst, size_t dst_size, I2cSession &session, const void *src, size_t src_size) {
|
||||
AMS_ASSERT(src != nullptr);
|
||||
AMS_ASSERT(dst != nullptr);
|
||||
|
||||
AMS_ABORT_UNLESS(src_size > 0);
|
||||
AMS_ABORT_UNLESS(dst_size > 0);
|
||||
|
||||
R_RETURN(impl::GetOpenI2cSessionImpl(session).ExecuteCommandList(dst, dst_size, src, src_size));
|
||||
}
|
||||
|
||||
Result SetRetryPolicy(I2cSession &session, int max_retry_count, int retry_interval_us) {
|
||||
AMS_ASSERT(max_retry_count > 0);
|
||||
AMS_ASSERT(retry_interval_us > 0);
|
||||
|
||||
R_RETURN(impl::GetOpenI2cSessionImpl(session).SetRetryPolicy(max_retry_count, retry_interval_us));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/i2c_driver_core.hpp"
|
||||
|
||||
namespace ams::i2c::driver {
|
||||
|
||||
void Initialize() {
|
||||
return impl::InitializeDrivers();
|
||||
}
|
||||
|
||||
void Finalize() {
|
||||
return impl::FinalizeDrivers();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
#include "impl/i2c_driver_core.hpp"
|
||||
|
||||
namespace ams::i2c::driver {
|
||||
|
||||
void RegisterDriver(II2cDriver *driver) {
|
||||
return impl::RegisterDriver(driver);
|
||||
}
|
||||
|
||||
void UnregisterDriver(II2cDriver *driver) {
|
||||
return impl::UnregisterDriver(driver);
|
||||
}
|
||||
|
||||
Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device) {
|
||||
R_RETURN(impl::RegisterDeviceCode(device_code, device));
|
||||
}
|
||||
|
||||
bool UnregisterDeviceCode(DeviceCode device_code) {
|
||||
return impl::UnregisterDeviceCode(device_code);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_driver_core.hpp"
|
||||
|
||||
namespace ams::i2c::driver::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
constinit os::SdkMutex g_init_mutex;
|
||||
constinit int g_init_count = 0;
|
||||
|
||||
i2c::driver::II2cDriver::List &GetI2cDriverList() {
|
||||
AMS_FUNCTION_LOCAL_STATIC_CONSTINIT(i2c::driver::II2cDriver::List, s_driver_list);
|
||||
return s_driver_list;
|
||||
}
|
||||
|
||||
ddsf::DeviceCodeEntryManager &GetDeviceCodeEntryManager() {
|
||||
AMS_FUNCTION_LOCAL_STATIC(ddsf::DeviceCodeEntryManager, s_device_code_entry_manager, ddsf::GetDeviceCodeEntryHolderMemoryResource());
|
||||
|
||||
return s_device_code_entry_manager;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void InitializeDrivers() {
|
||||
std::scoped_lock lk(g_init_mutex);
|
||||
|
||||
/* Initialize all registered drivers, if this is our first initialization. */
|
||||
if ((g_init_count++) == 0) {
|
||||
for (auto &driver : GetI2cDriverList()) {
|
||||
driver.SafeCastTo<II2cDriver>().InitializeDriver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FinalizeDrivers() {
|
||||
std::scoped_lock lk(g_init_mutex);
|
||||
|
||||
/* If we have no remaining sessions, close. */
|
||||
if ((--g_init_count) == 0) {
|
||||
/* Reset all device code entries. */
|
||||
GetDeviceCodeEntryManager().Reset();
|
||||
|
||||
/* Finalize all drivers. */
|
||||
for (auto &driver : GetI2cDriverList()) {
|
||||
driver.SafeCastTo<II2cDriver>().FinalizeDriver();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterDriver(II2cDriver *driver) {
|
||||
AMS_ASSERT(driver != nullptr);
|
||||
GetI2cDriverList().push_back(*driver);
|
||||
}
|
||||
|
||||
void UnregisterDriver(II2cDriver *driver) {
|
||||
AMS_ASSERT(driver != nullptr);
|
||||
if (driver->IsLinkedToList()) {
|
||||
auto &list = GetI2cDriverList();
|
||||
list.erase(list.iterator_to(*driver));
|
||||
}
|
||||
}
|
||||
|
||||
Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device) {
|
||||
AMS_ASSERT(device != nullptr);
|
||||
R_TRY(GetDeviceCodeEntryManager().Add(device_code, device));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
bool UnregisterDeviceCode(DeviceCode device_code) {
|
||||
return GetDeviceCodeEntryManager().Remove(device_code);
|
||||
}
|
||||
|
||||
Result FindDevice(I2cDeviceProperty **out, DeviceCode device_code) {
|
||||
/* Validate output. */
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
/* Find the device. */
|
||||
ddsf::IDevice *device;
|
||||
R_TRY(GetDeviceCodeEntryManager().FindDevice(std::addressof(device), device_code));
|
||||
|
||||
/* Set output. */
|
||||
*out = device->SafeCastToPointer<I2cDeviceProperty>();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result FindDeviceByBusIndexAndAddress(I2cDeviceProperty **out, i2c::I2cBus bus_index, u16 slave_address) {
|
||||
/* Validate output. */
|
||||
AMS_ASSERT(out != nullptr);
|
||||
|
||||
/* Convert the bus index to a device code. */
|
||||
const DeviceCode device_code = ConvertToDeviceCode(bus_index);
|
||||
|
||||
/* Find the device. */
|
||||
bool found = false;
|
||||
GetDeviceCodeEntryManager().ForEachEntry([&](ddsf::DeviceCodeEntry &entry) -> bool {
|
||||
/* Convert the entry to an I2cDeviceProperty. */
|
||||
auto &device = entry.GetDevice().SafeCastTo<I2cDeviceProperty>();
|
||||
auto &driver = device.GetDriver().SafeCastTo<II2cDriver>();
|
||||
|
||||
/* Check if the device is the one we're looking for. */
|
||||
if (driver.GetDeviceCode() == device_code && device.GetAddress() == slave_address) {
|
||||
found = true;
|
||||
*out = std::addressof(device);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
/* Check that we found the pad. */
|
||||
R_UNLESS(found, ddsf::ResultDeviceCodeNotFound());
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
namespace ams::i2c::driver::impl {
|
||||
|
||||
void InitializeDrivers();
|
||||
void FinalizeDrivers();
|
||||
|
||||
void RegisterDriver(II2cDriver *driver);
|
||||
void UnregisterDriver(II2cDriver *driver);
|
||||
|
||||
Result RegisterDeviceCode(DeviceCode device_code, I2cDeviceProperty *device);
|
||||
bool UnregisterDeviceCode(DeviceCode device_code);
|
||||
|
||||
Result FindDevice(I2cDeviceProperty **out, DeviceCode device_code);
|
||||
Result FindDeviceByBusIndexAndAddress(I2cDeviceProperty **out, i2c::I2cBus bus_index, u16 slave_address);
|
||||
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include <stratosphere.hpp>
|
||||
#include "i2c_driver_core.hpp"
|
||||
#include "../../impl/i2c_command_list_format.hpp"
|
||||
|
||||
namespace ams::i2c::driver::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr TransactionOption EncodeTransactionOption(bool start, bool stop) {
|
||||
return static_cast<TransactionOption>((start ? util::ToUnderlying(TransactionOption_StartCondition) : 0) | (stop ? util::ToUnderlying(TransactionOption_StopCondition) : 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::Open(I2cDeviceProperty *device, ddsf::AccessMode access_mode) {
|
||||
AMS_ASSERT(device != nullptr);
|
||||
|
||||
/* Check if we're the device's first session. */
|
||||
const bool first = !device->HasAnyOpenSession();
|
||||
|
||||
/* Open the session. */
|
||||
R_TRY(ddsf::OpenSession(device, this, access_mode));
|
||||
auto guard = SCOPE_GUARD { ddsf::CloseSession(this); };
|
||||
|
||||
/* If we're the first session, initialize the device. */
|
||||
if (first) {
|
||||
R_TRY(device->GetDriver().SafeCastTo<II2cDriver>().InitializeDevice(device));
|
||||
}
|
||||
|
||||
/* We're opened. */
|
||||
guard.Cancel();
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void I2cSessionImpl::Close() {
|
||||
/* If we're not open, do nothing. */
|
||||
if (!this->IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Get the device. */
|
||||
auto &device = this->GetDevice().SafeCastTo<I2cDeviceProperty>();
|
||||
|
||||
/* Close the session. */
|
||||
ddsf::CloseSession(this);
|
||||
|
||||
/* If there are no remaining sessions, finalize the device. */
|
||||
if (!device.HasAnyOpenSession()) {
|
||||
device.GetDriver().SafeCastTo<II2cDriver>().FinalizeDevice(std::addressof(device));
|
||||
}
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::SendHandler(const u8 **cur_cmd, u8 **cur_dst) {
|
||||
AMS_UNUSED(cur_dst);
|
||||
|
||||
/* Read the header bytes. */
|
||||
const util::BitPack8 hdr0{*((*cur_cmd)++)};
|
||||
const util::BitPack8 hdr1{*((*cur_cmd)++)};
|
||||
|
||||
/* Decode the header. */
|
||||
const bool start = hdr0.Get<i2c::impl::SendCommandFormat::StartCondition>();
|
||||
const bool stop = hdr0.Get<i2c::impl::SendCommandFormat::StopCondition>();
|
||||
const size_t size = hdr1.Get<i2c::impl::SendCommandFormat::Size>();
|
||||
|
||||
/* Execute the transaction. */
|
||||
R_TRY(this->ExecuteTransactionWithRetry(nullptr, Command::Send, *cur_cmd, size, EncodeTransactionOption(start, stop)));
|
||||
|
||||
/* Advance. */
|
||||
*cur_cmd += size;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::ReceiveHandler(const u8 **cur_cmd, u8 **cur_dst) {
|
||||
/* Read the header bytes. */
|
||||
const util::BitPack8 hdr0{*((*cur_cmd)++)};
|
||||
const util::BitPack8 hdr1{*((*cur_cmd)++)};
|
||||
|
||||
/* Decode the header. */
|
||||
const bool start = hdr0.Get<i2c::impl::ReceiveCommandFormat::StartCondition>();
|
||||
const bool stop = hdr0.Get<i2c::impl::ReceiveCommandFormat::StopCondition>();
|
||||
const size_t size = hdr1.Get<i2c::impl::ReceiveCommandFormat::Size>();
|
||||
|
||||
/* Execute the transaction. */
|
||||
R_TRY(this->ExecuteTransactionWithRetry(*cur_dst, Command::Receive, nullptr, size, EncodeTransactionOption(start, stop)));
|
||||
|
||||
/* Advance. */
|
||||
*cur_dst += size;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::ExtensionHandler(const u8 **cur_cmd, u8 **cur_dst) {
|
||||
AMS_UNUSED(cur_dst);
|
||||
|
||||
/* Read the header bytes. */
|
||||
const util::BitPack8 hdr0{*((*cur_cmd)++)};
|
||||
|
||||
/* Execute the subcommand. */
|
||||
switch (hdr0.Get<i2c::impl::CommonCommandFormat::SubCommandId>()) {
|
||||
case i2c::impl::SubCommandId_Sleep:
|
||||
{
|
||||
const util::BitPack8 param{*((*cur_cmd)++)};
|
||||
|
||||
os::SleepThread(TimeSpan::FromMicroSeconds(param.Get<i2c::impl::SleepCommandFormat::MicroSeconds>()));
|
||||
}
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::ExecuteTransactionWithRetry(void *dst, Command command, const void *src, size_t size, TransactionOption option) {
|
||||
/* Get the device. */
|
||||
auto &device = GetDevice().SafeCastTo<I2cDeviceProperty>();
|
||||
|
||||
/* Repeatedly try to execute the transaction. */
|
||||
int retry_count = 0;
|
||||
while (true) {
|
||||
/* Execute the transaction. */
|
||||
Result result;
|
||||
switch (command) {
|
||||
case Command::Send: result = device.GetDriver().SafeCastTo<II2cDriver>().Send(std::addressof(device), src, size, option); break;
|
||||
case Command::Receive: result = device.GetDriver().SafeCastTo<II2cDriver>().Receive(dst, size, std::addressof(device), option); break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* If we timed out, retry up to our max retry count. */
|
||||
R_TRY_CATCH(result) {
|
||||
R_CATCH(i2c::ResultTimeout) {
|
||||
if ((++retry_count) <= m_max_retry_count) {
|
||||
os::SleepThread(m_retry_interval);
|
||||
continue;
|
||||
}
|
||||
R_THROW(i2c::ResultBusBusy());
|
||||
}
|
||||
} R_END_TRY_CATCH;
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::Send(const void *src, size_t src_size, TransactionOption option) {
|
||||
/* Acquire exclusive access to the device. */
|
||||
std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex());
|
||||
|
||||
R_RETURN(this->ExecuteTransactionWithRetry(nullptr, Command::Send, src, src_size, option));
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::Receive(void *dst, size_t dst_size, TransactionOption option) {
|
||||
/* Acquire exclusive access to the device. */
|
||||
std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex());
|
||||
|
||||
R_RETURN(this->ExecuteTransactionWithRetry(dst, Command::Receive, nullptr, dst_size, option));
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::ExecuteCommandList(void *dst, size_t dst_size, const void *src, size_t src_size) {
|
||||
AMS_UNUSED(dst_size);
|
||||
|
||||
/* Acquire exclusive access to the device. */
|
||||
std::scoped_lock lk(this->GetDevice().SafeCastTo<I2cDeviceProperty>().GetDriver().SafeCastTo<II2cDriver>().GetTransactionOrderMutex());
|
||||
|
||||
/* Prepare to process the command list. */
|
||||
const u8 * cur_u8 = static_cast<const u8 *>(src);
|
||||
const u8 * const end_u8 = cur_u8 + src_size;
|
||||
u8 * dst_u8 = static_cast<u8 *>(dst);
|
||||
|
||||
/* Process commands. */
|
||||
while (cur_u8 < end_u8) {
|
||||
const util::BitPack8 hdr{*cur_u8};
|
||||
|
||||
switch (hdr.Get<i2c::impl::CommonCommandFormat::CommandId>()) {
|
||||
case i2c::impl::CommandId_Send: R_TRY(this->SendHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break;
|
||||
case i2c::impl::CommandId_Receive: R_TRY(this->ReceiveHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break;
|
||||
case i2c::impl::CommandId_Extension: R_TRY(this->ExtensionHandler(std::addressof(cur_u8), std::addressof(dst_u8))); break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result I2cSessionImpl::SetRetryPolicy(int mr, int interval_us) {
|
||||
m_max_retry_count = mr;
|
||||
m_retry_interval = TimeSpan::FromMicroSeconds(interval_us);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user