1
0
Fork 0
mirror of https://github.com/Atmosphere-NX/Atmosphere.git synced 2024-12-23 10:52:13 +00:00

boot: refactor i2c driver into namespace

This commit is contained in:
Michael Scire 2019-06-21 20:25:27 -07:00
parent e62754ed56
commit c87be7cd69
42 changed files with 1918 additions and 1836 deletions

View file

@ -33,7 +33,7 @@ endef
#---------------------------------------------------------------------------------
TARGET := $(notdir $(CURDIR))
BUILD := build
SOURCES := source source/i2c_driver source/updater
SOURCES := source source/i2c source/i2c/driver source/i2c/driver/impl source/updater
DATA := data
INCLUDES := include ../../common/include
EXEFS_SRC := exefs_src

View file

@ -18,21 +18,21 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
#include "boot_battery_parameters.hpp"
class BatteryDriver {
private:
I2cSessionImpl i2c_session;
sts::i2c::driver::Session i2c_session;
public:
BatteryDriver() {
I2cDriver::Initialize();
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max17050);
sts::i2c::driver::Initialize();
sts::i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max17050);
}
~BatteryDriver() {
I2cDriver::CloseSession(this->i2c_session);
I2cDriver::Finalize();
sts::i2c::driver::CloseSession(this->i2c_session);
sts::i2c::driver::Finalize();
}
private:
static const Max17050Parameters *GetBatteryParameters();

View file

@ -18,7 +18,7 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
#include "boot_functions.hpp"
#include "boot_bq24193_charger.hpp"
@ -26,19 +26,19 @@ class ChargerDriver {
private:
static constexpr u32 GpioPadName_Bq24193Charger = 0xA;
private:
I2cSessionImpl i2c_session;
sts::i2c::driver::Session i2c_session;
public:
ChargerDriver() {
I2cDriver::Initialize();
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Bq24193);
sts::i2c::driver::Initialize();
sts::i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Bq24193);
Boot::GpioConfigure(GpioPadName_Bq24193Charger);
Boot::GpioSetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output);
}
~ChargerDriver() {
I2cDriver::CloseSession(this->i2c_session);
I2cDriver::Finalize();
sts::i2c::driver::CloseSession(this->i2c_session);
sts::i2c::driver::Finalize();
}
private:
Result Read(u8 addr, u8 *out_data);

View file

