From 31c4c330420d09e1eda5f1a80468b482030e266e Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Thu, 2 May 2019 08:30:13 -0700 Subject: [PATCH] boot: finish i2c driver --- stratosphere/boot/source/boot_functions.hpp | 5 + stratosphere/boot/source/boot_i2c_utils.cpp | 71 +++++++ .../boot/source/i2c_driver/i2c_api.cpp | 186 ++++++++++++++++++ .../boot/source/i2c_driver/i2c_api.hpp | 38 ++++ .../source/i2c_driver/i2c_command_list.cpp | 74 +++++++ .../source/i2c_driver/i2c_command_list.hpp | 53 +++++ .../boot/source/i2c_driver/i2c_types.hpp | 12 ++ 7 files changed, 439 insertions(+) create mode 100644 stratosphere/boot/source/boot_i2c_utils.cpp create mode 100644 stratosphere/boot/source/i2c_driver/i2c_api.cpp create mode 100644 stratosphere/boot/source/i2c_driver/i2c_api.hpp create mode 100644 stratosphere/boot/source/i2c_driver/i2c_command_list.cpp create mode 100644 stratosphere/boot/source/i2c_driver/i2c_command_list.hpp diff --git a/stratosphere/boot/source/boot_functions.hpp b/stratosphere/boot/source/boot_functions.hpp index faafba936..03ba21e52 100644 --- a/stratosphere/boot/source/boot_functions.hpp +++ b/stratosphere/boot/source/boot_functions.hpp @@ -19,6 +19,7 @@ #include #include "boot_types.hpp" +#include "i2c_driver/i2c_types.hpp" class Boot { public: @@ -39,4 +40,8 @@ class Boot { /* SPL Utilities. */ static HardwareType GetHardwareType(); + + /* 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); }; diff --git a/stratosphere/boot/source/boot_i2c_utils.cpp b/stratosphere/boot/source/boot_i2c_utils.cpp new file mode 100644 index 000000000..1a4ebaa5d --- /dev/null +++ b/stratosphere/boot/source/boot_i2c_utils.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "boot_functions.hpp" +#include "i2c_driver/i2c_api.hpp" + +template +static Result RetryUntilSuccess(F f) { + constexpr u64 timeout = 10'000'000'000ul; + constexpr u64 retry_interval = 20'000'000ul; + + u64 cur_time = 0; + while (true) { + Result rc = f(); + if (R_SUCCEEDED(rc)) { + return rc; + } else { + cur_time += retry_interval; + if (cur_time >= timeout) { + return rc; + } + } + svcSleepThread(retry_interval); + } +} + +Result Boot::ReadI2cRegister(I2cSessionImpl &session, u8 *dst, size_t dst_size, const u8 *cmd, size_t cmd_size) { + Result rc; + if (dst == nullptr || dst_size == 0 || cmd == nullptr || cmd_size == 0) { + std::abort(); + } + + u8 cmd_list[I2cCommandListFormatter::MaxCommandListSize]; + + I2cCommandListFormatter formatter(cmd_list, sizeof(cmd_list)); + if (R_FAILED((rc = formatter.EnqueueSendCommand(I2cTransactionOption_Start, cmd, cmd_size)))) { + std::abort(); + } + if (R_FAILED((rc = formatter.EnqueueReceiveCommand(static_cast(I2cTransactionOption_Start | I2cTransactionOption_Stop), dst_size)))) { + std::abort(); + } + + return RetryUntilSuccess([&]() { return I2cDriver::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) { + if (src == nullptr || src_size == 0 || cmd == nullptr || cmd_size == 0) { + std::abort(); + } + + u8 cmd_list[0x20]; + + /* N doesn't use a CommandListFormatter here... */ + 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_Start | I2cTransactionOption_Stop)); }); +} diff --git a/stratosphere/boot/source/i2c_driver/i2c_api.cpp b/stratosphere/boot/source/i2c_driver/i2c_api.cpp new file mode 100644 index 000000000..c923be298 --- /dev/null +++ b/stratosphere/boot/source/i2c_driver/i2c_api.cpp @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software{ + +} you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY{ + +} without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "i2c_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( + (((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0) + ); + (*cur_cmd)++; + + size_t num_bytes = (**cur_cmd); + (*cur_cmd)++; + + Result rc = I2cDriver::Send(session, *cur_cmd, num_bytes, option); + if (R_FAILED(rc)) { + return rc; + } + (*cur_cmd) += num_bytes; + + return ResultSuccess; +} + +static Result I2cReceiveHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) { + I2cTransactionOption option = static_cast( + (((**cur_cmd) & (1 << 6)) ? I2cTransactionOption_Start : 0) | (((**cur_cmd) & (1 << 7)) ? I2cTransactionOption_Stop : 0) + ); + (*cur_cmd)++; + + size_t num_bytes = (**cur_cmd); + (*cur_cmd)++; + + Result rc = I2cDriver::Receive(session, *cur_dst, num_bytes, option); + if (R_FAILED(rc)) { + return rc; + } + (*cur_dst) += num_bytes; + + return ResultSuccess; +} + +static Result I2cSubCommandHandler(const u8 **cur_cmd, u8 **cur_dst, I2cSessionImpl& session) { + const I2cSubCommand sub_cmd = static_cast((**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 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 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(dst); + const u8 *cur_cmd = static_cast(cmd_list); + const u8 *cmd_list_end = cur_cmd + cmd_list_size; + + while (cur_cmd < cmd_list_end) { + I2cCommand cmd = static_cast((*cur_cmd) & 3); + if (cmd >= I2cCommand_Count) { + std::abort(); + } + + Result rc = g_cmd_handlers[cmd](&cur_cmd, &cur_dst, session); + if (R_FAILED(rc)) { + return rc; + } + } + + return ResultSuccess; +} + +void I2cDriver::SuspendBuses() { + GetResourceManager().SuspendBuses(); +} + +void I2cDriver::ResumeBuses() { + GetResourceManager().ResumeBuses(); +} + +void I2cDriver::SuspendPowerBus() { + GetResourceManager().SuspendPowerBus(); +} + +void I2cDriver::ResumePowerBus() { + GetResourceManager().ResumePowerBus(); +} \ No newline at end of file diff --git a/stratosphere/boot/source/i2c_driver/i2c_api.hpp b/stratosphere/boot/source/i2c_driver/i2c_api.hpp new file mode 100644 index 000000000..1db002ff2 --- /dev/null +++ b/stratosphere/boot/source/i2c_driver/i2c_api.hpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "i2c_types.hpp" +#include "i2c_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(); +}; \ No newline at end of file diff --git a/stratosphere/boot/source/i2c_driver/i2c_command_list.cpp b/stratosphere/boot/source/i2c_driver/i2c_command_list.cpp new file mode 100644 index 000000000..70e8024d8 --- /dev/null +++ b/stratosphere/boot/source/i2c_driver/i2c_command_list.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software{ + +} you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY{ + +} without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "i2c_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) { + Result rc = this->CanEnqueue(SendCommandSize + size); + if (R_FAILED(rc)) { return rc; } + + 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(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) { + Result rc = this->CanEnqueue(ReceiveCommandSize); + if (R_FAILED(rc)) { return rc; } + + 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) { + Result rc = this->CanEnqueue(SleepCommandSize); + if (R_FAILED(rc)) { return rc; } + + 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; +} diff --git a/stratosphere/boot/source/i2c_driver/i2c_command_list.hpp b/stratosphere/boot/source/i2c_driver/i2c_command_list.hpp new file mode 100644 index 000000000..6c09d8f28 --- /dev/null +++ b/stratosphere/boot/source/i2c_driver/i2c_command_list.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +#include "i2c_types.hpp" + +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(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); +}; diff --git a/stratosphere/boot/source/i2c_driver/i2c_types.hpp b/stratosphere/boot/source/i2c_driver/i2c_types.hpp index 527e9e3d9..554fd8b7b 100644 --- a/stratosphere/boot/source/i2c_driver/i2c_types.hpp +++ b/stratosphere/boot/source/i2c_driver/i2c_types.hpp @@ -48,6 +48,18 @@ struct I2cSessionImpl { 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);