diff --git a/stratosphere/boot/source/i2c_driver/boot_pcv.cpp b/stratosphere/boot/source/i2c_driver/boot_pcv.cpp
new file mode 100644
index 000000000..b54c315f1
--- /dev/null
+++ b/stratosphere/boot/source/i2c_driver/boot_pcv.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2018-2019 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 .
+ */
+
+#include
+#include
+
+#include "i2c_types.hpp"
+#include "i2c_registers.hpp"
+#include "boot_pcv.hpp"
+
+static I2cBus GetI2cBus(PcvModule module) {
+ switch (module) {
+ case PcvModule_I2C1:
+ return I2cBus_I2c1;
+ case PcvModule_I2C2:
+ return I2cBus_I2c2;
+ case PcvModule_I2C3:
+ return I2cBus_I2c3;
+ case PcvModule_I2C4:
+ return I2cBus_I2c4;
+ case PcvModule_I2C5:
+ return I2cBus_I2c5;
+ case PcvModule_I2C6:
+ return I2cBus_I2c6;
+ default:
+ std::abort();
+ }
+}
+
+void Pcv::Initialize() {
+ /* Don't do anything. */
+}
+
+void Pcv::Finalize() {
+ /* Don't do anything. */
+}
+
+Result Pcv::SetClockRate(PcvModule module, u32 hz) {
+ /* Get clock/reset registers. */
+ ClkRstRegisters regs;
+ regs.SetBus(GetI2cBus(module));
+ /* Set clock enabled/source. */
+ SetRegisterBits(regs.clk_en_reg, regs.mask);
+ ReadWriteRegisterBits(regs.clk_src_reg, 0x4, 0xFF);
+ svcSleepThread(1000ul);
+ ReadWriteRegisterBits(regs.clk_src_reg, 0, 0xE0000000);
+ svcSleepThread(2000ul);
+
+ return ResultSuccess;
+}
+
+Result Pcv::SetClockEnabled(PcvModule module, bool enabled) {
+ return ResultSuccess;
+}
+
+Result Pcv::SetVoltageEnabled(u32 domain, bool enabled) {
+ return ResultSuccess;
+}
+
+Result Pcv::SetVoltageValue(u32 domain, u32 voltage) {
+ return ResultSuccess;
+}
+
+Result Pcv::SetReset(PcvModule module, bool reset) {
+ /* Get clock/reset registers. */
+ ClkRstRegisters regs;
+ regs.SetBus(GetI2cBus(module));
+
+ /* Set/clear reset. */
+ if (reset) {
+ SetRegisterBits(regs.rst_reg, regs.mask);
+ } else {
+ ClearRegisterBits(regs.rst_reg, regs.mask);
+ }
+
+ return ResultSuccess;
+}
\ No newline at end of file
diff --git a/stratosphere/boot/source/i2c_driver/boot_pcv.hpp b/stratosphere/boot/source/i2c_driver/boot_pcv.hpp
new file mode 100644
index 000000000..9ccb244eb
--- /dev/null
+++ b/stratosphere/boot/source/i2c_driver/boot_pcv.hpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018-2019 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 .
+ */
+
+#pragma once
+#include
+#include
+
+/* pcv isn't alive at the time boot runs, but nn::i2c::driver needs nn::pcv. */
+/* These are the overrides N puts in boot. */
+
+class Pcv {
+ public:
+ static void Initialize();
+ static void Finalize();
+ static Result SetClockRate(PcvModule module, u32 hz);
+ static Result SetClockEnabled(PcvModule module, bool enabled);
+ static Result SetVoltageEnabled(u32 domain, bool enabled);
+ static Result SetVoltageValue(u32 domain, u32 voltage);
+ static Result SetReset(PcvModule module, bool reset);
+};
\ No newline at end of file
diff --git a/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.cpp b/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.cpp
new file mode 100644
index 000000000..d7f32fcab
--- /dev/null
+++ b/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.cpp
@@ -0,0 +1,494 @@
+/*
+ * Copyright (c) 2018-2019 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 .
+ */
+
+#include
+#include
+
+#include "i2c_bus_accessor.hpp"
+#include "boot_pcv.hpp"
+
+void I2cBusAccessor::Open(I2cBus bus, SpeedMode speed_mode) {
+ std::scoped_lock lk(this->open_mutex);
+ /* Open new session. */
+ this->open_sessions++;
+
+ /* Ensure we're good if this isn't our first session. */
+ if (this->open_sessions > 1) {
+ if (this->speed_mode != speed_mode) {
+ std::abort();
+ }
+ return;
+ }
+
+ /* Set all members for chosen bus. */
+ {
+ std::scoped_lock lk(this->register_mutex);
+ /* Set bus/registers. */
+ this->SetBus(bus);
+ /* Set pcv module. */
+ switch (bus) {
+ case I2cBus_I2c1:
+ this->pcv_module = PcvModule_I2C1;
+ break;
+ case I2cBus_I2c2:
+ this->pcv_module = PcvModule_I2C2;
+ break;
+ case I2cBus_I2c3:
+ this->pcv_module = PcvModule_I2C3;
+ break;
+ case I2cBus_I2c4:
+ this->pcv_module = PcvModule_I2C4;
+ break;
+ case I2cBus_I2c5:
+ this->pcv_module = PcvModule_I2C5;
+ break;
+ case I2cBus_I2c6:
+ this->pcv_module = PcvModule_I2C6;
+ break;
+ default:
+ std::abort();
+ }
+ /* Set speed mode. */
+ this->speed_mode = speed_mode;
+ /* Setup interrupt event. */
+ this->CreateInterruptEvent(bus);
+ }
+}
+
+void I2cBusAccessor::Close() {
+ std::scoped_lock lk(this->open_mutex);
+ /* Close current session. */
+ this->open_sessions--;
+ if (this->open_sessions > 0) {
+ return;
+ }
+
+ /* Close interrupt event. */
+ eventClose(&this->interrupt_event);
+
+ /* Close PCV. */
+ Pcv::Finalize();
+
+ this->suspended = false;
+}
+
+void I2cBusAccessor::Suspend() {
+ std::scoped_lock lk(this->open_mutex);
+ std::scoped_lock lk_reg(this->register_mutex);
+
+ if (!this->suspended) {
+ this->suspended = true;
+
+ if (this->pcv_module != PcvModule_I2C5) {
+ this->DisableClock();
+ }
+ }
+}
+
+void I2cBusAccessor::Resume() {
+ if (this->suspended) {
+ this->DoInitialConfig();
+ this->suspended = false;
+ }
+}
+
+void I2cBusAccessor::DoInitialConfig() {
+ std::scoped_lock lk(this->register_mutex);
+
+ if (this->pcv_module != PcvModule_I2C5) {
+ Pcv::Initialize();
+ }
+
+ this->ResetController();
+ this->SetClock(this->speed_mode);
+ this->SetPacketMode();
+ this->FlushFifos();
+}
+
+size_t I2cBusAccessor::GetOpenSessions() const {
+ return this->open_sessions;
+}
+
+bool I2cBusAccessor::GetBusy() const {
+ /* Nintendo has a loop here that calls a member function to check if busy, retrying a few times. */
+ /* This member function does "return false". */
+ /* We will not bother with the loop. */
+ return false;
+}
+
+Result I2cBusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
+ std::scoped_lock lk(this->register_mutex);
+ const u8 *cur_src = data;
+ size_t remaining = num_bytes;
+ Result rc;
+
+ /* Set interrupt enable, clear interrupt status. */
+ WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8E);
+ WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
+
+ ON_SCOPE_EXIT { this->ClearInterruptMask(); };
+
+ /* Send header. */
+ this->WriteTransferHeader(TransferMode_Send, option, addressing_mode, slave_address, num_bytes);
+
+ /* Send bytes. */
+ while (true) {
+ const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
+ const size_t fifo_cnt = (fifo_status >> 4);
+
+ for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
+ const size_t cur_bytes = std::min(remaining, sizeof(u32));
+ u32 val = 0;
+ for (size_t i = 0; i < cur_bytes; i++) {
+ val |= cur_src[i] << (8 * i);
+ }
+ WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, val);
+
+ cur_src += cur_bytes;
+ remaining -= cur_bytes;
+ }
+
+ if (remaining == 0) {
+ break;
+ }
+
+ eventClear(&this->interrupt_event);
+ if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
+ this->HandleTransactionResult(ResultI2cBusBusy);
+ eventClear(&this->interrupt_event);
+ return ResultI2cTimedOut;
+ }
+
+ if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
+ return rc;
+ }
+ }
+
+ WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
+
+ /* Wait for successful completion. */
+ while (true) {
+ if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
+ return rc;
+ }
+
+ /* Check PACKET_XFER_COMPLETE */
+ const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
+ if (interrupt_status & 0x80) {
+ if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
+ return rc;
+ }
+ break;
+ }
+
+ eventClear(&this->interrupt_event);
+ if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
+ this->HandleTransactionResult(ResultI2cBusBusy);
+ eventClear(&this->interrupt_event);
+ return ResultI2cTimedOut;
+ }
+ }
+
+ return ResultSuccess;
+}
+
+Result I2cBusAccessor::Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
+ std::scoped_lock lk(this->register_mutex);
+ u8 *cur_dst = out_data;
+ size_t remaining = num_bytes;
+ Result rc;
+
+ /* Set interrupt enable, clear interrupt status. */
+ WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8D);
+ WriteRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0, 0xFC);
+
+ /* Send header. */
+ this->WriteTransferHeader(TransferMode_Receive, option, addressing_mode, slave_address, num_bytes);
+
+ /* Receive bytes. */
+ while (remaining > 0) {
+ eventClear(&this->interrupt_event);
+ if (R_FAILED(eventWait(&this->interrupt_event, InterruptTimeout))) {
+ this->HandleTransactionResult(ResultI2cBusBusy);
+ this->ClearInterruptMask();
+ eventClear(&this->interrupt_event);
+ return ResultI2cTimedOut;
+ }
+
+ if (R_FAILED((rc = this->GetAndHandleTransactionResult()))) {
+ return rc;
+ }
+
+ const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
+ const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast(fifo_status & 0xF));
+
+ for (size_t fifo_idx = 0; remaining > 0 && fifo_idx < fifo_cnt; fifo_idx++) {
+ const u32 val = ReadRegister(&this->i2c_registers->I2C_I2C_RX_FIFO_0);
+ const size_t cur_bytes = std::min(remaining, sizeof(u32));
+ for (size_t i = 0; i < cur_bytes; i++) {
+ cur_dst[i] = static_cast((val >> (8 * i)) & 0xFF);
+ }
+
+ cur_dst += cur_bytes;
+ remaining -= cur_bytes;
+ }
+ }
+
+ /* N doesn't do ClearInterruptMask. */
+ return ResultSuccess;
+}
+
+void I2cBusAccessor::SetBus(I2cBus bus) {
+ this->bus = bus;
+ this->i2c_registers = GetI2cRegisters(bus);
+ this->clkrst_registers.SetBus(bus);
+}
+
+void I2cBusAccessor::CreateInterruptEvent(I2cBus bus) {
+ static constexpr u64 s_interrupts[] = {
+ 0x46, 0x74, 0x7C, 0x98, 0x55, 0x5F
+ };
+ if (static_cast(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
+ std::abort();
+ }
+
+ Handle evt_h;
+ if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[static_cast(bus)], 1))) {
+ std::abort();
+ }
+
+ eventLoadRemote(&this->interrupt_event, evt_h, false);
+}
+
+void I2cBusAccessor::SetClock(SpeedMode speed_mode) {
+ u32 t_high, t_low;
+ u32 clk_div, src_div;
+ u32 debounce;
+
+ switch (speed_mode) {
+ case SpeedMode_Normal:
+ t_high = 2;
+ t_low = 4;
+ clk_div = 0x19;
+ src_div = 0x13;
+ debounce = 2;
+ break;
+ case SpeedMode_Fast:
+ t_high = 2;
+ t_low = 4;
+ clk_div = 0x19;
+ src_div = 0x04;
+ debounce = 2;
+ break;
+ case SpeedMode_FastPlus:
+ t_high = 2;
+ t_low = 4;
+ clk_div = 0x10;
+ src_div = 0x02;
+ debounce = 0;
+ break;
+ case SpeedMode_HighSpeed:
+ t_high = 3;
+ t_low = 8;
+ clk_div = 0x02;
+ src_div = 0x02;
+ debounce = 0;
+ break;
+ default:
+ std::abort();
+ }
+
+ if (speed_mode == SpeedMode_HighSpeed) {
+ WriteRegister(&this->i2c_registers->I2C_I2C_HS_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
+ WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, clk_div);
+ } else {
+ WriteRegister(&this->i2c_registers->I2C_I2C_INTERFACE_TIMING_0_0, (t_high << 8) | (t_low));
+ WriteRegister(&this->i2c_registers->I2C_I2C_CLK_DIVISOR_REGISTER_0, (clk_div << 16));
+ }
+
+ WriteRegister(&this->i2c_registers->I2C_I2C_CNFG_0, debounce);
+ ReadRegister(&this->i2c_registers->I2C_I2C_CNFG_0);
+
+ if (this->pcv_module != PcvModule_I2C5) {
+ if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
+ std::abort();
+ }
+ if (R_FAILED(Pcv::SetClockRate(this->pcv_module, (408'000'000) / (src_div + 1)))) {
+ std::abort();
+ }
+ if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
+ std::abort();
+ }
+ }
+}
+
+void I2cBusAccessor::ResetController() const {
+ if (this->pcv_module != PcvModule_I2C5) {
+ if (R_FAILED(Pcv::SetReset(this->pcv_module, true))) {
+ std::abort();
+ }
+ if (R_FAILED(Pcv::SetClockRate(this->pcv_module, 81'600'000))) {
+ std::abort();
+ }
+ if (R_FAILED(Pcv::SetReset(this->pcv_module, false))) {
+ std::abort();
+ }
+ }
+}
+
+void I2cBusAccessor::ClearBus() const {
+ bool success = false;
+ for (size_t i = 0; i < 3 && !success; i++) {
+ success = true;
+
+ this->ResetController();
+
+ WriteRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x90000);
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x4);
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x2);
+
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
+ {
+ u64 start_tick = armGetSystemTick();
+ while (ReadRegister(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0) & 1) {
+ if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
+ success = false;
+ break;
+ }
+ }
+ }
+ if (!success) {
+ continue;
+ }
+
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0, 0x1);
+ {
+ u64 start_tick = armGetSystemTick();
+ while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_CONFIG_0) & 1) {
+ if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
+ success = false;
+ break;
+ }
+ }
+ }
+ if (!success) {
+ continue;
+ }
+
+ {
+ u64 start_tick = armGetSystemTick();
+ while (ReadRegister(&this->i2c_registers->I2C_I2C_BUS_CLEAR_STATUS_0) & 1) {
+ if (armTicksToNs(armGetSystemTick() - start_tick) > 1'000'000) {
+ success = false;
+ break;
+ }
+ }
+ }
+ if (!success) {
+ continue;
+ }
+ }
+}
+
+void I2cBusAccessor::DisableClock() {
+ if (R_FAILED(Pcv::SetClockEnabled(this->pcv_module, false))) {
+ std::abort();
+ }
+}
+
+void I2cBusAccessor::SetPacketMode() {
+ /* Set PACKET_MODE_EN, MSTR_CONFIG_LOAD */
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_CNFG_0, 0x400);
+ SetRegisterBits(&this->i2c_registers->I2C_I2C_CONFIG_LOAD_0, 0x1);
+
+ /* Set TX_FIFO_TRIGGER, RX_FIFO_TRIGGER */
+ WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFC);
+}
+
+Result I2cBusAccessor::FlushFifos() {
+ WriteRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0, 0xFF);
+
+ /* Wait for flush to finish, check every ms for 5 ms. */
+ for (size_t i = 0; i < 5; i++) {
+ if (!(ReadRegister(&this->i2c_registers->I2C_FIFO_CONTROL_0) & 3)) {
+ return ResultSuccess;
+ }
+ svcSleepThread(1'000'000ul);
+ }
+
+ return ResultI2cBusBusy;
+}
+
+Result I2cBusAccessor::GetTransactionResult() const {
+ const u32 packet_status = ReadRegister(&this->i2c_registers->I2C_PACKET_TRANSFER_STATUS_0);
+ const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
+
+ /* Check for no ack. */
+ if ((packet_status & 0xC) || (interrupt_status & 0x8)) {
+ return ResultI2cNoAck;
+ }
+
+ /* Check for arb lost. */
+ if ((packet_status & 0x2) || (interrupt_status & 0x4)) {
+ this->ClearBus();
+ return ResultI2cBusBusy;
+ }
+
+ return ResultSuccess;
+}
+
+void I2cBusAccessor::HandleTransactionResult(Result result) {
+ if (R_FAILED(result)) {
+ if (result == ResultI2cNoAck || result == ResultI2cBusBusy) {
+ this->ResetController();
+ this->SetClock(this->speed_mode);
+ this->SetPacketMode();
+ this->FlushFifos();
+ } else {
+ std::abort();
+ }
+ }
+}
+
+Result I2cBusAccessor::GetAndHandleTransactionResult() {
+ Result rc = this->GetTransactionResult();
+ this->HandleTransactionResult(rc);
+
+ if (R_FAILED(rc)) {
+ this->ClearInterruptMask();
+ eventClear(&this->interrupt_event);
+ return rc;
+ }
+ return ResultSuccess;
+}
+
+void I2cBusAccessor::WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes) {
+ this->FlushFifos();
+
+ WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, 0x10);
+ WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, static_cast(num_bytes - 1) & 0xFFF);
+
+ const u32 slave_addr_val = ((transfer_mode == TransferMode_Receive) & 1) | ((slave_address & 0x7F) << 1);
+ u32 hdr_val = 0;
+ hdr_val |= ((this->speed_mode == SpeedMode_HighSpeed) & 1) << 22;
+ hdr_val |= ((transfer_mode == TransferMode_Receive) & 1) << 19;
+ hdr_val |= ((addressing_mode != AddressingMode_7Bit) & 1) << 18;
+ hdr_val |= (1 << 17);
+ hdr_val |= (((option & I2cTransactionOption_Stop) != 0) & 1) << 18;
+ hdr_val |= slave_addr_val;
+
+ WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
+}
diff --git a/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.hpp b/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.hpp
new file mode 100644
index 000000000..e08f1586c
--- /dev/null
+++ b/stratosphere/boot/source/i2c_driver/i2c_bus_accessor.hpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2018-2019 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 .
+ */
+
+#pragma once
+#include
+#include
+
+#include "i2c_types.hpp"
+#include "i2c_registers.hpp"
+
+class I2cBusAccessor {
+ private:
+ enum TransferMode {
+ TransferMode_Send = 0,
+ TransferMode_Receive = 1,
+ };
+ static constexpr u64 InterruptTimeout = 100'000'000ul;
+ private:
+ Event interrupt_event;
+ HosMutex open_mutex;
+ HosMutex register_mutex;
+ I2cRegisters *i2c_registers = nullptr;
+ ClkRstRegisters clkrst_registers;
+ SpeedMode speed_mode = SpeedMode_Fast;
+ size_t open_sessions = 0;
+ I2cBus bus = I2cBus_I2c1;
+ PcvModule pcv_module = PcvModule_I2C1;
+ bool suspended = false;
+ public:
+ I2cBusAccessor() {
+ /* ... */
+ }
+ private:
+ inline void ClearInterruptMask() const {
+ WriteRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0);
+ ReadRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0);
+ }
+
+ void SetBus(I2cBus bus);
+ void CreateInterruptEvent(I2cBus bus);
+ void SetClock(SpeedMode speed_mode);
+
+ void ResetController() const;
+ void ClearBus() const;
+ void DisableClock();
+ void SetPacketMode();
+ Result FlushFifos();
+
+ Result GetTransactionResult() const;
+ void HandleTransactionResult(Result result);
+ Result GetAndHandleTransactionResult();
+
+ void WriteTransferHeader(TransferMode transfer_mode, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address, size_t num_bytes);
+ public:
+ void Open(I2cBus bus, SpeedMode speed_mode);
+ void Close();
+ void Suspend();
+ void Resume();
+ void DoInitialConfig();
+
+ size_t GetOpenSessions() const;
+ bool GetBusy() const;
+
+ Result Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
+ Result Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address);
+};
diff --git a/stratosphere/boot/source/i2c_driver/i2c_device_config.cpp b/stratosphere/boot/source/i2c_driver/i2c_device_config.cpp
index 0c514ac00..c39bf8244 100644
--- a/stratosphere/boot/source/i2c_driver/i2c_device_config.cpp
+++ b/stratosphere/boot/source/i2c_driver/i2c_device_config.cpp
@@ -114,4 +114,4 @@ u64 GetI2cDeviceRetryWaitTime(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].retry_wait_time;
-}
\ No newline at end of file
+}
diff --git a/stratosphere/boot/source/i2c_driver/i2c_registers.hpp b/stratosphere/boot/source/i2c_driver/i2c_registers.hpp
new file mode 100644
index 000000000..7293804f1
--- /dev/null
+++ b/stratosphere/boot/source/i2c_driver/i2c_registers.hpp
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2018-2019 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 .
+ */
+
+#pragma once
+#include
+#include
+
+#include "i2c_types.hpp"
+
+static inline uintptr_t GetIoMapping(u64 io_addr, u64 io_size) {
+ u64 vaddr;
+ u64 aligned_addr = (io_addr & ~0xFFFul);
+ u64 aligned_size = io_size + (io_addr - aligned_addr);
+ if (R_FAILED(svcQueryIoMapping(&vaddr, aligned_addr, aligned_size))) {
+ std::abort();
+ }
+ return static_cast(vaddr + (io_addr - aligned_addr));
+}
+
+struct I2cRegisters {
+ volatile u32 I2C_I2C_CNFG_0;
+ volatile u32 I2C_I2C_CMD_ADDR0_0;
+ volatile u32 I2C_I2C_CMD_ADDR1_0;
+ volatile u32 I2C_I2C_CMD_DATA1_0;
+ volatile u32 I2C_I2C_CMD_DATA2_0;
+ volatile u32 _0x14;
+ volatile u32 _0x18;
+ volatile u32 I2C_I2C_STATUS_0;
+ volatile u32 I2C_I2C_SL_CNFG_0;
+ volatile u32 I2C_I2C_SL_RCVD_0;
+ volatile u32 I2C_I2C_SL_STATUS_0;
+ volatile u32 I2C_I2C_SL_ADDR1_0;
+ volatile u32 I2C_I2C_SL_ADDR2_0;
+ volatile u32 I2C_I2C_TLOW_SEXT_0;
+ volatile u32 _0x38;
+ volatile u32 I2C_I2C_SL_DELAY_COUNT_0;
+ volatile u32 I2C_I2C_SL_INT_MASK_0;
+ volatile u32 I2C_I2C_SL_INT_SOURCE_0;
+ volatile u32 I2C_I2C_SL_INT_SET_0;
+ volatile u32 _0x4C;
+ volatile u32 I2C_I2C_TX_PACKET_FIFO_0;
+ volatile u32 I2C_I2C_RX_FIFO_0;
+ volatile u32 I2C_PACKET_TRANSFER_STATUS_0;
+ volatile u32 I2C_FIFO_CONTROL_0;
+ volatile u32 I2C_FIFO_STATUS_0;
+ volatile u32 I2C_INTERRUPT_MASK_REGISTER_0;
+ volatile u32 I2C_INTERRUPT_STATUS_REGISTER_0;
+ volatile u32 I2C_I2C_CLK_DIVISOR_REGISTER_0;
+ volatile u32 I2C_I2C_INTERRUPT_SOURCE_REGISTER_0;
+ volatile u32 I2C_I2C_INTERRUPT_SET_REGISTER_0;
+ volatile u32 I2C_I2C_SLV_TX_PACKET_FIFO_0;
+ volatile u32 I2C_I2C_SLV_RX_FIFO_0;
+ volatile u32 I2C_I2C_SLV_PACKET_STATUS_0;
+ volatile u32 I2C_I2C_BUS_CLEAR_CONFIG_0;
+ volatile u32 I2C_I2C_BUS_CLEAR_STATUS_0;
+ volatile u32 I2C_I2C_CONFIG_LOAD_0;
+ volatile u32 _0x90;
+ volatile u32 I2C_I2C_INTERFACE_TIMING_0_0;
+ volatile u32 I2C_I2C_INTERFACE_TIMING_1_0;
+ volatile u32 I2C_I2C_HS_INTERFACE_TIMING_0_0;
+ volatile u32 I2C_I2C_HS_INTERFACE_TIMING_1_0;
+};
+
+static inline I2cRegisters *GetI2cRegisters(I2cBus bus) {
+ static constexpr uintptr_t s_offsets[] = {
+ 0x0000, 0x0400, 0x0500, 0x0700, 0x1000, 0x1100
+ };
+ if (bus >= sizeof(s_offsets) / sizeof(s_offsets[0])) {
+ std::abort();
+ }
+
+ const uintptr_t registers = GetIoMapping(0x7000c000ul, 0x2000) + s_offsets[static_cast(bus)];
+ return reinterpret_cast(registers);
+}
+
+struct ClkRstRegisters {
+ public:
+ volatile u32 *clk_src_reg;
+ volatile u32 *clk_en_reg;
+ volatile u32 *rst_reg;
+ u32 mask;
+ public:
+ void SetBus(I2cBus bus) {
+ static constexpr uintptr_t s_clk_src_offsets[] = {
+ 0x124, 0x198, 0x1b8, 0x3c4, 0x128, 0x65c
+ };
+ static constexpr uintptr_t s_clk_en_offsets[] = {
+ 0x010, 0x014, 0x018, 0x360, 0x014, 0x280
+ };
+ static constexpr uintptr_t s_rst_offsets[] = {
+ 0x004, 0x008, 0x00c, 0x358, 0x008, 0x28c
+ };
+ static constexpr size_t s_bit_shifts[] = {
+ 12, 22, 3, 7, 15, 6
+ };
+ if (bus >= sizeof(s_clk_src_offsets) / sizeof(s_clk_src_offsets[0])) {
+ std::abort();
+ }
+
+ const uintptr_t registers = GetIoMapping(0x60006000ul, 0x1000);
+ const size_t idx = static_cast(bus);
+ this->clk_src_reg = reinterpret_cast(registers + s_clk_src_offsets[idx]);
+ this->clk_en_reg = reinterpret_cast(registers + s_clk_en_offsets[idx]);
+ this->rst_reg = reinterpret_cast(registers + s_rst_offsets[idx]);
+ this->mask = (1u << s_bit_shifts[idx]);
+ }
+};
+
+static inline void WriteRegister(volatile u32 *reg, u32 val) {
+ *reg = val;
+}
+
+static inline u32 ReadRegister(volatile u32 *reg) {
+ u32 val = *reg;
+ return val;
+}
+
+static inline void SetRegisterBits(volatile u32 *reg, u32 mask) {
+ *reg |= mask;
+}
+
+static inline void ClearRegisterBits(volatile u32 *reg, u32 mask) {
+ *reg &= mask;
+}
+
+static inline void ReadWriteRegisterBits(volatile u32 *reg, u32 val, u32 mask) {
+ *reg = (*reg & (~mask)) | (val & mask);
+}
diff --git a/stratosphere/libstratosphere b/stratosphere/libstratosphere
index a18907153..c3d344cd1 160000
--- a/stratosphere/libstratosphere
+++ b/stratosphere/libstratosphere
@@ -1 +1 @@
-Subproject commit a1890715371cd3e171130d951907f1afb11b08b8
+Subproject commit c3d344cd1bbad9e759732f866db59a84e4f86357