@ -28,11 +28,7 @@ static constexpr u32 ExpectedUtmipVal = (ExpectedUtmipDivN | ExpectedUtmipDivM)
static constexpr u32 ExpectedUtmipMask = 0xFFFF00;
static bool IsUsbClockValid() {
u64 _vaddr;
if (R_FAILED(svcQueryIoMapping(&_vaddr, 0x60006000ul, 0x1000))) {
std::abort();
}
volatile u32 *car_regs = reinterpret_cast<volatile u32 *>(_vaddr);
volatile u32 *car_regs = reinterpret_cast<volatile u32 *>(GetIoMapping(0x60006000ul, 0x1000));
const u32 pllu = car_regs[0xC0 >> 2];
const u32 utmip = car_regs[0x480 >> 2];

View file

@ -16,7 +16,7 @@
#include "boot_functions.hpp"
#include "boot_display_config.hpp"
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
/* Helpful defines. */
constexpr size_t DeviceAddressSpaceAlignSize = 0x400000;
@ -56,16 +56,6 @@ static uintptr_t g_gpio_regs = 0;
static uintptr_t g_apb_misc_regs = 0;
static uintptr_t g_mipi_cal_regs = 0;
static inline uintptr_t QueryVirtualAddress(uintptr_t phys, size_t size) {
uintptr_t aligned_phys = phys & ~0xFFFul;
size_t aligned_size = size + (phys - aligned_phys);
uintptr_t aligned_virt;
if (R_FAILED(svcQueryIoMapping(&aligned_virt, aligned_phys, aligned_size))) {
std::abort();
}
return aligned_virt + (phys - aligned_phys);
}
static inline void WriteRegister(volatile u32 *reg, u32 val) {
*reg = val;
}
@ -108,12 +98,12 @@ static inline void ReadWriteRegisterBits(uintptr_t reg, u32 val, u32 mask) {
}
static void InitializeRegisterBaseAddresses() {
g_disp1_regs = QueryVirtualAddress(Disp1Base, Disp1Size);
g_dsi_regs = QueryVirtualAddress(DsiBase, DsiSize);
g_clk_rst_regs = QueryVirtualAddress(ClkRstBase, ClkRstSize);
g_gpio_regs = QueryVirtualAddress(GpioBase, GpioSize);
g_apb_misc_regs = QueryVirtualAddress(ApbMiscBase, ApbMiscSize);
g_mipi_cal_regs = QueryVirtualAddress(MipiCalBase, MipiCalSize);
g_disp1_regs = GetIoMapping(Disp1Base, Disp1Size);
g_dsi_regs = GetIoMapping(DsiBase, DsiSize);
g_clk_rst_regs = GetIoMapping(ClkRstBase, ClkRstSize);
g_gpio_regs = GetIoMapping(GpioBase, GpioSize);
g_apb_misc_regs = GetIoMapping(ApbMiscBase, ApbMiscSize);
g_mipi_cal_regs = GetIoMapping(MipiCalBase, MipiCalSize);
}
static inline void DoRegisterWrites(uintptr_t base_address, const RegisterWrite *reg_writes, size_t num_writes) {
@ -232,17 +222,17 @@ void Boot::InitializeDisplay() {
/* Turn on DSI/voltage rail. */
{
I2cSessionImpl i2c_session;
I2cDriver::Initialize();
I2cDriver::OpenSession(&i2c_session, I2cDevice_Max77620Pmic);
sts::i2c::driver::Session i2c_session;
sts::i2c::driver::Initialize();
ON_SCOPE_EXIT { sts::i2c::driver::Finalize(); };
sts::i2c::driver::OpenSession(&i2c_session, I2cDevice_Max77620Pmic);
if (g_is_mariko) {
Boot::WriteI2cRegister(i2c_session, 0x18, 0x3A);
Boot::WriteI2cRegister(i2c_session, 0x1F, 0x71);
}
Boot::WriteI2cRegister(i2c_session, 0x23, 0xD0);
I2cDriver::Finalize();
}
/* Enable MIPI CAL, DSI, DISP1, HOST1X, UART_FST_MIPI_CAL, DSIA LP clocks. */
@ -449,7 +439,7 @@ void Boot::FinalizeDisplay() {
/* Nintendo waits 5 frames before continuing. */
{
const uintptr_t host1x_vaddr = QueryVirtualAddress(0x500030a4, 4);
const uintptr_t host1x_vaddr = GetIoMapping(0x500030a4, 4);
const u32 start_val = ReadRegister(host1x_vaddr);
while (ReadRegister(host1x_vaddr) < start_val + 5) {
/* spinlock here. */

View file

@ -19,7 +19,8 @@
#include <stratosphere.hpp>
#include "boot_types.hpp"
#include "i2c_driver/i2c_types.hpp"
#include "i2c/i2c_types.hpp"
#include "i2c/driver/i2c_api.hpp"
class Boot {
public:
@ -67,9 +68,9 @@ class Boot {
static bool IsMariko();
/* I2C Utilities. */
static Result ReadI2cRegister(I2cSessionImpl &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size);
static Result WriteI2cRegister(I2cSessionImpl &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size);
static Result WriteI2cRegister(I2cSessionImpl &session, const u8 address, const u8 value);
static Result ReadI2cRegister(sts::i2c::driver::Session &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size);
static Result WriteI2cRegister(sts::i2c::driver::Session &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size);
static Result WriteI2cRegister(sts::i2c::driver::Session &session, const u8 address, const u8 value);
/* Splash Screen/Display utilities. */
static void InitializeDisplay();

View file

@ -30,11 +30,7 @@ static inline u32 GetGpioPadDescriptor(u32 gpio_pad_name) {
static uintptr_t GetGpioBaseAddress() {
if (!g_initialized_gpio_vaddr) {
u64 vaddr;
if (R_FAILED(svcQueryIoMapping(&vaddr, Boot::GpioPhysicalBase, 0x1000))) {
std::abort();
}
g_gpio_vaddr = vaddr;
g_gpio_vaddr = GetIoMapping(Boot::GpioPhysicalBase, 0x1000);
g_initialized_gpio_vaddr = true;
}
return g_gpio_vaddr;

View file

@ -15,7 +15,7 @@
*/
#include "boot_functions.hpp"
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
template<typename F>
static Result RetryUntilSuccess(F f) {
@ -35,21 +35,21 @@ static Result RetryUntilSuccess(F f) {
}
}
Result Boot::ReadI2cRegister(I2cSessionImpl &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) {
Result Boot::ReadI2cRegister(sts::i2c::driver::Session &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) {
if (dst == nullptr || dst_size == 0 || cmd == nullptr || cmd_size == 0) {
std::abort();
}
u8 cmd_list[I2cCommandListFormatter::MaxCommandListSize];
u8 cmd_list[sts::i2c::CommandListFormatter::MaxCommandListSize];
I2cCommandListFormatter formatter(cmd_list, sizeof(cmd_list));
sts::i2c::CommandListFormatter formatter(cmd_list, sizeof(cmd_list));
R_ASSERT(formatter.EnqueueSendCommand(I2cTransactionOption_Start, cmd, cmd_size));
R_ASSERT(formatter.EnqueueReceiveCommand(static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop), dst_size));
return RetryUntilSuccess([&]() { return I2cDriver::ExecuteCommandList(session, dst, dst_size, cmd_list, formatter.GetCurrentSize()); });
return RetryUntilSuccess([&]() { return sts::i2c::driver::ExecuteCommandList(session, dst, dst_size, cmd_list, formatter.GetCurrentSize()); });
}
Result Boot::WriteI2cRegister(I2cSessionImpl &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size) {
Result Boot::WriteI2cRegister(sts::i2c::driver::Session &session, const u8 *src, size_t src_size, const u8 *cmd, size_t cmd_size) {
if (src == nullptr || src_size == 0 || cmd == nullptr || cmd_size == 0) {
std::abort();
}
@ -60,9 +60,9 @@ Result Boot::WriteI2cRegister(I2cSessionImpl &session, const u8 *src, size_t src
std::memcpy(&cmd_list[0], cmd, cmd_size);
std::memcpy(&cmd_list[cmd_size], src, src_size);
return RetryUntilSuccess([&]() { return I2cDriver::Send(session, cmd_list, src_size + cmd_size, static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop)); });
return RetryUntilSuccess([&]() { return sts::i2c::driver::Send(session, cmd_list, src_size + cmd_size, static_cast<I2cTransactionOption>(I2cTransactionOption_Start | I2cTransactionOption_Stop)); });
}
Result Boot::WriteI2cRegister(I2cSessionImpl &session, const u8 address, const u8 value) {
Result Boot::WriteI2cRegister(sts::i2c::driver::Session &session, const u8 address, const u8 value) {
return Boot::WriteI2cRegister(session, &value, sizeof(value), &address, sizeof(address));
}

View file

@ -0,0 +1,77 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c/i2c_types.hpp"
#include "i2c/driver/impl/i2c_pcv.hpp"
#include "i2c/driver/impl/i2c_registers.hpp"
using namespace sts::i2c::driver::impl;
namespace sts::pcv {
void Initialize() {
/* Don't do anything. */
}
void Finalize() {
/* Don't do anything. */
}
Result SetClockRate(PcvModule module, u32 hz) {
/* Get clock/reset registers. */
ClkRstRegisters regs;
regs.SetBus(ConvertFromPcvModule(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 SetClockEnabled(PcvModule module, bool enabled) {
return ResultSuccess;
}
Result SetVoltageEnabled(u32 domain, bool enabled) {
return ResultSuccess;
}
Result SetVoltageValue(u32 domain, u32 voltage) {
return ResultSuccess;
}
Result SetReset(PcvModule module, bool reset) {
/* Get clock/reset registers. */
ClkRstRegisters regs;
regs.SetBus(ConvertFromPcvModule(module));
/* Set/clear reset. */
if (reset) {
SetRegisterBits(regs.rst_reg, regs.mask);
} else {
ClearRegisterBits(regs.rst_reg, ~regs.mask);
}
return ResultSuccess;
}
}

View file

@ -38,11 +38,7 @@ static inline const PinmuxDrivePadDefinition *GetPinmuxDrivePadDefinition(u32 pi
static uintptr_t GetPinmuxBaseAddress() {
if (!g_initialized_pinmux_vaddr) {
u64 vaddr;
if (R_FAILED(svcQueryIoMapping(&vaddr, Boot::ApbMiscPhysicalBase, 0x4000))) {
std::abort();
}
g_pinmux_vaddr = vaddr;
g_pinmux_vaddr = GetIoMapping(Boot::ApbMiscPhysicalBase, 0x4000);
g_initialized_pinmux_vaddr = true;
}
return g_pinmux_vaddr;

View file

@ -18,21 +18,21 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
#include "boot_battery_driver.hpp"
class PmicDriver {
private:
I2cSessionImpl i2c_session;
sts::i2c::driver::Session i2c_session;
public:
PmicDriver() {
I2cDriver::Initialize();
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max77620Pmic);
sts::i2c::driver::Initialize();
sts::i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max77620Pmic);
}
~PmicDriver() {
I2cDriver::CloseSession(this->i2c_session);
I2cDriver::Finalize();
sts::i2c::driver::CloseSession(this->i2c_session);
sts::i2c::driver::Finalize();
}
private:
Result GetPowerStatus(u8 *out);

View file

@ -18,20 +18,20 @@
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver/i2c_api.hpp"
#include "i2c/driver/i2c_api.hpp"
class RtcDriver {
private:
I2cSessionImpl i2c_session;
sts::i2c::driver::Session i2c_session;
public:
RtcDriver() {
I2cDriver::Initialize();
I2cDriver::OpenSession(&this->i2c_session, I2cDevice_Max77620Rtc);
sts::i2c::driver::Initialize();
sts::i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Max77620Rtc);
}
~RtcDriver() {
I2cDriver::CloseSession(this->i2c_session);
I2cDriver::Finalize();
sts::i2c::driver::CloseSession(this->i2c_session);
sts::i2c::driver::Finalize();
}
private:
Result ReadRtcRegister(u8 *out, u8 address);

View file

@ -0,0 +1,188 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_api.hpp"
#include "impl/i2c_resource_manager.hpp"
namespace sts::i2c::driver {
namespace {
/* For convenience. */
using CommandHandler = Result (*)(const u8 **cur_cmd, u8 **cur_dst, Session& session);
/* Command handlers. */
Result SendHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
I2cTransactionOption option = static_cast<I2cTransactionOption>(
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
);
(*cur_cmd)++;
size_t num_bytes = (**cur_cmd);
(*cur_cmd)++;
R_TRY(Send(session, *cur_cmd, num_bytes, option));
(*cur_cmd) += num_bytes;
return ResultSuccess;
}
Result ReceiveHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
I2cTransactionOption option = static_cast<I2cTransactionOption>(
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
);
(*cur_cmd)++;
size_t num_bytes = (**cur_cmd);
(*cur_cmd)++;
R_TRY(Receive(session, *cur_dst, num_bytes, option));
(*cur_dst) += num_bytes;
return ResultSuccess;
}
Result SubCommandHandler(const u8 **cur_cmd, u8 **cur_dst, Session& session) {
const SubCommand sub_cmd = static_cast<SubCommand>((**cur_cmd) >> 2);
(*cur_cmd)++;
switch (sub_cmd) {
case SubCommand::Sleep:
{
const size_t us = (**cur_cmd);
(*cur_cmd)++;
svcSleepThread(us * 1'000ul);
}
break;
default:
std::abort();
}
return ResultSuccess;
}
/* Command handler list. */
constexpr CommandHandler g_cmd_handlers[static_cast<size_t>(Command::Count)] = {
SendHandler,
ReceiveHandler,
SubCommandHandler,
};
inline impl::ResourceManager &GetResourceManager() {
return impl::ResourceManager::GetInstance();
}
inline void CheckInitialized() {
if (!GetResourceManager().IsInitialized()) {
std::abort();
}
}
}
/* Initialization. */
void Initialize() {
GetResourceManager().Initialize();
}
void Finalize() {
GetResourceManager().Finalize();
}
/* Session management. */
void OpenSession(Session *out_session, I2cDevice device) {
CheckInitialized();
if (!impl::IsDeviceSupported(device)) {
std::abort();
}
const auto bus = impl::GetDeviceBus(device);
const auto slave_address = impl::GetDeviceSlaveAddress(device);
const auto addressing_mode = impl::GetDeviceAddressingMode(device);
const auto speed_mode = impl::GetDeviceSpeedMode(device);
const auto max_retries = impl::GetDeviceMaxRetries(device);
const auto retry_wait_time = impl::GetDeviceRetryWaitTime(device);
GetResourceManager().OpenSession(out_session, bus, slave_address, addressing_mode, speed_mode, max_retries, retry_wait_time);
}
void CloseSession(Session &session) {
CheckInitialized();
GetResourceManager().CloseSession(session);
}
/* Communication. */
Result Send(Session &session, const void *src, size_t size, I2cTransactionOption option) {
CheckInitialized();
if (src == nullptr || size == 0) {
std::abort();
}
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(impl::ConvertFromIndex(session.bus_idx)));
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(nullptr, src, size, option, impl::Command::Send);
}
Result Receive(Session &session, void *dst, size_t size, I2cTransactionOption option) {
CheckInitialized();
if (dst == nullptr || size == 0) {
std::abort();
}
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(impl::ConvertFromIndex(session.bus_idx)));
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(dst, nullptr, size, option, impl::Command::Receive);
}
Result ExecuteCommandList(Session &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size) {
CheckInitialized();
if (dst == nullptr || size == 0 || cmd_list == nullptr || cmd_list_size == 0) {
std::abort();
}
u8 *cur_dst = static_cast<u8 *>(dst);
const u8 *cur_cmd = static_cast<const u8 *>(cmd_list);
const u8 *cmd_list_end = cur_cmd + cmd_list_size;
while (cur_cmd < cmd_list_end) {
Command cmd = static_cast<Command>((*cur_cmd) & 3);
if (cmd >= Command::Count) {
std::abort();
}
R_TRY(g_cmd_handlers[static_cast<size_t>(cmd)](&cur_cmd, &cur_dst, session));
}
return ResultSuccess;
}
/* Power management. */
void SuspendBuses() {
GetResourceManager().SuspendBuses();
}
void ResumeBuses() {
GetResourceManager().ResumeBuses();
}
void SuspendPowerBus() {
GetResourceManager().SuspendPowerBus();
}
void ResumePowerBus() {
GetResourceManager().ResumePowerBus();
}
}

View file

@ -0,0 +1,50 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../i2c_types.hpp"
#include "../i2c_command_list.hpp"
namespace sts::i2c::driver {
struct Session {
size_t bus_idx;
size_t session_id;
};
/* Initialization. */
void Initialize();
void Finalize();
/* Session management. */
void OpenSession(Session *out_session, I2cDevice device);
void CloseSession(Session &session);
/* Communication. */
Result Send(Session &session, const void *src, size_t size, I2cTransactionOption option);
Result Receive(Session &session, void *dst, size_t size, I2cTransactionOption option);
Result ExecuteCommandList(Session &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size);
/* Power management. */
void SuspendBuses();
void ResumeBuses();
void SuspendPowerBus();
void ResumePowerBus();
}

View file

@ -0,0 +1,478 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_pcv.hpp"
#include "i2c_bus_accessor.hpp"
namespace sts::i2c::driver::impl {
void BusAccessor::Open(Bus bus, SpeedMode speed_mode) {
std::scoped_lock<HosMutex> 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<HosMutex> lk(this->register_mutex);
/* Set bus/registers. */
this->SetBus(bus);
/* Set pcv module. */
this->pcv_module = ConvertToPcvModule(bus);
/* Set speed mode. */
this->speed_mode = speed_mode;
/* Setup interrupt event. */
this->CreateInterruptEvent(bus);
}
}
void BusAccessor::Close() {
std::scoped_lock<HosMutex> 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 BusAccessor::Suspend() {
std::scoped_lock<HosMutex> lk(this->open_mutex);
std::scoped_lock<HosMutex> lk_reg(this->register_mutex);
if (!this->suspended) {
this->suspended = true;
if (this->pcv_module != PcvModule_I2C5) {
this->DisableClock();
}
}
}
void BusAccessor::Resume() {
if (this->suspended) {
this->DoInitialConfig();
this->suspended = false;
}
}
void BusAccessor::DoInitialConfig() {
std::scoped_lock<HosMutex> 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 BusAccessor::GetOpenSessions() const {
return this->open_sessions;
}
bool BusAccessor::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;
}
void BusAccessor::OnStartTransaction() const {
/* Nothing actually happens here. */
}
void BusAccessor::OnStopTransaction() const {
/* Nothing actually happens here. */
}
Result BusAccessor::StartTransaction(Command command, AddressingMode addressing_mode, u32 slave_address) {
/* Nothing actually happens here... */
return ResultSuccess;
}
Result BusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
std::scoped_lock<HosMutex> lk(this->register_mutex);
const u8 *cur_src = data;
size_t remaining = num_bytes;
/* 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;
}
R_TRY(this->GetAndHandleTransactionResult());
}
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
/* Wait for successful completion. */
while (true) {
R_TRY(this->GetAndHandleTransactionResult());
/* Check PACKET_XFER_COMPLETE */
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
if (interrupt_status & 0x80) {
R_TRY(this->GetAndHandleTransactionResult());
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 BusAccessor::Receive(u8 *out_data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
std::scoped_lock<HosMutex> lk(this->register_mutex);
u8 *cur_dst = out_data;
size_t remaining = num_bytes;
/* 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;
}
R_TRY(this->GetAndHandleTransactionResult());
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast<size_t>(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<u8>((val >> (8 * i)) & 0xFF);
}
cur_dst += cur_bytes;
remaining -= cur_bytes;
}
}
/* N doesn't do ClearInterruptMask. */
return ResultSuccess;
}
void BusAccessor::SetBus(Bus bus) {
this->bus = bus;
this->i2c_registers = GetRegisters(bus);
this->clkrst_registers.SetBus(bus);
}
void BusAccessor::CreateInterruptEvent(Bus bus) {
static constexpr u64 s_interrupts[] = {
0x46, 0x74, 0x7C, 0x98, 0x55, 0x5F
};
if (ConvertToIndex(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
std::abort();
}
Handle evt_h;
if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[ConvertToIndex(bus)], 1))) {
std::abort();
}
eventLoadRemote(&this->interrupt_event, evt_h, false);
}
void BusAccessor::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 BusAccessor::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 BusAccessor::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 BusAccessor::DisableClock() {
if (R_FAILED(pcv::SetClockEnabled(this->pcv_module, false))) {
std::abort();
}
}
void BusAccessor::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 BusAccessor::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 BusAccessor::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 BusAccessor::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 BusAccessor::GetAndHandleTransactionResult() {
const Result transaction_res = this->GetTransactionResult();
R_TRY_CLEANUP(transaction_res, {
this->HandleTransactionResult(transaction_res);
this->ClearInterruptMask();
eventClear(&this->interrupt_event);
});
return ResultSuccess;
}
void BusAccessor::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<u32>(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::SevenBit) & 1) << 18;
hdr_val |= (1 << 17);
hdr_val |= (((option & I2cTransactionOption_Stop) == 0) & 1) << 16;
hdr_val |= slave_addr_val;
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
}
}

View file

@ -0,0 +1,85 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver_types.hpp"
#include "i2c_registers.hpp"
namespace sts::i2c::driver::impl {
class BusAccessor {
private:
enum class TransferMode {
Send = 0,
Receive = 1,
};
static constexpr u64 InterruptTimeout = 100'000'000ul;
private:
Event interrupt_event;
HosMutex open_mutex;
HosMutex register_mutex;
Registers *i2c_registers = nullptr;
ClkRstRegisters clkrst_registers;
SpeedMode speed_mode = SpeedMode::Fast;
size_t open_sessions = 0;
Bus bus = Bus::I2C1;
PcvModule pcv_module = PcvModule_I2C1;
bool suspended = false;
public:
BusAccessor() { /* ... */ }
private:
inline void ClearInterruptMask() const {
WriteRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0);
ReadRegister(&i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0);
}
void SetBus(Bus bus);
void CreateInterruptEvent(Bus 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(Bus bus, SpeedMode speed_mode);
void Close();
void Suspend();
void Resume();
void DoInitialConfig();
size_t GetOpenSessions() const;
bool GetBusy() const;
void OnStartTransaction() const;
Result StartTransaction(Command command, AddressingMode addressing_mode, u32 slave_address);
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);
void OnStopTransaction() const;
};
}

View file

@ -0,0 +1,125 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver_types.hpp"
namespace sts::i2c::driver::impl {
namespace {
struct DeviceConfig {
I2cDevice device;
Bus bus;
u32 slave_address;
AddressingMode addressing_mode;
SpeedMode speed_mode;
u32 max_retries;
u64 retry_wait_time;
};
constexpr DeviceConfig g_device_configs[I2cDevice_Count] = {
{I2cDevice_DebugPad, Bus::I2C1, 0x52, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_TouchPanel, Bus::I2C3, 0x49, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
{I2cDevice_Tmp451, Bus::I2C1, 0x4c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_Nct72, Bus::I2C1, 0x4c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_Alc5639, Bus::I2C1, 0x1c, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_Max77620Rtc, Bus::I2C5, 0x68, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Max77620Pmic, Bus::I2C5, 0x3c, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Max77621Cpu, Bus::I2C5, 0x1b, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Max77621Gpu, Bus::I2C5, 0x1c, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Bq24193, Bus::I2C1, 0x6b, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
{I2cDevice_Max17050, Bus::I2C1, 0x36, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
{I2cDevice_Bm92t30mwv, Bus::I2C1, 0x18, AddressingMode::SevenBit, SpeedMode::Normal, 3, 5'000'000},
{I2cDevice_Ina226Vdd15v0Hb, Bus::I2C2, 0x40, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysCpuDs, Bus::I2C2, 0x41, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysGpuDs, Bus::I2C2, 0x44, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysDdrDs, Bus::I2C2, 0x45, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysAp, Bus::I2C2, 0x46, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysBlDs, Bus::I2C2, 0x47, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Bh1730, Bus::I2C2, 0x29, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysCore, Bus::I2C2, 0x48, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226Soc1V8, Bus::I2C2, 0x49, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226Lpddr1V8, Bus::I2C2, 0x4a, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226Reg1V32, Bus::I2C2, 0x4b, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_Ina226Vdd3V3Sys, Bus::I2C2, 0x4d, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
{I2cDevice_HdmiDdc, Bus::I2C4, 0x50, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_HdmiScdc, Bus::I2C4, 0x54, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_HdmiHdcp, Bus::I2C4, 0x3a, AddressingMode::SevenBit, SpeedMode::Normal, 0, 0},
{I2cDevice_Fan53528, Bus::I2C5, 0xa4, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
{I2cDevice_Max77812_3, Bus::I2C5, 0x31, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
{I2cDevice_Max77812_2, Bus::I2C5, 0x33, AddressingMode::SevenBit, SpeedMode::Fast, 0, 0},
{I2cDevice_Ina226VddDdr0V6, Bus::I2C2, 0x4e, AddressingMode::SevenBit, SpeedMode::Fast, 3, 5'000'000},
};
constexpr size_t NumDeviceConfigs = sizeof(g_device_configs) / sizeof(g_device_configs[0]);
constexpr size_t DeviceInvalidIndex = static_cast<size_t>(-1);
size_t GetDeviceIndex(I2cDevice dev) {
for (size_t i = 0; i < NumDeviceConfigs; i++) {
if (g_device_configs[i].device == dev) {
return i;
}
}
return DeviceInvalidIndex;
}
}
bool IsDeviceSupported(I2cDevice dev) {
return GetDeviceIndex(dev) != DeviceInvalidIndex;
}
Bus GetDeviceBus(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].bus;
}
u32 GetDeviceSlaveAddress(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].slave_address;
}
AddressingMode GetDeviceAddressingMode(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].addressing_mode;
}
SpeedMode GetDeviceSpeedMode(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].speed_mode;
}
u32 GetDeviceMaxRetries(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].max_retries;
}
u64 GetDeviceRetryWaitTime(I2cDevice dev) {
const size_t dev_idx = GetDeviceIndex(dev);
if (dev_idx == DeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].retry_wait_time;
}
}

View file

@ -0,0 +1,99 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../../i2c_types.hpp"
namespace sts::i2c::driver::impl {
enum class Command {
Send = 0,
Receive = 1,
};
enum class Bus {
I2C1 = 0,
I2C2 = 1,
I2C3 = 2,
I2C4 = 3,
I2C5 = 4,
I2C6 = 5,
Count,
};
/* Bus helpers. */
constexpr inline size_t ConvertToIndex(Bus bus) {
return static_cast<size_t>(bus);
}
constexpr inline Bus ConvertFromIndex(size_t idx) {
if (idx >= static_cast<size_t>(Bus::Count)) {
std::abort();
}
return static_cast<Bus>(idx);
}
constexpr inline PcvModule ConvertToPcvModule(Bus bus) {
switch (bus) {
case Bus::I2C1:
return PcvModule_I2C1;
case Bus::I2C2:
return PcvModule_I2C2;
case Bus::I2C3:
return PcvModule_I2C3;
case Bus::I2C4:
return PcvModule_I2C4;
case Bus::I2C5:
return PcvModule_I2C5;
case Bus::I2C6:
return PcvModule_I2C6;
default:
std::abort();
}
}
constexpr inline Bus ConvertFromPcvModule(PcvModule module) {
switch (module) {
case PcvModule_I2C1:
return Bus::I2C1;
case PcvModule_I2C2:
return Bus::I2C2;
case PcvModule_I2C3:
return Bus::I2C3;
case PcvModule_I2C4:
return Bus::I2C4;
case PcvModule_I2C5:
return Bus::I2C5;
case PcvModule_I2C6:
return Bus::I2C6;
default:
std::abort();
}
}
/* Global type functions. */
bool IsDeviceSupported(I2cDevice dev);
Bus GetDeviceBus(I2cDevice dev);
u32 GetDeviceSlaveAddress(I2cDevice dev);
AddressingMode GetDeviceAddressingMode(I2cDevice dev);
SpeedMode GetDeviceSpeedMode(I2cDevice dev);
u32 GetDeviceMaxRetries(I2cDevice dev);
u64 GetDeviceRetryWaitTime(I2cDevice dev);
}

View file

@ -18,16 +18,16 @@
#include <switch.h>
#include <stratosphere.hpp>
/* pcv isn't alive at the time boot runs, but nn::i2c::driver needs nn::pcv. */
/* These are the overrides N puts in boot. */
/* This forward declares the functionality from pcv that i2c::driver uses. */
/* This allows for overriding at compile-time (e.g., for boot sysmodule). */
namespace sts::pcv {
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);
};
void Initialize();
void Finalize();
Result SetClockRate(PcvModule module, u32 hz);
Result SetClockEnabled(PcvModule module, bool enabled);
Result SetVoltageEnabled(u32 domain, bool enabled);
Result SetVoltageValue(u32 domain, u32 voltage);
Result SetReset(PcvModule module, bool reset);
}

View file

@ -0,0 +1,129 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver_types.hpp"
namespace sts::i2c::driver::impl {
struct Registers {
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;
};
struct ClkRstRegisters {
public:
volatile u32 *clk_src_reg;
volatile u32 *clk_en_reg;
volatile u32 *rst_reg;
u32 mask;
public:
void SetBus(Bus bus) {
static constexpr uintptr_t s_clk_src_offsets[ConvertToIndex(Bus::Count)] = {
0x124, 0x198, 0x1b8, 0x3c4, 0x128, 0x65c
};
static constexpr uintptr_t s_clk_en_offsets[ConvertToIndex(Bus::Count)] = {
0x010, 0x014, 0x018, 0x360, 0x014, 0x280
};
static constexpr uintptr_t s_rst_offsets[ConvertToIndex(Bus::Count)] = {
0x004, 0x008, 0x00c, 0x358, 0x008, 0x28c
};
static constexpr size_t s_bit_shifts[ConvertToIndex(Bus::Count)] = {
12, 22, 3, 7, 15, 6
};
const uintptr_t registers = GetIoMapping(0x60006000ul, 0x1000);
const size_t idx = ConvertToIndex(bus);
this->clk_src_reg = reinterpret_cast<volatile u32 *>(registers + s_clk_src_offsets[idx]);
this->clk_en_reg = reinterpret_cast<volatile u32 *>(registers + s_clk_en_offsets[idx]);
this->rst_reg = reinterpret_cast<volatile u32 *>(registers + s_rst_offsets[idx]);
this->mask = (1u << s_bit_shifts[idx]);
}
};
inline Registers *GetRegisters(Bus bus) {
static constexpr uintptr_t s_offsets[ConvertToIndex(Bus::Count)] = {
0x0000, 0x0400, 0x0500, 0x0700, 0x1000, 0x1100
};
const uintptr_t registers = GetIoMapping(0x7000c000ul, 0x2000) + s_offsets[ConvertToIndex(bus)];
return reinterpret_cast<Registers *>(registers);
}
inline void WriteRegister(volatile u32 *reg, u32 val) {
*reg = val;
}
inline u32 ReadRegister(volatile u32 *reg) {
u32 val = *reg;
return val;
}
inline void SetRegisterBits(volatile u32 *reg, u32 mask) {
*reg |= mask;
}
inline void ClearRegisterBits(volatile u32 *reg, u32 mask) {
*reg &= mask;
}
inline void ReadWriteRegisterBits(volatile u32 *reg, u32 val, u32 mask) {
*reg = (*reg & (~mask)) | (val & mask);
}
}

View file

@ -0,0 +1,205 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_pcv.hpp"
#include "i2c_resource_manager.hpp"
namespace sts::i2c::driver::impl {
void ResourceManager::Initialize() {
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
this->ref_cnt++;
}
void ResourceManager::Finalize() {
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
if (this->ref_cnt == 0) {
std::abort();
}
this->ref_cnt--;
if (this->ref_cnt > 0) {
return;
}
{
std::scoped_lock<HosMutex> sess_lk(this->session_open_mutex);
for (size_t i = 0; i < MaxDriverSessions; i++) {
this->sessions[i].Close();
}
}
}
size_t ResourceManager::GetFreeSessionId() const {
for (size_t i = 0; i < MaxDriverSessions; i++) {
if (!this->sessions[i].IsOpen()) {
return i;
}
}
return InvalidSessionId;
}
void ResourceManager::OpenSession(driver::Session *out_session, Bus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time) {
bool need_enable_ldo6 = false;
size_t session_id = InvalidSessionId;
/* Get, open session. */
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (out_session == nullptr || bus >= Bus::Count) {
std::abort();
}
session_id = GetFreeSessionId();
if (session_id == InvalidSessionId) {
std::abort();
}
if ((bus == Bus::I2C2 || bus == Bus::I2C3) && (this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() == 0 && this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() == 0)) {
need_enable_ldo6 = true;
}
out_session->session_id = session_id;
out_session->bus_idx = ConvertToIndex(bus);
this->sessions[session_id].Open(bus, slave_address, addressing_mode, speed_mode, &this->bus_accessors[ConvertToIndex(bus)], max_retries, retry_wait_time);
}
this->sessions[session_id].Start();
if (need_enable_ldo6) {
pcv::Initialize();
if (R_FAILED(pcv::SetVoltageValue(10, 2'900'000))) {
std::abort();
}
if (R_FAILED(pcv::SetVoltageEnabled(10, true))) {
std::abort();
}
pcv::Finalize();
svcSleepThread(560'000ul);
}
}
void ResourceManager::CloseSession(const driver::Session &session) {
bool need_disable_ldo6 = false;
/* Get, open session. */
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (!this->sessions[session.session_id].IsOpen()) {
std::abort();
}
this->sessions[session.session_id].Close();
if ((ConvertFromIndex(session.bus_idx) == Bus::I2C2 || ConvertFromIndex(session.bus_idx) == Bus::I2C3) &&
(this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() == 0 && this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() == 0)) {
need_disable_ldo6 = true;
}
}
if (need_disable_ldo6) {
pcv::Initialize();
if (R_FAILED(pcv::SetVoltageEnabled(10, false))) {
std::abort();
}
pcv::Finalize();
}
}
void ResourceManager::SuspendBuses() {
if (this->ref_cnt == 0) {
std::abort();
}
if (!this->suspended) {
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
this->suspended = true;
for (size_t i = 0; i < ConvertToIndex(Bus::Count); i++) {
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
this->bus_accessors[i].Suspend();
}
}
}
pcv::Initialize();
if (R_FAILED(pcv::SetVoltageEnabled(10, false))) {
std::abort();
}
pcv::Finalize();
}
}
void ResourceManager::ResumeBuses() {
if (this->ref_cnt == 0) {
std::abort();
}
if (this->suspended) {
if (this->bus_accessors[ConvertToIndex(Bus::I2C2)].GetOpenSessions() > 0 || this->bus_accessors[ConvertToIndex(Bus::I2C3)].GetOpenSessions() > 0) {
pcv::Initialize();
if (R_FAILED(pcv::SetVoltageValue(10, 2'900'000))) {
std::abort();
}
if (R_FAILED(pcv::SetVoltageEnabled(10, true))) {
std::abort();
}
pcv::Finalize();
svcSleepThread(1'560'000ul);
}
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
for (size_t i = 0; i < ConvertToIndex(Bus::Count); i++) {
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
this->bus_accessors[i].Resume();
}
}
}
this->suspended = false;
}
}
void ResourceManager::SuspendPowerBus() {
if (this->ref_cnt == 0) {
std::abort();
}
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (!this->power_bus_suspended) {
this->power_bus_suspended = true;
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
this->bus_accessors[PowerBusId].Suspend();
}
}
}
void ResourceManager::ResumePowerBus() {
if (this->ref_cnt == 0) {
std::abort();
}
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (this->power_bus_suspended) {
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
this->bus_accessors[PowerBusId].Resume();
}
this->power_bus_suspended = false;
}
}
}

View file

@ -0,0 +1,78 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "../i2c_api.hpp"
#include "i2c_driver_types.hpp"
#include "i2c_bus_accessor.hpp"
#include "i2c_session.hpp"
namespace sts::i2c::driver::impl {
class ResourceManager {
public:
static constexpr size_t MaxDriverSessions = 40;
static constexpr size_t PowerBusId = ConvertToIndex(Bus::I2C5);
static constexpr size_t InvalidSessionId = static_cast<size_t>(-1);
private:
HosMutex initialize_mutex;
HosMutex session_open_mutex;
size_t ref_cnt = 0;
bool suspended = false;
bool power_bus_suspended = false;
Session sessions[MaxDriverSessions];
BusAccessor bus_accessors[ConvertToIndex(Bus::Count)];
HosMutex transaction_mutexes[ConvertToIndex(Bus::Count)];
public:
ResourceManager() {
/* ... */
}
private:
size_t GetFreeSessionId() const;
public:
/* N uses a singleton here, we'll oblige. */
static ResourceManager &GetInstance() {
static ResourceManager s_instance;
return s_instance;
}
bool IsInitialized() const {
return this->ref_cnt > 0;
}
Session& GetSession(size_t id) {
return this->sessions[id];
}
HosMutex& GetTransactionMutex(Bus bus) {
return this->transaction_mutexes[ConvertToIndex(bus)];
}
void Initialize();
void Finalize();
void OpenSession(driver::Session *out_session, Bus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time);
void CloseSession(const driver::Session &session);
void SuspendBuses();
void ResumeBuses();
void SuspendPowerBus();
void ResumePowerBus();
};
}

View file

@ -0,0 +1,103 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_session.hpp"
namespace sts::i2c::driver::impl {
void Session::Open(Bus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, BusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time) {
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (!this->open) {
this->bus_accessor = bus_accessor;
this->bus = bus;
this->slave_address = slave_address;
this->addressing_mode = addr_mode;
this->max_retries = max_retries;
this->retry_wait_time = retry_wait_time;
this->bus_accessor->Open(this->bus, speed_mode);
this->open = true;
}
}
void Session::Start() {
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->open) {
if (this->bus_accessor->GetOpenSessions() == 1) {
this->bus_accessor->DoInitialConfig();
}
}
}
void Session::Close() {
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->open) {
this->bus_accessor->Close();
this->bus_accessor = nullptr;
this->open = false;
}
}
bool Session::IsOpen() const {
return this->open;
}
Result Session::DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command) {
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->bus_accessor->GetBusy()) {
return ResultI2cBusBusy;
}
this->bus_accessor->OnStartTransaction();
ON_SCOPE_EXIT { this->bus_accessor->OnStopTransaction(); };
R_TRY(this->bus_accessor->StartTransaction(command, this->addressing_mode, this->slave_address));
switch (command) {
case Command::Send:
R_TRY(this->bus_accessor->Send(reinterpret_cast<const u8 *>(src), num_bytes, option, this->addressing_mode, this->slave_address));
break;
case Command::Receive:
R_TRY(this->bus_accessor->Receive(reinterpret_cast<u8 *>(dst), num_bytes, option, this->addressing_mode, this->slave_address));
break;
default:
std::abort();
}
return ResultSuccess;
}
Result Session::DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command) {
size_t i = 0;
while (true) {
R_TRY_CATCH(this->DoTransaction(dst, src, num_bytes, option, command)) {
R_CATCH(ResultI2cTimedOut) {
i++;
if (i <= this->max_retries) {
svcSleepThread(this->retry_wait_time);
continue;
}
return ResultI2cBusBusy;
}
} R_END_TRY_CATCH;
return ResultSuccess;
}
}
}

View file

@ -0,0 +1,51 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver_types.hpp"
#include "i2c_bus_accessor.hpp"
namespace sts::i2c::driver::impl {
class Session {
private:
HosMutex bus_accessor_mutex;
BusAccessor *bus_accessor = nullptr;
Bus bus = Bus::I2C1;
u32 slave_address = 0;
AddressingMode addressing_mode = AddressingMode::SevenBit;
u32 max_retries = 0;
u64 retry_wait_time = 0;
bool open = false;
public:
Session() { /* ... */ }
public:
void Open(Bus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, BusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time);
void Start();
void Close();
bool IsOpen() const;
Result DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command);
Result DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, Command command);
};
}

View file

@ -0,0 +1,83 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
#include "i2c_command_list.hpp"
namespace sts::i2c {
namespace {
/* Useful definitions. */
constexpr size_t SendCommandSize = 2;
constexpr size_t ReceiveCommandSize = 2;
constexpr size_t SleepCommandSize = 2;
}
Result CommandListFormatter::CanEnqueue(size_t size) const {
if (this->cmd_list_size - this->cur_index < size) {
return ResultI2cFullCommandList;
}
return ResultSuccess;
}
Result CommandListFormatter::EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size) {
R_TRY(this->CanEnqueue(SendCommandSize + size));
this->cmd_list[this->cur_index] = static_cast<u8>(Command::Send);
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
this->cur_index++;
this->cmd_list[this->cur_index++] = size;
const u8 *src_u8 = reinterpret_cast<const u8 *>(src);
for (size_t i = 0; i < size; i++) {
this->cmd_list[this->cur_index++] = src_u8[i];
}
return ResultSuccess;
}
Result CommandListFormatter::EnqueueReceiveCommand(I2cTransactionOption option, size_t size) {
R_TRY(this->CanEnqueue(ReceiveCommandSize));
this->cmd_list[this->cur_index] = static_cast<u8>(Command::Receive);
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
this->cur_index++;
this->cmd_list[this->cur_index++] = size;
return ResultSuccess;
}
Result CommandListFormatter::EnqueueSleepCommand(size_t us) {
R_TRY(this->CanEnqueue(SleepCommandSize));
this->cmd_list[this->cur_index] = static_cast<u8>(Command::SubCommand);
this->cmd_list[this->cur_index] |= static_cast<u8>(SubCommand::Sleep) << 2;
this->cur_index++;
this->cmd_list[this->cur_index++] = us;
return ResultSuccess;
}
}

View file

@ -0,0 +1,66 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
namespace sts::i2c {
enum class Command {
Send = 0,
Receive = 1,
SubCommand = 2,
Count,
};
enum class SubCommand {
Sleep = 0,
Count,
};
class CommandListFormatter {
public:
static constexpr size_t MaxCommandListSize = 0x100;
private:
u8 *cmd_list = nullptr;
size_t cmd_list_size = 0;
size_t cur_index = 0;
public:
CommandListFormatter(void *cmd_list, size_t cmd_list_size) : cmd_list(static_cast<u8 *>(cmd_list)), cmd_list_size(cmd_list_size) {
if (cmd_list_size > MaxCommandListSize) {
std::abort();
}
}
~CommandListFormatter() {
this->cmd_list = nullptr;
}
private:
Result CanEnqueue(size_t size) const;
public:
size_t GetCurrentSize() const {
return this->cur_index;
}
Result EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size);
Result EnqueueReceiveCommand(I2cTransactionOption option, size_t size);
Result EnqueueSleepCommand(size_t us);
};
}

View file

@ -0,0 +1,34 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
namespace sts::i2c {
enum class AddressingMode {
SevenBit = 0,
};
enum class SpeedMode {
Normal = 100000,
Fast = 400000,
FastPlus = 1000000,
HighSpeed = 3400000,
};
}

View file

@ -1,90 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#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;
}

View file

@ -1,173 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_api.hpp"
#include "i2c_resource_manager.hpp"
typedef Result (*CommandHandler)(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session);
static Result I2cSendHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
I2cTransactionOption option = static_cast<I2cTransactionOption>(
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
);
(*cur_cmd)++;
size_t num_bytes = (**cur_cmd);
(*cur_cmd)++;
R_TRY(I2cDriver::Send(session, *cur_cmd, num_bytes, option));
(*cur_cmd) += num_bytes;
return ResultSuccess;
}
static Result I2cReceiveHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
I2cTransactionOption option = static_cast<I2cTransactionOption>(
(((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0)
);
(*cur_cmd)++;
size_t num_bytes = (**cur_cmd);
(*cur_cmd)++;
R_TRY(I2cDriver::Receive(session, *cur_dst, num_bytes, option));
(*cur_dst) += num_bytes;
return ResultSuccess;
}
static Result I2cSubCommandHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) {
const I2cSubCommand sub_cmd = static_cast<I2cSubCommand>((**cur_cmd) >> 2);
(*cur_cmd)++;
switch (sub_cmd) {
case I2cSubCommand_Sleep:
{
const size_t us = (**cur_cmd);
(*cur_cmd)++;
svcSleepThread(us * 1'000ul);
}
break;
default:
std::abort();
}
return ResultSuccess;
}
static constexpr CommandHandler g_cmd_handlers[I2cCommand_Count] = {
I2cSendHandler,
I2cReceiveHandler,
I2cSubCommandHandler,
};
static inline I2cResourceManager &GetResourceManager() {
return I2cResourceManager::GetInstance();
}
static inline void CheckInitialized() {
if (!GetResourceManager().IsInitialized()) {
std::abort();
}
}
void I2cDriver::Initialize() {
GetResourceManager().Initialize();
}
void I2cDriver::Finalize() {
GetResourceManager().Finalize();
}
void I2cDriver::OpenSession(I2cSessionImpl *out_session, I2cDevice device) {
CheckInitialized();
if (!IsI2cDeviceSupported(device)) {
std::abort();
}
const I2cBus bus = GetI2cDeviceBus(device);
const u32 slave_address = GetI2cDeviceSlaveAddress(device);
const AddressingMode addressing_mode = GetI2cDeviceAddressingMode(device);
const SpeedMode speed_mode = GetI2cDeviceSpeedMode(device);
const u32 max_retries = GetI2cDeviceMaxRetries(device);
const u64 retry_wait_time = GetI2cDeviceRetryWaitTime(device);
GetResourceManager().OpenSession(out_session, bus, slave_address, addressing_mode, speed_mode, max_retries, retry_wait_time);
}
void I2cDriver::CloseSession(I2cSessionImpl &session) {
CheckInitialized();
GetResourceManager().CloseSession(session);
}
Result I2cDriver::Send(I2cSessionImpl &session, const void *src, size_t size, I2cTransactionOption option) {
CheckInitialized();
if (src == nullptr || size == 0) {
std::abort();
}
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(session.bus));
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(nullptr, src, size, option, DriverCommand_Send);
}
Result I2cDriver::Receive(I2cSessionImpl &session, void *dst, size_t size, I2cTransactionOption option) {
CheckInitialized();
if (dst == nullptr || size == 0) {
std::abort();
}
std::scoped_lock<HosMutex &> lk(GetResourceManager().GetTransactionMutex(session.bus));
return GetResourceManager().GetSession(session.session_id).DoTransactionWithRetry(dst, nullptr, size, option, DriverCommand_Receive);
}
Result I2cDriver::ExecuteCommandList(I2cSessionImpl &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size) {
CheckInitialized();
if (dst == nullptr || size == 0 || cmd_list == nullptr || cmd_list_size == 0) {
std::abort();
}
u8 *cur_dst = static_cast<u8 *>(dst);
const u8 *cur_cmd = static_cast<const u8 *>(cmd_list);
const u8 *cmd_list_end = cur_cmd + cmd_list_size;
while (cur_cmd < cmd_list_end) {
I2cCommand cmd = static_cast<I2cCommand>((*cur_cmd) & 3);
if (cmd >= I2cCommand_Count) {
std::abort();
}
R_TRY(g_cmd_handlers[cmd](&cur_cmd, &cur_dst, session));
}
return ResultSuccess;
}
void I2cDriver::SuspendBuses() {
GetResourceManager().SuspendBuses();
}
void I2cDriver::ResumeBuses() {
GetResourceManager().ResumeBuses();
}
void I2cDriver::SuspendPowerBus() {
GetResourceManager().SuspendPowerBus();
}
void I2cDriver::ResumePowerBus() {
GetResourceManager().ResumePowerBus();
}

View file

@ -1,38 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
#include "i2c_command_list.hpp"
class I2cDriver {
public:
static void Initialize();
static void Finalize();
static void OpenSession(I2cSessionImpl *out_session, I2cDevice device);
static void CloseSession(I2cSessionImpl &session);
static Result Send(I2cSessionImpl &session, const void *src, size_t size, I2cTransactionOption option);
static Result Receive(I2cSessionImpl &session, void *dst, size_t size, I2cTransactionOption option);
static Result ExecuteCommandList(I2cSessionImpl &session, void *dst, size_t size, const void *cmd_list, size_t cmd_list_size);
static void SuspendBuses();
static void ResumeBuses();
static void SuspendPowerBus();
static void ResumePowerBus();
};

View file

@ -1,495 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_bus_accessor.hpp"
#include "boot_pcv.hpp"
void I2cBusAccessor::Open(I2cBus bus, SpeedMode speed_mode) {
std::scoped_lock<HosMutex> 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<HosMutex> 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<HosMutex> 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<HosMutex> lk(this->open_mutex);
std::scoped_lock<HosMutex> 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<HosMutex> 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;
}
void I2cBusAccessor::OnStartTransaction() const {
/* Nothing actually happens here. */
}
void I2cBusAccessor::OnStopTransaction() const {
/* Nothing actually happens here. */
}
Result I2cBusAccessor::StartTransaction(DriverCommand command, AddressingMode addressing_mode, u32 slave_address) {
/* Nothing actually happens here... */
return ResultSuccess;
}
Result I2cBusAccessor::Send(const u8 *data, size_t num_bytes, I2cTransactionOption option, AddressingMode addressing_mode, u32 slave_address) {
std::scoped_lock<HosMutex> lk(this->register_mutex);
const u8 *cur_src = data;
size_t remaining = num_bytes;
/* 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;
}
R_TRY(this->GetAndHandleTransactionResult());
}
WriteRegister(&this->i2c_registers->I2C_INTERRUPT_MASK_REGISTER_0, 0x8C);
/* Wait for successful completion. */
while (true) {
R_TRY(this->GetAndHandleTransactionResult());
/* Check PACKET_XFER_COMPLETE */
const u32 interrupt_status = ReadRegister(&this->i2c_registers->I2C_INTERRUPT_STATUS_REGISTER_0);
if (interrupt_status & 0x80) {
R_TRY(this->GetAndHandleTransactionResult());
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<HosMutex> lk(this->register_mutex);
u8 *cur_dst = out_data;
size_t remaining = num_bytes;
/* 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;
}
R_TRY(this->GetAndHandleTransactionResult());
const u32 fifo_status = ReadRegister(&this->i2c_registers->I2C_FIFO_STATUS_0);
const size_t fifo_cnt = std::min((remaining + 3) >> 2, static_cast<size_t>(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<u8>((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<size_t>(bus) >= sizeof(s_interrupts) / sizeof(s_interrupts[0])) {
std::abort();
}
Handle evt_h;
if (R_FAILED(svcCreateInterruptEvent(&evt_h, s_interrupts[static_cast<size_t>(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() {
const Result transaction_res = this->GetTransactionResult();
R_TRY_CLEANUP(transaction_res, {
this->HandleTransactionResult(transaction_res);
this->ClearInterruptMask();
eventClear(&this->interrupt_event);
});
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<u32>(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) << 16;
hdr_val |= slave_addr_val;
WriteRegister(&this->i2c_registers->I2C_I2C_TX_PACKET_FIFO_0, hdr_val);
}

View file

@ -1,82 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#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;
void OnStartTransaction() const;
Result StartTransaction(DriverCommand command, AddressingMode addressing_mode, u32 slave_address);
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);
void OnStopTransaction() const;
};

View file

@ -1,67 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_command_list.hpp"
Result I2cCommandListFormatter::CanEnqueue(size_t size) const {
if (this->cmd_list_size - this->cur_index < size) {
return ResultI2cFullCommandList;
}
return ResultSuccess;
}
Result I2cCommandListFormatter::EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size) {
R_TRY(this->CanEnqueue(SendCommandSize + size));
this->cmd_list[this->cur_index] = I2cCommand_Send;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
this->cur_index++;
this->cmd_list[this->cur_index++] = size;
const u8 *src_u8 = reinterpret_cast<const u8 *>(src);
for (size_t i = 0; i < size; i++) {
this->cmd_list[this->cur_index++] = src_u8[i];
}
return ResultSuccess;
}
Result I2cCommandListFormatter::EnqueueReceiveCommand(I2cTransactionOption option, size_t size) {
R_TRY(this->CanEnqueue(ReceiveCommandSize));
this->cmd_list[this->cur_index] = I2cCommand_Receive;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Start) != 0) << 6;
this->cmd_list[this->cur_index] |= ((option & I2cTransactionOption_Stop) != 0) << 7;
this->cur_index++;
this->cmd_list[this->cur_index++] = size;
return ResultSuccess;
}
Result I2cCommandListFormatter::EnqueueSleepCommand(size_t us) {
R_TRY(this->CanEnqueue(SleepCommandSize));
this->cmd_list[this->cur_index] = I2cCommand_SubCommand;
this->cmd_list[this->cur_index] |= I2cSubCommand_Sleep << 2;
this->cur_index++;
this->cmd_list[this->cur_index++] = us;
return ResultSuccess;
}

View file

@ -1,53 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
class I2cCommandListFormatter {
public:
static constexpr size_t MaxCommandListSize = 0x100;
static constexpr size_t SendCommandSize = 2;
static constexpr size_t ReceiveCommandSize = 2;
static constexpr size_t SleepCommandSize = 2;
private:
u8 *cmd_list = nullptr;
size_t cmd_list_size = 0;
size_t cur_index = 0;
public:
I2cCommandListFormatter(void *cmd_list, size_t cmd_list_size) : cmd_list(static_cast<u8 *>(cmd_list)), cmd_list_size(cmd_list_size) {
if (cmd_list_size > MaxCommandListSize) {
std::abort();
}
}
~I2cCommandListFormatter() {
this->cmd_list = nullptr;
}
private:
Result CanEnqueue(size_t size) const;
public:
size_t GetCurrentSize() const {
return this->cur_index;
}
Result EnqueueSendCommand(I2cTransactionOption option, const void *src, size_t size);
Result EnqueueReceiveCommand(I2cTransactionOption option, size_t size);
Result EnqueueSleepCommand(size_t us);
};

View file

@ -1,117 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
struct DeviceConfig {
I2cDevice device;
I2cBus bus;
u32 slave_address;
AddressingMode addressing_mode;
SpeedMode speed_mode;
u32 max_retries;
u64 retry_wait_time;
};
static constexpr DeviceConfig g_device_configs[I2cDevice_Count] = {
{I2cDevice_DebugPad, I2cBus_I2c1, 0x52, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_TouchPanel, I2cBus_I2c3, 0x49, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
{I2cDevice_Tmp451, I2cBus_I2c1, 0x4c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_Nct72, I2cBus_I2c1, 0x4c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_Alc5639, I2cBus_I2c1, 0x1c, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_Max77620Rtc, I2cBus_I2c5, 0x68, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Max77620Pmic, I2cBus_I2c5, 0x3c, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Max77621Cpu, I2cBus_I2c5, 0x1b, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Max77621Gpu, I2cBus_I2c5, 0x1c, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Bq24193, I2cBus_I2c1, 0x6b, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
{I2cDevice_Max17050, I2cBus_I2c1, 0x36, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
{I2cDevice_Bm92t30mwv, I2cBus_I2c1, 0x18, AddressingMode_7Bit, SpeedMode_Normal, 3, 5'000'000},
{I2cDevice_Ina226Vdd15v0Hb, I2cBus_I2c2, 0x40, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysCpuDs, I2cBus_I2c2, 0x41, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysGpuDs, I2cBus_I2c2, 0x44, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysDdrDs, I2cBus_I2c2, 0x45, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysAp, I2cBus_I2c2, 0x46, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysBlDs, I2cBus_I2c2, 0x47, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Bh1730, I2cBus_I2c2, 0x29, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226VsysCore, I2cBus_I2c2, 0x48, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226Soc1V8, I2cBus_I2c2, 0x49, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226Lpddr1V8, I2cBus_I2c2, 0x4a, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226Reg1V32, I2cBus_I2c2, 0x4b, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_Ina226Vdd3V3Sys, I2cBus_I2c2, 0x4d, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
{I2cDevice_HdmiDdc, I2cBus_I2c4, 0x50, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_HdmiScdc, I2cBus_I2c4, 0x54, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_HdmiHdcp, I2cBus_I2c4, 0x3a, AddressingMode_7Bit, SpeedMode_Normal, 0, 0},
{I2cDevice_Fan53528, I2cBus_I2c5, 0xa4, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
{I2cDevice_Max77812_3, I2cBus_I2c5, 0x31, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
{I2cDevice_Max77812_2, I2cBus_I2c5, 0x33, AddressingMode_7Bit, SpeedMode_Fast, 0, 0},
{I2cDevice_Ina226VddDdr0V6, I2cBus_I2c2, 0x4e, AddressingMode_7Bit, SpeedMode_Fast, 3, 5'000'000},
};
static constexpr size_t NumDeviceConfigs = sizeof(g_device_configs) / sizeof(g_device_configs[0]);
static constexpr size_t I2cDeviceInvalidIndex = static_cast<size_t>(-1);
static size_t GetI2cDeviceIndex(I2cDevice dev) {
for (size_t i = 0; i < NumDeviceConfigs; i++) {
if (g_device_configs[i].device == dev) {
return i;
}
}
return I2cDeviceInvalidIndex;
}
bool IsI2cDeviceSupported(I2cDevice dev) {
return GetI2cDeviceIndex(dev) != I2cDeviceInvalidIndex;
}
I2cBus GetI2cDeviceBus(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].bus;
}
u32 GetI2cDeviceSlaveAddress(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].slave_address;
}
AddressingMode GetI2cDeviceAddressingMode(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].addressing_mode;
}
SpeedMode GetI2cDeviceSpeedMode(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].speed_mode;
}
u32 GetI2cDeviceMaxRetries(I2cDevice dev) {
const size_t dev_idx = GetI2cDeviceIndex(dev);
if (dev_idx == I2cDeviceInvalidIndex) { std::abort(); }
return g_device_configs[dev_idx].max_retries;
}
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;
}

View file

@ -1,99 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_driver_session.hpp"
void I2cDriverSession::Open(I2cBus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, I2cBusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time){
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (!this->open) {
this->bus_accessor = bus_accessor;
this->bus = bus;
this->slave_address = slave_address;
this->addressing_mode = addr_mode;
this->max_retries = max_retries;
this->retry_wait_time = retry_wait_time;
this->bus_accessor->Open(this->bus, speed_mode);
this->open = true;
}
}
void I2cDriverSession::Start(){
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->open) {
if (this->bus_accessor->GetOpenSessions() == 1) {
this->bus_accessor->DoInitialConfig();
}
}
}
void I2cDriverSession::Close(){
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->open) {
this->bus_accessor->Close();
this->bus_accessor = nullptr;
this->open = false;
}
}
bool I2cDriverSession::IsOpen() const{
return this->open;
}
Result I2cDriverSession::DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, DriverCommand command){
std::scoped_lock<HosMutex> lk(this->bus_accessor_mutex);
if (this->bus_accessor->GetBusy()) {
return ResultI2cBusBusy;
}
this->bus_accessor->OnStartTransaction();
ON_SCOPE_EXIT { this->bus_accessor->OnStopTransaction(); };
R_TRY(this->bus_accessor->StartTransaction(command, this->addressing_mode, this->slave_address));
switch (command) {
case DriverCommand_Send:
R_TRY(this->bus_accessor->Send(reinterpret_cast<const u8 *>(src), num_bytes, option, this->addressing_mode, this->slave_address));
break;
case DriverCommand_Receive:
R_TRY(this->bus_accessor->Receive(reinterpret_cast<u8 *>(dst), num_bytes, option, this->addressing_mode, this->slave_address));
break;
default:
std::abort();
}
return ResultSuccess;
}
Result I2cDriverSession::DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, DriverCommand command){
size_t i = 0;
while (true) {
R_TRY_CATCH(this->DoTransaction(dst, src, num_bytes, option, command)) {
R_CATCH(ResultI2cTimedOut) {
i++;
if (i <= this->max_retries) {
svcSleepThread(this->retry_wait_time);
continue;
}
return ResultI2cBusBusy;
}
} R_END_TRY_CATCH;
return ResultSuccess;
}
}

View file

@ -1,47 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
#include "i2c_bus_accessor.hpp"
class I2cDriverSession {
private:
HosMutex bus_accessor_mutex;
I2cBusAccessor *bus_accessor = nullptr;
I2cBus bus = I2cBus_I2c1;
u32 slave_address = 0;
AddressingMode addressing_mode = AddressingMode_7Bit;
u32 max_retries = 0;
u64 retry_wait_time = 0;
bool open = false;
public:
I2cDriverSession() {
/* ... */
}
public:
void Open(I2cBus bus, u32 slave_address, AddressingMode addr_mode, SpeedMode speed_mode, I2cBusAccessor *bus_accessor, u32 max_retries, u64 retry_wait_time);
void Start();
void Close();
bool IsOpen() const;
Result DoTransaction(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, DriverCommand command);
Result DoTransactionWithRetry(void *dst, const void *src, size_t num_bytes, I2cTransactionOption option, DriverCommand command);
};

View file

@ -1,141 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#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<uintptr_t>(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<size_t>(bus)];
return reinterpret_cast<I2cRegisters *>(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<size_t>(bus);
this->clk_src_reg = reinterpret_cast<volatile u32 *>(registers + s_clk_src_offsets[idx]);
this->clk_en_reg = reinterpret_cast<volatile u32 *>(registers + s_clk_en_offsets[idx]);
this->rst_reg = reinterpret_cast<volatile u32 *>(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);
}

View file

@ -1,199 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <switch.h>
#include <stratosphere.hpp>
#include "boot_pcv.hpp"
#include "i2c_resource_manager.hpp"
void I2cResourceManager::Initialize() {
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
this->ref_cnt++;
}
void I2cResourceManager::Finalize() {
std::scoped_lock<HosMutex> lk(this->initialize_mutex);
if (this->ref_cnt == 0) {
std::abort();
}
this->ref_cnt--;
if (this->ref_cnt > 0) {
return;
}
{
std::scoped_lock<HosMutex> sess_lk(this->session_open_mutex);
for (size_t i = 0; i < MaxDriverSessions; i++) {
this->sessions[i].Close();
}
}
}
size_t I2cResourceManager::GetFreeSessionId() const {
for (size_t i = 0; i < MaxDriverSessions; i++) {
if (!this->sessions[i].IsOpen()) {
return i;
}
}
return InvalidSessionId;
}
void I2cResourceManager::OpenSession(I2cSessionImpl *out_session, I2cBus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time) {
bool need_enable_ldo6 = false;
size_t session_id = InvalidSessionId;
/* Get, open session. */
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (out_session == nullptr || bus >= MaxBuses) {
std::abort();
}
session_id = GetFreeSessionId();
if (session_id == InvalidSessionId) {
std::abort();
}
if ((bus == I2cBus_I2c2 || bus == I2cBus_I2c3) && (this->bus_accessors[I2cBus_I2c2].GetOpenSessions() == 0 && this->bus_accessors[I2cBus_I2c3].GetOpenSessions() == 0)) {
need_enable_ldo6 = true;
}
out_session->session_id = session_id;
out_session->bus = bus;
this->sessions[session_id].Open(bus, slave_address, addressing_mode, speed_mode, &this->bus_accessors[bus], max_retries, retry_wait_time);
}
this->sessions[session_id].Start();
if (need_enable_ldo6) {
Pcv::Initialize();
if (R_FAILED(Pcv::SetVoltageValue(10, 2'900'000))) {
std::abort();
}
if (R_FAILED(Pcv::SetVoltageEnabled(10, true))) {
std::abort();
}
Pcv::Finalize();
svcSleepThread(560'000ul);
}
}
void I2cResourceManager::CloseSession(const I2cSessionImpl &session) {
bool need_disable_ldo6 = false;
/* Get, open session. */
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (!this->sessions[session.session_id].IsOpen()) {
std::abort();
}
this->sessions[session.session_id].Close();
if ((session.bus == I2cBus_I2c2 || session.bus == I2cBus_I2c3) && (this->bus_accessors[I2cBus_I2c2].GetOpenSessions() == 0 && this->bus_accessors[I2cBus_I2c3].GetOpenSessions() == 0)) {
need_disable_ldo6 = true;
}
}
if (need_disable_ldo6) {
Pcv::Initialize();
if (R_FAILED(Pcv::SetVoltageEnabled(10, false))) {
std::abort();
}
Pcv::Finalize();
}
}
void I2cResourceManager::SuspendBuses() {
if (this->ref_cnt == 0) {
std::abort();
}
if (!this->suspended) {
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
this->suspended = true;
for (size_t i = 0; i < MaxBuses; i++) {
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
this->bus_accessors[i].Suspend();
}
}
}
Pcv::Initialize();
if (R_FAILED(Pcv::SetVoltageEnabled(10, false))) {
std::abort();
}
Pcv::Finalize();
}
}
void I2cResourceManager::ResumeBuses() {
if (this->ref_cnt == 0) {
std::abort();
}
if (this->suspended) {
if (this->bus_accessors[I2cBus_I2c2].GetOpenSessions() > 0 || this->bus_accessors[I2cBus_I2c3].GetOpenSessions() > 0) {
Pcv::Initialize();
if (R_FAILED(Pcv::SetVoltageValue(10, 2'900'000))) {
std::abort();
}
if (R_FAILED(Pcv::SetVoltageEnabled(10, true))) {
std::abort();
}
Pcv::Finalize();
svcSleepThread(1'560'000ul);
}
{
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
for (size_t i = 0; i < MaxBuses; i++) {
if (i != PowerBusId && this->bus_accessors[i].GetOpenSessions() > 0) {
this->bus_accessors[i].Resume();
}
}
}
this->suspended = false;
}
}
void I2cResourceManager::SuspendPowerBus() {
if (this->ref_cnt == 0) {
std::abort();
}
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (!this->power_bus_suspended) {
this->power_bus_suspended = true;
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
this->bus_accessors[PowerBusId].Suspend();
}
}
}
void I2cResourceManager::ResumePowerBus() {
if (this->ref_cnt == 0) {
std::abort();
}
std::scoped_lock<HosMutex> lk(this->session_open_mutex);
if (this->power_bus_suspended) {
if (this->bus_accessors[PowerBusId].GetOpenSessions() > 0) {
this->bus_accessors[PowerBusId].Resume();
}
this->power_bus_suspended = false;
}
}

View file

@ -1,78 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
#include "i2c_types.hpp"
#include "i2c_bus_accessor.hpp"
#include "i2c_driver_session.hpp"
class I2cResourceManager {
public:
static constexpr size_t MaxDriverSessions = 40;
static constexpr size_t MaxBuses = 6;
static constexpr size_t PowerBusId = static_cast<size_t>(I2cBus_I2c5);
static constexpr size_t InvalidSessionId = static_cast<size_t>(-1);
private:
HosMutex initialize_mutex;
HosMutex session_open_mutex;
size_t ref_cnt = 0;
bool suspended = false;
bool power_bus_suspended = false;
I2cDriverSession sessions[MaxDriverSessions];
I2cBusAccessor bus_accessors[MaxBuses];
HosMutex transaction_mutexes[MaxBuses];
public:
I2cResourceManager() {
/* ... */
}
private:
size_t GetFreeSessionId() const;
public:
/* N uses a singleton here, we'll oblige. */
static I2cResourceManager &GetInstance() {
static I2cResourceManager s_instance;
return s_instance;
}
bool IsInitialized() const {
return this->ref_cnt > 0;
}
I2cDriverSession& GetSession(size_t id) {
return this->sessions[id];
}
HosMutex& GetTransactionMutex(I2cBus bus) {
if (bus >= MaxBuses) {
std::abort();
}
return this->transaction_mutexes[bus];
}
void Initialize();
void Finalize();
void OpenSession(I2cSessionImpl *out_session, I2cBus bus, u32 slave_address, AddressingMode addressing_mode, SpeedMode speed_mode, u32 max_retries, u64 retry_wait_time);
void CloseSession(const I2cSessionImpl &session);
void SuspendBuses();
void ResumeBuses();
void SuspendPowerBus();
void ResumePowerBus();
};

View file

@ -1,69 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <switch.h>
#include <stratosphere.hpp>
enum AddressingMode {
AddressingMode_7Bit = 0,
};
enum SpeedMode {
SpeedMode_Normal = 100000,
SpeedMode_Fast = 400000,
SpeedMode_FastPlus = 1000000,
SpeedMode_HighSpeed = 3400000,
};
enum I2cBus {
I2cBus_I2c1 = 0,
I2cBus_I2c2 = 1,
I2cBus_I2c3 = 2,
I2cBus_I2c4 = 3,
I2cBus_I2c5 = 4,
I2cBus_I2c6 = 5,
};
enum DriverCommand {
DriverCommand_Send = 0,
DriverCommand_Receive = 1,
};
struct I2cSessionImpl {
I2cBus bus;
size_t session_id;
};
enum I2cCommand {
I2cCommand_Send = 0,
I2cCommand_Receive = 1,
I2cCommand_SubCommand = 2,
I2cCommand_Count,
};
enum I2cSubCommand {
I2cSubCommand_Sleep = 0,
I2cSubCommand_Count,
};
bool IsI2cDeviceSupported(I2cDevice dev);
I2cBus GetI2cDeviceBus(I2cDevice dev);
u32 GetI2cDeviceSlaveAddress(I2cDevice dev);
AddressingMode GetI2cDeviceAddressingMode(I2cDevice dev);
SpeedMode GetI2cDeviceSpeedMode(I2cDevice dev);
u32 GetI2cDeviceMaxRetries(I2cDevice dev);
u64 GetI2cDeviceRetryWaitTime(I2cDevice dev);

@ -1 +1 @@
Subproject commit 0c26276b21e4cf0f7a0665c0280b3888ebfbd1af
Subproject commit 2e36c24a01f30f91873cfab208ffdfe13a18f097