diff --git a/libraries/libmesosphere/Makefile b/libraries/libmesosphere/Makefile index 7e8c26d..18027ae 100644 --- a/libraries/libmesosphere/Makefile +++ b/libraries/libmesosphere/Makefile @@ -16,7 +16,7 @@ CXXFLAGS := $(CFLAGS) $(ATMOSPHERE_CXXFLAGS) -fno-use-cxa-atexit -flto ASFLAGS := $(ATMOSPHERE_ASFLAGS) $(SETTINGS) $(DEFINES) $(INCLUDE) -SOURCES += $(call ALL_SOURCE_DIRS,../libvapours/source) +SOURCES += $(foreach v,$(call ALL_SOURCE_DIRS,../libvapours/source),$(if $(findstring ../libvapours/source/sdmmc,$v),,$v)) LIBS := diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.cpp deleted file mode 100644 index c9234e9..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.cpp +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_base_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - #if defined(AMS_SDMMC_THREAD_SAFE) - - #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() std::scoped_lock lk(this->base_device->device_mutex) - - #else - - #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() - - #endif - - void BaseDevice::GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const { - AMS_ABORT_UNLESS(out_c_size_mult != nullptr); - AMS_ABORT_UNLESS(out_read_bl_len != nullptr); - - /* Extract C_SIZE_MULT and READ_BL_LEN from the CSD. */ - *out_c_size_mult = static_cast((this->csd[2] >> 7) & 0x7); - *out_read_bl_len = static_cast((this->csd[4] >> 8) & 0xF); - } - - Result BaseDevice::SetLegacyMemoryCapacity() { - /* Get csize from the csd. */ - const u32 c_size = ((this->csd[3] >> 6) & 0x3FF) | ((this->csd[4] & 0x3) << 10); - - /* Get c_size_mult and read_bl_len. */ - u8 c_size_mult, read_bl_len; - this->GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); - - /* Validate the parameters. */ - R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); - - /* Set memory capacity. */ - this->memory_capacity = (c_size + 1) << ((read_bl_len + c_size_mult + 2) - 9); - this->is_valid_memory_capacity = true; - - return ResultSuccess(); - } - - Result BaseDevice::CheckDeviceStatus(u32 r1_resp) const { - /* Check if there are any errors at all. */ - R_SUCCEED_IF((r1_resp & DeviceStatus_ErrorMask) == 0); - - /* Check errors individually. */ - #define AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(__ERROR__) R_UNLESS((r1_resp & DeviceStatus_##__ERROR__) == 0, sdmmc::ResultDeviceStatus##__ERROR__()) - - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(ComCrcError); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(DeviceEccFailed); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CcError); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(Error); - - if (this->GetDeviceType() == DeviceType_Mmc) { - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(SwitchError); - } - - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressMisaligned); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(BlockLenError); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseSeqError); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseParam); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpViolation); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(LockUnlockFailed); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CidCsdOverwrite); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseReset); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(IllegalCommand); - AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressOutOfRange); - - #undef AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR - - return ResultSuccess(); - } - - DeviceState BaseDevice::GetDeviceState(u32 r1_resp) const { - return static_cast((r1_resp & DeviceStatus_CurrentStateMask) >> DeviceStatus_CurrentStateShift); - } - - Result BaseDeviceAccessor::IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(command_index, command_arg, CommandResponseType, is_busy); - R_TRY(this->host_controller->IssueCommand(std::addressof(command))); - - /* Get the response. */ - AMS_ABORT_UNLESS(out_response != nullptr); - this->host_controller->GetLastResponse(out_response, sizeof(u32), CommandResponseType); - - /* Mask out the ignored status bits. */ - if (status_ignore_mask != 0) { - *out_response &= ~status_ignore_mask; - } - - /* Check the r1 response for errors. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - R_TRY(this->base_device->CheckDeviceStatus(*out_response)); - - /* Check the device state. */ - if (expected_state != DeviceState_Unknown) { - R_UNLESS(this->base_device->GetDeviceState(*out_response) == expected_state, sdmmc::ResultUnexpectedDeviceState()); - } - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::IssueCommandGoIdleState() const { - /* Issue the command. */ - Command command(CommandIndex_GoIdleState, 0, ResponseType_R0, false); - return this->host_controller->IssueCommand(std::addressof(command)); - } - - Result BaseDeviceAccessor::IssueCommandAllSendCid(void *dst, size_t dst_size) const { - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R2; - Command command(CommandIndex_AllSendCid, 0, CommandResponseType, false); - R_TRY(this->host_controller->IssueCommand(std::addressof(command))); - - /* Copy the data out. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); - AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); - this->host_controller->GetLastResponse(static_cast(dst), DeviceCidSize, CommandResponseType); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::IssueCommandSelectCard() const { - /* Get the command argument. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - const u32 arg = static_cast(this->base_device->GetRca()) << 16; - - /* Issue the command. */ - return this->IssueCommandAndCheckR1(CommandIndex_SelectCard, arg, true, DeviceState_Unknown); - } - - Result BaseDeviceAccessor::IssueCommandSendCsd(void *dst, size_t dst_size) const { - /* Get the command argument. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - const u32 arg = static_cast(this->base_device->GetRca()) << 16; - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R2; - Command command(CommandIndex_SendCsd, arg, CommandResponseType, false); - R_TRY(this->host_controller->IssueCommand(std::addressof(command))); - - /* Copy the data out. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); - AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); - this->host_controller->GetLastResponse(static_cast(dst), DeviceCsdSize, CommandResponseType); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const { - /* Get the command argument. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - const u32 arg = static_cast(this->base_device->GetRca()) << 16; - - /* Issue the command. */ - return this->IssueCommandAndCheckR1(out_device_status, CommandIndex_SendStatus, arg, false, DeviceState_Tran, status_ignore_mask); - } - - Result BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize() const { - /* Issue the command. */ - return this->IssueCommandAndCheckR1(CommandIndex_SetBlockLen, SectorSize, false, DeviceState_Tran); - } - - Result BaseDeviceAccessor::IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { - /* Get the argument. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - const u32 arg = this->base_device->IsHighCapacity() ? sector_index : sector_index * SectorSize; - - /* Get the command index and transfer direction. */ - const u32 command_index = is_read ? CommandIndex_ReadMultipleBlock : CommandIndex_WriteMultipleBlock; - const auto xfer_direction = is_read ? TransferDirection_ReadFromDevice : TransferDirection_WriteToDevice; - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(command_index, arg, CommandResponseType, false); - TransferData xfer_data(buf, SectorSize, num_sectors, xfer_direction, true, true); - Result result = this->host_controller->IssueCommand(std::addressof(command), std::addressof(xfer_data), out_num_transferred_blocks); - - /* Handle the failure case. */ - if (R_FAILED(result)) { - /* Check if we were removed. */ - R_TRY(this->CheckRemoved()); - - /* By default, we'll want to return the result we just got. */ - Result result_to_return = result; - - /* Stop transmission. */ - u32 resp = 0; - result = this->host_controller->IssueStopTransmissionCommand(std::addressof(resp)); - if (R_SUCCEEDED(result)) { - result = this->base_device->CheckDeviceStatus(resp & (~DeviceStatus_IllegalCommand)); - if (R_FAILED(result)) { - result_to_return = result; - } - } - - /* Check if we were removed. */ - R_TRY(this->CheckRemoved()); - - /* Get the device status. */ - u32 device_status = 0; - result = this->IssueCommandSendStatus(std::addressof(device_status), DeviceStatus_IllegalCommand); - - /* If there's a device status error we don't already have, we prefer to return it. */ - if (!sdmmc::ResultDeviceStatusHasError::Includes(result_to_return) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { - result_to_return = result; - } - - /* Return the result we chose. */ - return result_to_return; - } - - /* Get the responses. */ - u32 resp, st_resp; - this->host_controller->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - this->host_controller->GetLastStopTransmissionResponse(std::addressof(st_resp), sizeof(st_resp)); - - /* Check the device status. */ - R_TRY(this->base_device->CheckDeviceStatus(resp)); - - /* Decide on what errors to ignore. */ - u32 status_ignore_mask = 0; - if (is_read) { - AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); - if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { - status_ignore_mask = DeviceStatus_AddressOutOfRange; - } - } - - /* Check the device status. */ - R_TRY(this->base_device->CheckDeviceStatus(st_resp & ~status_ignore_mask)); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { - /* Issue the read/write command. */ - AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); - R_TRY(this->IssueCommandMultipleBlock(out_num_transferred_blocks, sector_index, num_sectors, buf, is_read)); - - /* Decide on what errors to ignore. */ - u32 status_ignore_mask = 0; - if (is_read) { - AMS_ABORT_UNLESS(this->base_device != nullptr); - if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { - status_ignore_mask = DeviceStatus_AddressOutOfRange; - } - } - - /* Get and check the status. */ - u32 device_status; - R_TRY(this->IssueCommandSendStatus(std::addressof(device_status), status_ignore_mask)); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read) { - /* Verify that we can send the command. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - - /* If we want to read zero sectors, there's no work for us to do. */ - R_SUCCEED_IF(num_sectors == 0); - - /* Check that the buffer is big enough for the sectors we're reading. */ - AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); - - /* Read sectors repeatedly until we've read all the ones we want. */ - u32 cur_sector_index = sector_index; - u32 remaining_sectors = num_sectors; - u8 *cur_buf = static_cast(buf); - while (remaining_sectors > 0) { - /* Determine how many sectors we can read in this iteration. */ - u32 cur_sectors = remaining_sectors; - if (sector_index_alignment > 0) { - AMS_ABORT_UNLESS((cur_sector_index % sector_index_alignment) == 0); - - const u32 max_sectors = this->host_controller->GetMaxTransferNumBlocks(); - if (remaining_sectors > max_sectors) { - cur_sectors = max_sectors - (max_sectors % sector_index_alignment); - } - } - - /* Try to perform the read/write. */ - u32 num_transferred_blocks = 0; - Result result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); - if (R_FAILED(result)) { - /* Check if we were removed. */ - R_TRY(this->CheckRemoved()); - - /* Log that we failed to read/write. */ - this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); - - /* Retry the read/write. */ - num_transferred_blocks = 0; - result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); - if (R_FAILED(result)) { - /* Check if we were removed. */ - R_TRY(this->CheckRemoved()); - - /* Log that we failed to read/write. */ - this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); - - /* Re-startup the connection, to see if that helps. */ - R_TRY(this->ReStartup()); - - /* Retry the read/write a third time. */ - num_transferred_blocks = 0; - result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); - if (R_FAILED(result)) { - /* Log that we failed after a re-startup. */ - this->PushErrorLog(true, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); - return result; - } - - /* Log that we succeeded after a retry. */ - this->PushErrorLog(true, "%s %X %X:0", is_read ? "R" : "W", cur_sector_index, cur_sectors); - - /* Increment the number of error corrections we've done. */ - ++this->num_read_write_error_corrections; - } - } - - /* Update our tracking variables. */ - AMS_ABORT_UNLESS(remaining_sectors >= num_transferred_blocks); - remaining_sectors -= num_transferred_blocks; - cur_sector_index += num_transferred_blocks; - cur_buf += num_transferred_blocks * SectorSize; - } - - return ResultSuccess(); - } - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - void BaseDeviceAccessor::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Register the address. */ - return this->host_controller->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); - } - - void BaseDeviceAccessor::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Register the address. */ - return this->host_controller->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); - } - #endif - - Result BaseDeviceAccessor::Activate() { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is awake. */ - R_UNLESS(this->base_device->IsAwake(), sdmmc::ResultNotAwakened()); - - /* If the device is already active, we don't need to do anything. */ - R_SUCCEED_IF(this->base_device->IsActive()); - - /* Activate the base device. */ - auto activate_guard = SCOPE_GUARD { ++this->num_activation_failures; }; - R_TRY(this->OnActivate()); - - /* We successfully activated the device. */ - activate_guard.Cancel(); - this->base_device->SetActive(); - - return ResultSuccess(); - } - - void BaseDeviceAccessor::Deactivate() { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Deactivate the base device. */ - if (this->base_device->IsActive()) { - this->host_controller->Shutdown(); - this->base_device->Deactivate(); - } - } - - Result BaseDeviceAccessor::ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Perform the read/write. */ - auto rw_guard = SCOPE_GUARD { ++this->num_read_write_failures; }; - R_TRY(this->OnReadWrite(sector_index, num_sectors, buffer, buffer_size, is_read)); - - /* We successfully performed the read/write. */ - rw_guard.Cancel(); - return ResultSuccess(); - } - - Result BaseDeviceAccessor::CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the current speed mode/bus width. */ - *out_speed_mode = this->host_controller->GetSpeedMode(); - *out_bus_width = this->host_controller->GetBusWidth(); - - /* Verify that we can get the status. */ - R_TRY(this->host_controller->GetInternalStatus()); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetMemoryCapacity(u32 *out_sectors) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the capacity. */ - AMS_ABORT_UNLESS(out_sectors != nullptr); - *out_sectors = this->base_device->GetMemoryCapacity(); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetDeviceStatus(u32 *out) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the status. */ - R_TRY(this->IssueCommandSendStatus(out, 0)); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetOcr(u32 *out) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the ocr. */ - AMS_ABORT_UNLESS(out != nullptr); - *out = this->base_device->GetOcr(); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetRca(u16 *out) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the rca. */ - AMS_ABORT_UNLESS(out != nullptr); - *out = this->base_device->GetRca(); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetCid(void *out, size_t size) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the cid. */ - this->base_device->GetCid(out, size); - - return ResultSuccess(); - } - - Result BaseDeviceAccessor::GetCsd(void *out, size_t size) const { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Check that the device is accessible. */ - R_TRY(this->base_device->CheckAccessible()); - - /* Get the csd. */ - this->base_device->GetCsd(out, size); - - return ResultSuccess(); - } - - void BaseDeviceAccessor::GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { - /* Lock exclusive access of the base device. */ - AMS_ABORT_UNLESS(this->base_device != nullptr); - AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); - - /* Set the output error info. */ - AMS_ABORT_UNLESS(out_error_info != nullptr); - out_error_info->num_activation_failures = this->num_activation_failures; - out_error_info->num_activation_error_corrections = this->num_activation_error_corrections; - out_error_info->num_read_write_failures = this->num_read_write_failures; - out_error_info->num_read_write_error_corrections = this->num_read_write_error_corrections; - this->ClearErrorInfo(); - - /* Check if we should write logs. */ - if (out_log_size == nullptr) { - return; - } - - /* Check if we can write logs. */ - if (out_log_buffer == nullptr || log_buffer_size == 0) { - *out_log_size = 0; - return; - } - - /* Get and clear our logs. */ - #if defined(AMS_SDMMC_USE_LOGGER) - { - if (this->error_logger.HasLog()) { - this->PushErrorTimeStamp(); - - *out_log_size = this->error_logger.GetAndClearLogs(out_log_buffer, log_buffer_size); - } else { - *out_log_size = 0; - } - } - #else - { - *out_log_size = 0; - } - #endif - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.hpp deleted file mode 100644 index 8cb0490..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_base_device_accessor.hpp +++ /dev/null @@ -1,512 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" -#include "sdmmc_i_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - #if defined(AMS_SDMMC_USE_LOGGER) - class Logger { - private: - static constexpr size_t LogLengthMax = 0x20; - static constexpr size_t LogCountMax = 0x10; - private: - char logs[LogCountMax][LogLengthMax]; - int log_index; - private: - void Clear() { - for (size_t i = 0; i < LogCountMax; ++i) { - this->logs[i][0] = '\0'; - } - this->log_index = 0; - } - - size_t Pop(char *dst, size_t dst_size) { - /* Decrease log index. */ - if ((--this->log_index) < 0) { - this->log_index = LogCountMax - 1; - } - - /* Check if we have a log. */ - if (this->logs[this->log_index][0] == '\0') { - return 0; - } - - /* Copy log to output. */ - const int len = ::ams::util::Strlcpy(dst, this->logs[this->log_index], dst_size); - - /* Clear the log we copied. */ - this->logs[this->log_index][0] = '\0'; - - return static_cast(len); - } - - public: - Logger() { - this->Clear(); - } - - void Push(const char *fmt, std::va_list vl) { - /* Format the log into the current buffer. */ - ::ams::util::TVSNPrintf(this->logs[this->log_index], LogLengthMax, fmt, vl); - - /* Update our log index. */ - if ((++this->log_index) >= static_cast(LogCountMax)) { - this->log_index = 0; - } - } - - void Push(const char *fmt, ...) { - std::va_list vl; - va_start(vl, fmt); - this->Push(fmt, vl); - va_end(vl); - } - - bool HasLog() const { - const int index = this->log_index > 0 ? this->log_index - 1 : static_cast(LogCountMax - 1); - return this->logs[index][0] != '\0'; - } - - size_t GetAndClearLogs(char *dst, size_t dst_size) { - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size > 0); - - /* Pop logs until we run out of them. */ - size_t total_len = 0; - while (true) { - /* Pop the current log. */ - const size_t cur_len = this->Pop(dst + total_len, dst_size - total_len); - if (cur_len == 0) { - break; - } - - /* Check if the log exceeded the buffer size. */ - if (total_len + cur_len + 1 >= dst_size) { - break; - } - - /* Advance the total length. */ - total_len += cur_len; - - /* Check if there's space for our separator. */ - if (total_len + 3 >= dst_size) { - break; - } - - dst[total_len + 0] = ','; - dst[total_len + 1] = ' '; - total_len += 2; - } - - /* Ensure that the complete log fits in the buffer. */ - if (total_len >= dst_size) { - total_len = dst_size - 1; - } - - /* Ensure null termination. */ - dst[total_len] = '\0'; - - /* Clear any remaining logs. */ - this->Clear(); - - /* Return the length of the logs, including null terminator. */ - return total_len + 1; - } - }; - #endif - - enum DeviceType { - DeviceType_Mmc = 0, - DeviceType_SdCard = 1, - DeviceType_GcAsic = 2, - }; - - enum DeviceState { - DeviceState_Idle = 0, - DeviceState_Ready = 1, - DeviceState_Ident = 2, - DeviceState_Stby = 3, - DeviceState_Tran = 4, - DeviceState_Data = 5, - DeviceState_Rcv = 6, - DeviceState_Prg = 7, - DeviceState_Dis = 8, - DeviceState_Rsvd0 = 9, - DeviceState_Rsvd1 = 10, - DeviceState_Rsvd2 = 11, - DeviceState_Rsvd3 = 12, - DeviceState_Rsvd4 = 13, - DeviceState_Rsvd5 = 14, - DeviceState_RsvdIoMode = 15, - DeviceState_Unknown = 16, - }; - - enum DeviceStatus : u32 { - DeviceStatus_AkeSeqError = (1u << 3), - DeviceStatus_AppCmd = (1u << 5), - DeviceStatus_SwitchError = (1u << 7), - DeviceStatus_EraseReset = (1u << 13), - DeviceStatus_WpEraseSkip = (1u << 15), - DeviceStatus_CidCsdOverwrite = (1u << 16), - DeviceStatus_Error = (1u << 19), - DeviceStatus_CcError = (1u << 20), - DeviceStatus_DeviceEccFailed = (1u << 21), - DeviceStatus_IllegalCommand = (1u << 22), - DeviceStatus_ComCrcError = (1u << 23), - DeviceStatus_LockUnlockFailed = (1u << 24), - DeviceStatus_WpViolation = (1u << 26), - DeviceStatus_EraseParam = (1u << 27), - DeviceStatus_EraseSeqError = (1u << 28), - DeviceStatus_BlockLenError = (1u << 29), - DeviceStatus_AddressMisaligned = (1u << 30), - DeviceStatus_AddressOutOfRange = (1u << 31), - - DeviceStatus_CurrentStateShift = 9, - DeviceStatus_CurrentStateMask = (0b1111u << DeviceStatus_CurrentStateShift), - - DeviceStatus_ErrorMask = (DeviceStatus_SwitchError | - DeviceStatus_EraseReset | - DeviceStatus_WpEraseSkip | - DeviceStatus_CidCsdOverwrite | - DeviceStatus_Error | - DeviceStatus_CcError | - DeviceStatus_DeviceEccFailed | - DeviceStatus_IllegalCommand | - DeviceStatus_ComCrcError | - DeviceStatus_LockUnlockFailed | - DeviceStatus_WpViolation | - DeviceStatus_EraseParam | - DeviceStatus_EraseSeqError | - DeviceStatus_BlockLenError | - DeviceStatus_AddressMisaligned | - DeviceStatus_AddressOutOfRange), - }; - - class BaseDevice { - private: - u32 ocr; - u8 cid[DeviceCidSize]; - u16 csd[DeviceCsdSize / sizeof(u16)]; - u32 memory_capacity; - bool is_high_capacity; - bool is_valid_ocr; - bool is_valid_cid; - bool is_valid_csd; - bool is_valid_high_capacity; - bool is_valid_memory_capacity; - bool is_active; - bool is_awake; - public: - #if defined(AMS_SDMMC_THREAD_SAFE) - mutable os::Mutex device_mutex; - public: - BaseDevice() : device_mutex(true) - #else - BaseDevice() - #endif - { - this->is_awake = true; - this->ocr = 0; - this->memory_capacity = 0; - this->is_high_capacity = false; - this->OnDeactivate(); - } - - void OnDeactivate() { - this->is_active = false; - this->is_valid_ocr = false; - this->is_valid_cid = false; - this->is_valid_csd = false; - this->is_valid_high_capacity = false; - this->is_valid_memory_capacity = false; - } - - bool IsAwake() const { - return this->is_awake; - } - - void Awaken() { - this->is_awake = true; - } - - void PutToSleep() { - this->is_awake = false; - } - - bool IsActive() const { - return this->is_active; - } - - void SetActive() { - this->is_active = true; - } - - virtual void Deactivate() { - this->OnDeactivate(); - } - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual os::EventType *GetRemovedEvent() const = 0; - #endif - - virtual DeviceType GetDeviceType() const = 0; - virtual u16 GetRca() const = 0; - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - void InitializeRemovedEvent() { - if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { - os::InitializeEvent(removed_event, false, os::EventClearMode_ManualClear); - } - } - - void FinalizeRemovedEvent() { - if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { - os::FinalizeEvent(removed_event); - } - } - - void SignalRemovedEvent() { - if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { - os::SignalEvent(removed_event); - } - } - - void ClearRemovedEvent() { - if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { - os::ClearEvent(removed_event); - } - } - - bool IsRemoved() const { - if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { - return os::TryWaitEvent(removed_event); - } - return false; - } - #endif - - Result CheckRemoved() const { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); - #endif - - return ResultSuccess(); - } - - Result CheckAccessible() const { - /* Check awake. */ - R_UNLESS(this->IsAwake(), sdmmc::ResultNotAwakened()); - - /* Check active. */ - R_UNLESS(this->IsActive(), sdmmc::ResultNotActivated()); - - /* Check removed. */ - R_TRY(this->CheckRemoved()); - - return ResultSuccess(); - } - - void SetHighCapacity(bool en) { - this->is_high_capacity = en; - this->is_valid_high_capacity = true; - } - - bool IsHighCapacity() const { - AMS_ABORT_UNLESS(this->is_valid_high_capacity); - return this->is_high_capacity; - } - - void SetOcr(u32 o) { - this->ocr = o; - this->is_valid_ocr = true; - } - - u32 GetOcr() const { - AMS_ABORT_UNLESS(this->is_valid_ocr); - return this->ocr; - } - - void SetCid(const void *src, size_t src_size) { - AMS_ABORT_UNLESS(src != nullptr); - AMS_ABORT_UNLESS(src_size >= DeviceCidSize); - std::memcpy(this->cid, src, DeviceCidSize); - this->is_valid_cid = true; - } - - void GetCid(void *dst, size_t dst_size) const { - AMS_ABORT_UNLESS(this->is_valid_cid); - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); - std::memcpy(dst, this->cid, DeviceCidSize); - } - - void SetCsd(const void *src, size_t src_size) { - AMS_ABORT_UNLESS(src != nullptr); - AMS_ABORT_UNLESS(src_size >= DeviceCsdSize); - std::memcpy(this->csd, src, DeviceCsdSize); - this->is_valid_csd = true; - } - - void GetCsd(void *dst, size_t dst_size) const { - AMS_ABORT_UNLESS(this->is_valid_csd); - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); - std::memcpy(dst, this->csd, DeviceCsdSize); - } - - void SetMemoryCapacity(u32 num_sectors) { - this->memory_capacity = num_sectors; - this->is_valid_memory_capacity = true; - } - - u32 GetMemoryCapacity() const { - AMS_ABORT_UNLESS(this->is_valid_memory_capacity); - return this->memory_capacity; - } - - void GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const; - Result SetLegacyMemoryCapacity(); - - Result CheckDeviceStatus(u32 r1_resp) const; - DeviceState GetDeviceState(u32 r1_resp) const; - }; - - class BaseDeviceAccessor : public IDeviceAccessor { - private: - IHostController *host_controller; - BaseDevice *base_device; - u32 num_activation_failures; - u32 num_activation_error_corrections; - u32 num_read_write_failures; - u32 num_read_write_error_corrections; - #if defined(AMS_SDMMC_USE_LOGGER) - Logger error_logger; - #endif - private: - void ClearErrorInfo() { - this->num_activation_failures = 0; - this->num_activation_error_corrections = 0; - this->num_read_write_failures = 0; - this->num_read_write_error_corrections = 0; - } - protected: - explicit BaseDeviceAccessor(IHostController *hc) : host_controller(hc), base_device(nullptr) { - this->ClearErrorInfo(); - } - - IHostController *GetHostController() const { - return this->host_controller; - } - - void SetDevice(BaseDevice *bd) { - this->base_device = bd; - } - - Result CheckRemoved() const { - return this->base_device->CheckRemoved(); - } - - Result IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const; - - Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { - u32 dummy; - return this->IssueCommandAndCheckR1(std::addressof(dummy), command_index, command_arg, is_busy, expected_state, status_ignore_mask); - } - - Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state) const { - return this->IssueCommandAndCheckR1(command_index, command_arg, is_busy, expected_state, 0); - } - - Result IssueCommandGoIdleState() const; - Result IssueCommandAllSendCid(void *dst, size_t dst_size) const; - Result IssueCommandSelectCard() const; - Result IssueCommandSendCsd(void *dst, size_t dst_size) const; - Result IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const; - - Result IssueCommandSendStatus(u32 status_ignore_mask) const { - u32 dummy; - return this->IssueCommandSendStatus(std::addressof(dummy), status_ignore_mask); - } - - Result IssueCommandSendStatus() const { - return this->IssueCommandSendStatus(0); - } - - Result IssueCommandSetBlockLenToSectorSize() const; - Result IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; - Result ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; - Result ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read); - - void IncrementNumActivationErrorCorrections() { - ++this->num_activation_error_corrections; - } - - void PushErrorTimeStamp() { - #if defined(AMS_SDMMC_USE_LOGGER) - { - this->error_logger.Push("%u", static_cast(os::ConvertToTimeSpan(os::GetSystemTick()).GetSeconds())); - } - #endif - } - - void PushErrorLog(bool with_timestamp, const char *fmt, ...) { - #if defined(AMS_SDMMC_USE_LOGGER) - { - std::va_list vl; - va_start(vl, fmt); - this->error_logger.Push(fmt, vl); - va_end(vl); - - if (with_timestamp) { - this->PushErrorTimeStamp(); - } - } - #else - { - AMS_UNUSED(with_timestamp, fmt); - } - #endif - } - - virtual Result OnActivate() = 0; - virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) = 0; - virtual Result ReStartup() = 0; - public: - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; - virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; - #endif - - virtual Result Activate() override; - virtual void Deactivate() override; - - virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) override; - virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) override; - - virtual Result GetMemoryCapacity(u32 *out_sectors) const override; - virtual Result GetDeviceStatus(u32 *out) const override; - virtual Result GetOcr(u32 *out) const override; - virtual Result GetRca(u16 *out) const override; - virtual Result GetCid(void *out, size_t size) const override; - virtual Result GetCsd(void *out, size_t size) const override; - - virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) override; - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp deleted file mode 100644 index 1790380..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_clock_reset_controller.hpp" -#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" -#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" - -namespace ams::sdmmc::impl::ClockResetController { - - namespace { - - constinit bool g_is_module_initialized[Module_Count] = {}; - - #if defined(AMS_SDMMC_THREAD_SAFE) - - constinit os::SdkMutex g_module_mutex; - - #define AMS_SDMMC_LOCK_MODULE_MUTEX() std::scoped_lock lk(g_module_mutex) - - #else - - #define AMS_SDMMC_LOCK_MODULE_MUTEX() - - #endif - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - constinit bool g_is_pcv_control = false; - - #define AMS_SDMMC_IF_IS_PCV_CONTROL() if (g_is_pcv_control) - - #else - - #define AMS_SDMMC_IF_IS_PCV_CONTROL() if constexpr (false) - - #endif - - } - - void Initialize(Module module) { - /* Acquire exclusive access to module state. */ - AMS_SDMMC_LOCK_MODULE_MUTEX(); - - /* Mark the module as initialized. */ - g_is_module_initialized[module] = true; - - /* Initialize the module. */ - AMS_SDMMC_IF_IS_PCV_CONTROL() { - ClockResetController::pcv::Initialize(module); - } else { - ClockResetController::reg::Initialize(module); - } - } - - void Finalize(Module module) { - /* Acquire exclusive access to module state. */ - AMS_SDMMC_LOCK_MODULE_MUTEX(); - - /* Finalize the module. */ - AMS_SDMMC_IF_IS_PCV_CONTROL() { - ClockResetController::pcv::Finalize(module); - } else { - ClockResetController::reg::Finalize(module); - } - - /* Mark the module as finalized. */ - g_is_module_initialized[module] = false; - } - - bool IsAvailable(Module module) { - AMS_SDMMC_IF_IS_PCV_CONTROL() { - return ClockResetController::pcv::IsAvailable(module); - } else { - return ClockResetController::reg::IsAvailable(module); - } - } - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - void SwitchToPcvControl() { - /* Acquire exclusive access to module state. */ - AMS_SDMMC_LOCK_MODULE_MUTEX(); - - /* If we're already using pcv control, we don't need to do anything. */ - AMS_SDMMC_IF_IS_PCV_CONTROL() { - return; - } - - /* Finalize all modules. */ - for (int i = 0; i < Module_Count; ++i) { - if (g_is_module_initialized[i]) { - ClockResetController::reg::Finalize(static_cast(i)); - } - } - - /* Mark that we've switched to pcv control. */ - - /* Initialize modules using pcv control. */ - for (int i = 0; i < Module_Count; ++i) { - if (g_is_module_initialized[i]) { - ClockResetController::pcv::Initialize(static_cast(i)); - } - } - } - #endif - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { - AMS_SDMMC_IF_IS_PCV_CONTROL() { - return ClockResetController::pcv::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); - } else { - return ClockResetController::reg::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); - } - } - - void AssertReset(Module module) { - AMS_SDMMC_IF_IS_PCV_CONTROL() { - return ClockResetController::pcv::AssertReset(module); - } else { - return ClockResetController::reg::AssertReset(module); - } - } - - void ReleaseReset(Module module, u32 target_frequency_khz) { - AMS_SDMMC_IF_IS_PCV_CONTROL() { - return ClockResetController::pcv::ReleaseReset(module, target_frequency_khz); - } else { - return ClockResetController::reg::ReleaseReset(module, target_frequency_khz); - } - } - - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.hpp deleted file mode 100644 index 29ac276..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.hpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -namespace ams::sdmmc::impl::ClockResetController { - - enum Module { - #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) - Module_Sdmmc1 = 0, - Module_Sdmmc2 = 1, - Module_Sdmmc3 = 2, - Module_Sdmmc4 = 3, - #endif - - Module_Count, - }; - - void Initialize(Module module); - void Finalize(Module module); - bool IsAvailable(Module module); - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - void SwitchToPcvControl(); - #endif - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); - void AssertReset(Module module); - void ReleaseReset(Module module, u32 target_frequency_khz); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp deleted file mode 100644 index 8d8c39d..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" - -namespace ams::sdmmc::impl::ClockResetController::pcv { - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - - void Initialize(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not implemented"); - } - - void Finalize(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not implemented"); - } - - bool IsAvailable(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not implemented"); - } - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { - AMS_UNUSED(out_actual_frequency, module, target_frequency); - AMS_ABORT("PCV Control not implemented"); - } - - void AssertReset(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not implemented"); - } - - void ReleaseReset(Module module, u32 target_frequency_khz) { - AMS_UNUSED(module, target_frequency_khz); - AMS_ABORT("PCV Control not implemented"); - } - - #else - - void Initialize(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not supported"); - } - - void Finalize(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not supported"); - } - - bool IsAvailable(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not supported"); - } - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { - AMS_UNUSED(out_actual_frequency, module, target_frequency); - AMS_ABORT("PCV Control not supported"); - } - - void AssertReset(Module module) { - AMS_UNUSED(module); - AMS_ABORT("PCV Control not supported"); - } - - void ReleaseReset(Module module, u32 target_frequency_khz) { - AMS_UNUSED(module, target_frequency_khz); - AMS_ABORT("PCV Control not supported"); - } - - #endif - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp deleted file mode 100644 index 85144bf..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_clock_reset_controller.hpp" - -namespace ams::sdmmc::impl::ClockResetController::pcv { - - void Initialize(Module module); - void Finalize(Module module); - bool IsAvailable(Module module); - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); - void AssertReset(Module module); - void ReleaseReset(Module module, u32 target_frequency_khz); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp deleted file mode 100644 index 3a49428..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl::ClockResetController::reg { - - namespace { - - #if defined(AMS_SDMMC_THREAD_SAFE) - - constinit os::SdkMutex g_init_mutex; - - #define AMS_SDMMC_LOCK_INIT_MUTEX() std::scoped_lock lk(g_init_mutex) - - #else - - #define AMS_SDMMC_LOCK_INIT_MUTEX() - - #endif - - constinit bool g_is_initialized = false; - - struct ModuleInfo { - u32 target_frequency_khz; - u32 actual_frequency_khz; - }; - - constinit ModuleInfo g_module_infos[Module_Count] = {}; - - constexpr inline dd::PhysicalAddress ClockResetControllerRegistersPhysicalAddress = UINT64_C(0x60006000); - constexpr inline size_t ClockResetControllerRegistersSize = 4_KB; - - constinit uintptr_t g_clkrst_registers_address = 0; - - [[maybe_unused]] void InitializePllc4() { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Check if PLLC4_BASE has the expected value; if it does, we have nothing to do. */ - constexpr u32 ExpectedPllc4Base = 0x58006804; - if (ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE) == ExpectedPllc4Base) { - return; - } - - /* Disable PLLC4_ENABLE, if it's currently set. */ - if (ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE))) { - ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, DISABLE)); - } - - /* Operate on the register with PLLC4_ENABLE cleared. */ - { - /* Clear IDDQ, read to be sure it takes, wait 5 us. */ - ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_IDDQ, OFF)); - ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); - WaitMicroSeconds(5); - - /* Write the expected value sans IDDQ/PLLC4_ENABLE. */ - constexpr u32 ExpectedPllc4BaseMask = ~ams::reg::EncodeMask(CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_ENABLE), - CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_IDDQ)); - ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, ExpectedPllc4Base & ExpectedPllc4BaseMask); - } - - /* Write PLLC4_ENABLE, and read to be sure our configuration takes. */ - ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE)); - ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); - - /* Wait up to 1s for changes to take. */ - { - ManualTimer timer(1000); - while (true) { - /* Check if we're done. */ - if (!ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_LOCK, NOT_LOCK))) { - break; - } - - /* Check that we haven't timed out. */ - AMS_ABORT_UNLESS(timer.Update()); - } - } - } - - void InitializeLegacyTmClk() { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Configure the legacy tm clock as 12MHz. */ - ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_LEGACY_TM_CLK_SRC, PLLP_OUT0), - CLK_RST_REG_BITS_VALUE(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 66)); - - /* Enable clock to the legacy tm. */ - ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_LEGACY_TM, ENABLE)); - } - - bool IsResetReleased(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Get the reset bit from RST_DEVICES_* */ - switch (module) { - case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC1_RST, DISABLE)); - case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC2_RST, DISABLE)); - case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_REG_BITS_ENUM(RST_DEVICES_U_SWR_SDMMC3_RST, DISABLE)); - case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC4_RST, DISABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void SetReset(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Set reset in RST_DEV_*_SET */ - switch (module) { - case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); - case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); - case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); - case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void ClearReset(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Set reset in RST_DEV_*_CLR */ - switch (module) { - case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); - case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); - case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); - case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - bool IsClockEnabled(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Get the enable bit from CLK_OUT_ENB_* */ - switch (module) { - case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC1, ENABLE)); - case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC2, ENABLE)); - case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_SDMMC3, ENABLE)); - case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC4, ENABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void SetClockEnable(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Set clock enable bit in CLK_ENB_*_SET */ - switch (module) { - case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); - case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); - case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); - case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void ClearClockEnable(Module module) { - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Set clock enable bit in CLK_ENB_*_CLR */ - switch (module) { - case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); - case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); - case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); - case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void SetClockSourceSdmmc(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { - /* Check that we can write to output. */ - AMS_ABORT_UNLESS(out_actual_frequency_khz != nullptr); - - /* Determine frequency/divisor. */ - u32 clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLP_OUT0)); - u8 n; - switch (target_frequency_khz) { - case 25'000: - *out_actual_frequency_khz = 24'728; - n = 31; - break; - case 26'000: - *out_actual_frequency_khz = 25'500; - n = 30; - break; - case 40'800: - *out_actual_frequency_khz = 40'800; - n = 18; - break; - case 50'000: - *out_actual_frequency_khz = 48'000; - n = 15; - break; - case 52'000: - *out_actual_frequency_khz = 51'000; - n = 14; - break; - case 100'000: - #if defined(AMS_SDMMC_SET_PLLC4_BASE) - *out_actual_frequency_khz = 99'840; - n = 2; - clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); - #else - *out_actual_frequency_khz = 90'667; - n = 7; - #endif - break; - case 200'000: - #if defined(AMS_SDMMC_SET_PLLC4_BASE) - *out_actual_frequency_khz = 199'680; - n = 0; - if (module == Module_Sdmmc2 || module == Module_Sdmmc4) { - clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, PLLC4_OUT2_LJ)); - } else { - clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); - } - #else - *out_actual_frequency_khz = 163'200; - n = 3; - #endif - break; - case 208'000: - *out_actual_frequency_khz = 204'000; - n = 2; - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Set frequencies in module info. */ - g_module_infos[module].target_frequency_khz = target_frequency_khz; - g_module_infos[module].actual_frequency_khz = *out_actual_frequency_khz; - - /* Check that we have registers we can write to. */ - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Update the clock source. */ - switch (module) { - case Module_Sdmmc1: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1, clk_m | static_cast(n)); break; - case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast(n)); break; - case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast(n)); break; - case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast(n)); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void EnsureControl(Module module) { - /* Read from RST_DEVICES_* to be sure previous configuration takes. */ - switch (module) { - case Module_Sdmmc1: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; - case Module_Sdmmc2: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; - case Module_Sdmmc3: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U); break; - case Module_Sdmmc4: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - } - - void Initialize(Module module) { - /* Initialization isn't module specific. */ - AMS_UNUSED(module); - - /* Acquire exclusive access to the initialization lock. */ - AMS_SDMMC_LOCK_INIT_MUTEX(); - - /* If we've already initialized, we don't need to do anything. */ - if (g_is_initialized) { - return; - } - - /* Clear module infos. */ - std::memset(g_module_infos, 0, sizeof(g_module_infos)); - - /* Get the registers address. */ - g_clkrst_registers_address = dd::QueryIoMapping(ClockResetControllerRegistersPhysicalAddress, ClockResetControllerRegistersSize); - AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); - - /* Perform register initialization. */ - #if defined(AMS_SDMMC_SET_PLLC4_BASE) - InitializePllc4(); - #endif - InitializeLegacyTmClk(); - - /* Mark that we've initialized. */ - g_is_initialized = true; - } - - void Finalize(Module module) { - /* Nothing is needed for finalization. */ - AMS_UNUSED(module); - } - - bool IsAvailable(Module module) { - return IsResetReleased(module) && IsClockEnabled(module); - } - - void SetClockFrequencyKHz(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { - /* If we're not changing the clock frequency, we don't need to do anything. */ - if (target_frequency_khz == g_module_infos[module].target_frequency_khz) { - *out_actual_frequency_khz = g_module_infos[module].actual_frequency_khz; - return; - } - - /* Temporarily disable clock. */ - const bool clock_enabled = IsClockEnabled(module); - if (clock_enabled) { - ClearClockEnable(module); - } - - /* Set the clock source. */ - SetClockSourceSdmmc(out_actual_frequency_khz, module, target_frequency_khz); - - /* Re-enable clock, if we should. */ - if (clock_enabled) { - SetClockEnable(module); - } - - /* Ensure that our configuration takes. */ - EnsureControl(module); - } - - void AssertReset(Module module) { - /* Set reset and disable clock. */ - SetReset(module); - ClearClockEnable(module); - - /* Ensure that our configuration takes. */ - EnsureControl(module); - } - - void ReleaseReset(Module module, u32 target_frequency_khz) { - /* Disable clock if it's enabled. */ - if (IsClockEnabled(module)) { - ClearClockEnable(module); - } - - /* Set reset. */ - SetReset(module); - - /* Set the clock source. */ - u32 actual_source_frequency_khz; - SetClockSourceSdmmc(std::addressof(actual_source_frequency_khz), module, target_frequency_khz); - - /* Enable clock. */ - SetClockEnable(module); - - /* Ensure that our configuration takes. */ - EnsureControl(module); - - /* Wait 100 clocks. */ - WaitClocks(100, actual_source_frequency_khz); - - /* Clear reset. */ - ClearReset(module); - - /* Ensure that our configuration takes. */ - EnsureControl(module); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp deleted file mode 100644 index 4043037..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_clock_reset_controller.hpp" - -namespace ams::sdmmc::impl::ClockResetController::reg { - - void Initialize(Module module); - void Finalize(Module module); - bool IsAvailable(Module module); - - void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); - void AssertReset(Module module); - void ReleaseReset(Module module, u32 target_frequency_khz); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.cpp deleted file mode 100644 index 3529b85..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) -#include "sdmmc_device_detector.hpp" - -namespace ams::sdmmc::impl { - - bool DeviceDetector::IsCurrentInserted() { - return gpio::GetValue(std::addressof(this->gpio_pad_session)) == this->inserted_gpio_value; - } - - void DeviceDetector::HandleDeviceStatus(bool prev_inserted, bool cur_inserted) { - if (!prev_inserted && !cur_inserted) { - /* Not inserted -> Not inserted, nothing to do. */ - } else if (!prev_inserted && cur_inserted) { - /* Card was inserted. */ - if (this->callback_info.inserted_callback != nullptr) { - this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); - } - } else if (prev_inserted && !cur_inserted) { - /* Card was removed. */ - if (this->callback_info.removed_callback != nullptr) { - this->callback_info.removed_callback(this->callback_info.removed_callback_arg); - } - } else /* if (prev_inserted && cur_inserted) */ { - /* Card was removed, and then inserted. */ - if (this->callback_info.removed_callback != nullptr) { - this->callback_info.removed_callback(this->callback_info.removed_callback_arg); - } - - if (this->callback_info.inserted_callback != nullptr) { - this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); - } - } - } - - void DeviceDetector::DetectorThread() { - /* Initialize the gpio session. */ - sm::DoWithSession([] { gpio::Initialize(); }); - gpio::OpenSession(std::addressof(this->gpio_pad_session), this->gpio_device_code); - gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Input); - gpio::SetDebounceTime(std::addressof(this->gpio_pad_session), this->gpio_debounce_ms); - gpio::SetDebounceEnabled(std::addressof(this->gpio_pad_session), true); - gpio::SetInterruptMode(std::addressof(this->gpio_pad_session), gpio::InterruptMode_AnyEdge); - - /* Get the gpio session's interrupt event. */ - os::SystemEventType gpio_event; - R_ABORT_UNLESS(gpio::BindInterrupt(std::addressof(gpio_event), std::addressof(this->gpio_pad_session))); - - /* Initialize and link waitable holders. */ - os::WaitableManagerType wait_manager; - os::WaitableHolderType detector_thread_end_holder; - os::WaitableHolderType request_sleep_wake_event_holder; - os::WaitableHolderType gpio_event_holder; - os::InitializeWaitableManager(std::addressof(wait_manager)); - os::InitializeWaitableHolder(std::addressof(detector_thread_end_holder), std::addressof(this->detector_thread_end_event)); - os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(detector_thread_end_holder)); - os::InitializeWaitableHolder(std::addressof(request_sleep_wake_event_holder), std::addressof(this->request_sleep_wake_event)); - os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(request_sleep_wake_event_holder)); - os::InitializeWaitableHolder(std::addressof(gpio_event_holder), std::addressof(gpio_event)); - os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); - - /* Wait before detecting the initial state of the card. */ - os::SleepThread(TimeSpan::FromMilliSeconds(this->gpio_debounce_ms)); - bool cur_inserted = this->IsCurrentInserted(); - this->is_prev_inserted = cur_inserted; - - /* Set state as awake. */ - this->state = State_Awake; - os::SignalEvent(std::addressof(this->ready_device_status_event)); - - /* Enable interrupts to be informed of device status. */ - gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); - - /* Wait, servicing our events. */ - while (true) { - /* Get the signaled holder. */ - os::WaitableHolderType *signaled_holder = os::WaitAny(std::addressof(wait_manager)); - - /* Process the holder. */ - bool insert_change = false; - if (signaled_holder == std::addressof(detector_thread_end_holder)) { - /* We should kill ourselves. */ - os::ClearEvent(std::addressof(this->detector_thread_end_event)); - this->state = State_Finalized; - break; - } else if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) { - /* A request for us to sleep/wake has come in, so we'll acknowledge it. */ - os::ClearEvent(std::addressof(this->request_sleep_wake_event)); - this->state = State_Sleep; - os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); - - /* Temporarily unlink our interrupt event. */ - os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); - - /* Wait to be signaled. */ - signaled_holder = os::WaitAny(std::addressof(wait_manager)); - - /* Link our interrupt event back in. */ - os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); - - /* We're awake again. Either because we should exit, or because we were asked to wake up. */ - os::ClearEvent(std::addressof(this->request_sleep_wake_event)); - this->state = State_Awake; - os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); - - /* If we were asked to exit, do so. */ - if (signaled_holder == std::addressof(detector_thread_end_holder)) { - /* We should kill ourselves. */ - os::ClearEvent(std::addressof(this->detector_thread_end_event)); - this->state = State_Finalized; - break; - } else /* if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) */ { - if ((this->force_detection) || - (({ bool active; R_SUCCEEDED(gpio::IsWakeEventActive(std::addressof(active), this->gpio_device_code)) && active; })) || - (os::TryWaitSystemEvent(std::addressof(gpio_event))) || - (this->is_prev_inserted != this->IsCurrentInserted())) - { - insert_change = true; - } - } - } else /* if (signaled_holder == std::addressof(gpio_event_holder)) */ { - /* An event was detected. */ - insert_change = true; - } - - /* Handle an insert change, if one occurred. */ - if (insert_change) { - /* Call the relevant callback, if we have one. */ - if (this->device_detection_event_callback != nullptr) { - this->device_detection_event_callback(this->device_detection_event_callback_arg); - } - - /* Clear the interrupt event. */ - os::ClearSystemEvent(std::addressof(gpio_event)); - gpio::ClearInterruptStatus(std::addressof(this->gpio_pad_session)); - gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); - - /* Update insertion status. */ - cur_inserted = this->IsCurrentInserted(); - this->HandleDeviceStatus(this->is_prev_inserted, cur_inserted); - this->is_prev_inserted = cur_inserted; - } - } - - /* Disable interrupts to our gpio event. */ - gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), false); - - /* Finalize and unlink waitable holders. */ - os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); - os::FinalizeWaitableHolder(std::addressof(gpio_event_holder)); - os::UnlinkWaitableHolder(std::addressof(request_sleep_wake_event_holder)); - os::FinalizeWaitableHolder(std::addressof(request_sleep_wake_event_holder)); - os::UnlinkWaitableHolder(std::addressof(detector_thread_end_holder)); - os::FinalizeWaitableHolder(std::addressof(detector_thread_end_holder)); - os::FinalizeWaitableManager(std::addressof(wait_manager)); - - /* Finalize the gpio session. */ - gpio::UnbindInterrupt(std::addressof(this->gpio_pad_session)); - gpio::CloseSession(std::addressof(this->gpio_pad_session)); - gpio::Finalize(); - } - - void DeviceDetector::Initialize(CallbackInfo *ci) { - /* Transition our state from finalized to initializing. */ - AMS_ABORT_UNLESS(this->state == State_Finalized); - this->state = State_Initializing; - - /* Set our callback infos. */ - this->callback_info = *ci; - - /* Initialize our events. */ - os::InitializeEvent(std::addressof(this->ready_device_status_event), false, os::EventClearMode_ManualClear); - os::InitializeEvent(std::addressof(this->request_sleep_wake_event), false, os::EventClearMode_ManualClear); - os::InitializeEvent(std::addressof(this->acknowledge_sleep_awake_event), false, os::EventClearMode_ManualClear); - os::InitializeEvent(std::addressof(this->detector_thread_end_event), false, os::EventClearMode_ManualClear); - - /* Create and start the detector thread. */ - os::CreateThread(std::addressof(this->detector_thread), DetectorThreadEntry, this, this->detector_thread_stack, sizeof(this->detector_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(sdmmc, DeviceDetector)); - os::SetThreadNamePointer(std::addressof(this->detector_thread), AMS_GET_SYSTEM_THREAD_NAME(sdmmc, DeviceDetector)); - os::StartThread(std::addressof(this->detector_thread)); - } - - void DeviceDetector::Finalize() { - /* Ensure we're not already finalized. */ - AMS_ABORT_UNLESS(this->state != State_Finalized); - - /* Signal event to end the detector thread. */ - os::SignalEvent(std::addressof(this->detector_thread_end_event)); - os::WaitThread(std::addressof(this->detector_thread)); - - /* Finalize thread and events. */ - os::DestroyThread(std::addressof(this->detector_thread)); - os::FinalizeEvent(std::addressof(this->ready_device_status_event)); - os::FinalizeEvent(std::addressof(this->request_sleep_wake_event)); - os::FinalizeEvent(std::addressof(this->acknowledge_sleep_awake_event)); - os::FinalizeEvent(std::addressof(this->detector_thread_end_event)); - } - - void DeviceDetector::PutToSleep() { - /* Signal request, wait for acknowledgement. */ - os::SignalEvent(std::addressof(this->request_sleep_wake_event)); - os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); - os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); - } - - void DeviceDetector::Awaken(bool force_det) { - /* Signal request, wait for acknowledgement. */ - this->force_detection = force_det; - os::SignalEvent(std::addressof(this->request_sleep_wake_event)); - os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); - os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); - } - - bool DeviceDetector::IsInserted() { - bool inserted = false; - - switch (this->state) { - case State_Initializing: - /* Wait for us to know whether the device is inserted. */ - os::WaitEvent(std::addressof(this->ready_device_status_event)); - [[fallthrough]]; - case State_Awake: - /* Get whether the device is currently inserted. */ - inserted = this->IsCurrentInserted(); - break; - case State_Sleep: - case State_Finalized: - /* Get whether the device was inserted when we last knew. */ - inserted = this->is_prev_inserted; - break; - } - - return inserted; - } - - void DeviceDetector::RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { - this->device_detection_event_callback_arg = arg; - this->device_detection_event_callback = cb; - } - - void DeviceDetector::UnregisterDetectionEventCallback() { - this->device_detection_event_callback = nullptr; - } - -} - -#endif diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.hpp deleted file mode 100644 index a61f050..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_device_detector.hpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 -#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) -#include - -namespace ams::sdmmc::impl { - - using InsertedCallback = void (*)(void *); - using RemovedCallback = void (*)(void *); - - struct CallbackInfo { - InsertedCallback inserted_callback; - void *inserted_callback_arg; - RemovedCallback removed_callback; - void *removed_callback_arg; - }; - - class DeviceDetector { - private: - enum State { - State_Initializing = 0, - State_Awake = 1, - State_Sleep = 2, - State_Finalized = 3, - }; - private: - alignas(os::ThreadStackAlignment) u8 detector_thread_stack[8_KB]; - State state; - bool is_prev_inserted; - bool force_detection; - os::ThreadType detector_thread; - os::EventType detector_thread_end_event; - os::EventType request_sleep_wake_event; - os::EventType acknowledge_sleep_awake_event; - os::EventType ready_device_status_event; - - DeviceCode gpio_device_code; - gpio::GpioValue inserted_gpio_value; - u32 gpio_debounce_ms; - gpio::GpioPadSession gpio_pad_session; - - CallbackInfo callback_info; - - DeviceDetectionEventCallback device_detection_event_callback; - void *device_detection_event_callback_arg; - private: - static void DetectorThreadEntry(void *arg) { - reinterpret_cast(arg)->DetectorThread(); - } - - void DetectorThread(); - bool IsCurrentInserted(); - void HandleDeviceStatus(bool prev_inserted, bool cur_inserted); - public: - explicit DeviceDetector(DeviceCode dc, gpio::GpioValue igv, u32 gd) - : gpio_device_code(dc), inserted_gpio_value(igv), gpio_debounce_ms(gd) - { - this->state = State_Finalized; - this->is_prev_inserted = false; - this->force_detection = false; - this->callback_info = {}; - this->device_detection_event_callback = nullptr; - this->device_detection_event_callback_arg = nullptr; - } - - void Initialize(CallbackInfo *ci); - void Finalize(); - - void PutToSleep(); - void Awaken(bool force_det); - - u32 GetDebounceMilliSeconds() const { - return this->gpio_debounce_ms; - } - - bool IsInserted(); - - void RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg); - void UnregisterDetectionEventCallback(); - }; - -} - -#endif \ No newline at end of file diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.cpp deleted file mode 100644 index a1ebe02..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.cpp +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_gc_asic_device_accessor.hpp" -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl { - - #if defined(AMS_SDMMC_THREAD_SAFE) - - #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() std::scoped_lock lk(this->gc_asic_device.device_mutex) - - #else - - #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() - - #endif - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - - #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() R_UNLESS(!this->gc_asic_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) - - #else - - #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() - - #endif - - Result GcAsicDeviceAccessor::IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const { - /* Validate the operation buffer. */ - AMS_ABORT_UNLESS(op_buf != nullptr); - AMS_ABORT_UNLESS(op_buf_size >= GcAsicOperationSize); - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(CommandIndex_GcAsicWriteOperation, 0, CommandResponseType, false); - TransferData xfer_data(const_cast(op_buf), GcAsicOperationSize, 1, TransferDirection_WriteToDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - Result result = hc->IssueCommand(std::addressof(command), std::addressof(xfer_data)); - if (R_FAILED(result)) { - /* We failed to write operation. Check if we were removed. */ - AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); - - /* Determine what result we should return. */ - Result return_result = result; - { - /* Issue a stop transmission command. */ - u32 resp = 0; - result = hc->IssueStopTransmissionCommand(std::addressof(resp)); - if (R_SUCCEEDED(result)) { - /* If we successfully stopped transmission but have an error status, we prefer to return that. */ - result = this->gc_asic_device.CheckDeviceStatus(resp); - if (R_FAILED(result)) { - return_result = result; - } - } - - /* Check again if we were removed. */ - AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); - - /* Request device status. */ - u32 device_status; - result = BaseDeviceAccessor::IssueCommandSendStatus(std::addressof(device_status), 0); - - /* If we got a device status error here and we didn't previously, we prefer to return that. */ - if (!sdmmc::ResultDeviceStatusHasError::Includes(return_result) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { - return_result = result; - } - } - return return_result; - } - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::IssueCommandFinishOperation() const { - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicFinishOperation, 0, true, DeviceState_Tran)); - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::IssueCommandSleep() { - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicSleep, 0, true, DeviceState_Tran)); - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::IssueCommandUpdateKey() const { - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicUpdateKey, 0, true, DeviceState_Tran)); - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::StartupGcAsicDevice() { - /* Start up the host controller. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->Startup(BusPower_1_8V, BusWidth_8Bit, SpeedMode_GcAsicSpeed, false)); - - /* Wait 10 clocks for configuration to take. */ - WaitClocks(10, hc->GetDeviceClockFrequencyKHz()); - - /* Perform tuning with command index 21. */ - AMS_ABORT_UNLESS(hc->IsSupportedTuning()); - R_TRY(hc->Tuning(SpeedMode_GcAsicSpeed, 21)); - - /* Set the device as low capacity/no memory. */ - this->gc_asic_device.SetHighCapacity(false); - this->gc_asic_device.SetMemoryCapacity(0); - - /* Enable power saving. */ - hc->SetPowerSaving(true); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::OnActivate() { - /* If we fail to start up the device, ensure the host controller is shut down. */ - auto power_guard = SCOPE_GUARD { BaseDeviceAccessor::GetHostController()->Shutdown(); }; - - /* Try to start up the device. */ - R_TRY(this->StartupGcAsicDevice()); - - /* We started up, so we don't need to power down. */ - power_guard.Cancel(); - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { - /* Check that we're not performing zero-byte rw. */ - AMS_ABORT_UNLESS(num_sectors > 0); - - /* Check that the buffer is big enough for the rw. */ - AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); - - /* Perform the read/write. */ - u32 num_transferred_blocks; - R_TRY(BaseDeviceAccessor::ReadWriteSingle(std::addressof(num_transferred_blocks), sector_index, num_sectors, buf, is_read)); - - /* Require that we read/wrote as many sectors as we expected. */ - AMS_ABORT_UNLESS(num_transferred_blocks == num_sectors); - - return ResultSuccess(); - } - - void GcAsicDeviceAccessor::Initialize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* If we've already initialized, we don't need to do anything. */ - if (this->is_initialized) { - return; - } - - /* Set the base device to our gc asic device. */ - BaseDeviceAccessor::SetDevice(std::addressof(this->gc_asic_device)); - - /* Initialize. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - this->gc_asic_device.InitializeRemovedEvent(); - hc->PreSetRemovedEvent(this->gc_asic_device.GetRemovedEvent()); - } - #endif - hc->Initialize(); - - /* Mark ourselves as initialized. */ - this->is_initialized = true; - } - - void GcAsicDeviceAccessor::Finalize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* If we've already finalized, we don't need to do anything. */ - if (!this->is_initialized) { - return; - } - this->is_initialized = false; - - /* Deactivate the device. */ - BaseDeviceAccessor::Deactivate(); - - /* Finalize the host controller. */ - BaseDeviceAccessor::GetHostController()->Finalize(); - - /* Finalize the removed event. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - this->gc_asic_device.FinalizeRemovedEvent(); - } - #endif - } - - Result GcAsicDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { - /* Check that we can write to output. */ - AMS_ABORT_UNLESS(out_speed_mode != nullptr); - - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - *out_speed_mode = SpeedMode_GcAsicSpeed; - return ResultSuccess(); - } - - void GcAsicDeviceAccessor::PutGcAsicToSleep() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* If the device isn't awake, we don't need to do anything. */ - if (!this->gc_asic_device.IsAwake()) { - return; - } - - /* If necessary, put the host controller to sleep. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) - #else - if (this->gc_asic_device.IsActive()) - #endif - { - BaseDeviceAccessor::GetHostController()->PutToSleep(); - } - - /* Put the gc asic device to sleep. */ - this->gc_asic_device.PutToSleep(); - } - - Result GcAsicDeviceAccessor::AwakenGcAsic() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* If the device is awake, we don't need to do anything. */ - R_SUCCEED_IF(this->gc_asic_device.IsAwake()); - - /* Wake the device. */ - this->gc_asic_device.Awaken(); - - /* Wake the host controller, if we need to.*/ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) - #else - if (this->gc_asic_device.IsActive()) - #endif - { - R_TRY(BaseDeviceAccessor::GetHostController()->Awaken()); - } - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::WriteGcAsicOperation(const void *op_buf, size_t op_buf_size) { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - /* Issue the command. */ - R_TRY(this->IssueCommandWriteOperation(op_buf, op_buf_size)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::FinishGcAsicOperation() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - /* Issue the command. */ - R_TRY(this->IssueCommandFinishOperation()); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::AbortGcAsicOperation() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - /* Issue stop transmission command. */ - u32 resp = 0; - R_TRY(BaseDeviceAccessor::GetHostController()->IssueStopTransmissionCommand(std::addressof(resp))); - R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::SleepGcAsic() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - /* Issue the command. */ - R_TRY(this->IssueCommandSleep()); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result GcAsicDeviceAccessor::UpdateGcAsicKey() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->gc_asic_device.CheckAccessible()); - - /* Issue the command. */ - R_TRY(this->IssueCommandUpdateKey()); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.hpp deleted file mode 100644 index c6c24ec..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_gc_asic_device_accessor.hpp +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - class GcAsicDevice : public BaseDevice { - private: - static constexpr u16 Rca = 0; - private: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - mutable os::EventType removed_event; - #endif - public: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual os::EventType *GetRemovedEvent() const override { - return std::addressof(this->removed_event); - } - #endif - - virtual DeviceType GetDeviceType() const override { - return DeviceType_GcAsic; - } - - virtual u16 GetRca() const override { - return Rca; - } - }; - - class GcAsicDeviceAccessor : public BaseDeviceAccessor { - private: - GcAsicDevice gc_asic_device; - bool is_initialized; - private: - Result IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const; - Result IssueCommandFinishOperation() const; - Result IssueCommandSleep(); - Result IssueCommandUpdateKey() const; - Result StartupGcAsicDevice(); - protected: - virtual Result OnActivate() override; - virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; - - virtual Result ReStartup() override { - AMS_ABORT("Can't ReStartup GcAsic\n"); - } - public: - virtual void Initialize() override; - virtual void Finalize() override; - virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; - public: - explicit GcAsicDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc), is_initialized(false) { - /* ... */ - } - - void PutGcAsicToSleep(); - Result AwakenGcAsic(); - Result WriteGcAsicOperation(const void *op_buf, size_t op_buf_size); - Result FinishGcAsicOperation(); - Result AbortGcAsicOperation(); - Result SleepGcAsic(); - Result UpdateGcAsicKey(); - - void SignalGcRemovedEvent() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - this->gc_asic_device.SignalRemovedEvent(); - #else - AMS_ABORT("SignalGcRemovedEvent called without event support\n"); - #endif - } - - void ClearGcRemovedEvent() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - this->gc_asic_device.ClearRemovedEvent(); - #else - AMS_ABORT("ClearGcRemovedEvent called without event support\n"); - #endif - } - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_device_accessor.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_device_accessor.hpp deleted file mode 100644 index 94e85bd..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_device_accessor.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" - -namespace ams::sdmmc::impl { - - class IDeviceAccessor { - public: - virtual void Initialize() = 0; - virtual void Finalize() = 0; - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; - virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; - #endif - - virtual Result Activate() = 0; - virtual void Deactivate() = 0; - - virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) = 0; - virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) = 0; - - virtual Result GetSpeedMode(SpeedMode *out) const = 0; - virtual Result GetMemoryCapacity(u32 *out_sectors) const = 0; - virtual Result GetDeviceStatus(u32 *out) const = 0; - virtual Result GetOcr(u32 *out) const = 0; - virtual Result GetRca(u16 *out) const = 0; - virtual Result GetCid(void *out, size_t size) const = 0; - virtual Result GetCsd(void *out, size_t size) const = 0; - - virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) = 0; - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_host_controller.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_host_controller.hpp deleted file mode 100644 index f4439bb..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_i_host_controller.hpp +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -#if defined(AMS_SDMMC_USE_OS_EVENTS) -#include -#endif - -namespace ams::sdmmc::impl { - - enum ResponseType { - ResponseType_R0 = 0, - ResponseType_R1 = 1, - ResponseType_R2 = 2, - ResponseType_R3 = 3, - ResponseType_R6 = 4, - ResponseType_R7 = 5, - }; - - enum TransferDirection { - TransferDirection_ReadFromDevice = 0, - TransferDirection_WriteToDevice = 1, - }; - - enum CommandIndex { - /* Generic commands. */ - CommandIndex_GoIdleState = 0, - CommandIndex_SendOpCond = 1, - CommandIndex_AllSendCid = 2, - CommandIndex_SendRelativeAddr = 3, - CommandIndex_SetRelativeAddr = 3, - CommandIndex_SetDsr = 4, - - CommandIndex_Switch = 6, - CommandIndex_SelectCard = 7, - CommandIndex_DeselectCard = 7, - CommandIndex_SendIfCond = 8, - CommandIndex_SendExtCsd = 8, - CommandIndex_SendCsd = 9, - CommandIndex_SendCid = 10, - CommandIndex_VoltageSwitch = 11, - CommandIndex_StopTransmission = 12, - CommandIndex_SendStatus = 13, - CommandIndex_SendTaskStatus = 13, - - CommandIndex_GoInactiveState = 15, - CommandIndex_SetBlockLen = 16, - CommandIndex_ReadSingleBlock = 17, - CommandIndex_ReadMultipleBlock = 18, - CommandIndex_SendTuningBlock = 19, - CommandIndex_SpeedClassControl = 20, - - CommandIndex_AddressExtension = 22, - CommandIndex_SetBlockCount = 23, - CommandIndex_WriteBlock = 24, - CommandIndex_WriteMultipleBlock = 25, - - CommandIndex_ProgramCsd = 27, - CommandIndex_SetWriteProt = 28, - CommandIndex_ClearWriteProt = 29, - CommandIndex_SendWriteProt = 30, - - CommandIndex_EraseWriteBlockStart = 32, - CommandIndex_EraseWriteBlockEnd = 33, - - CommandIndex_EraseGroupStart = 35, - CommandIndex_EraseGroupEnd = 36, - - CommandIndex_Erase = 38, - - CommandIndex_LockUnlock = 42, - - CommandIndex_AppCmd = 55, - CommandIndex_GenCmd = 56, - - /* Nintendo specific vendor commands for lotus3. */ - CommandIndex_GcAsicWriteOperation = 60, - CommandIndex_GcAsicFinishOperation = 61, - CommandIndex_GcAsicSleep = 62, - CommandIndex_GcAsicUpdateKey = 63, - }; - - struct Command { - u32 command_index; - u32 command_argument; - ResponseType response_type; - bool is_busy; - - constexpr Command(u32 ci, u32 ca, ResponseType r, bool b) : command_index(ci), command_argument(ca), response_type(r), is_busy(b) { /* ... */ } - }; - - struct TransferData { - void *buffer; - size_t block_size; - u32 num_blocks; - TransferDirection transfer_direction; - bool is_multi_block_transfer; - bool is_stop_transmission_command_enabled; - - constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd, bool mb, bool st) - : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(mb), is_stop_transmission_command_enabled(st) - { - if (this->num_blocks > 1) { - AMS_ABORT_UNLESS(this->is_multi_block_transfer); - } - } - - constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd) - : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(false), is_stop_transmission_command_enabled(false) - { - AMS_ABORT_UNLESS(this->num_blocks == 1); - } - }; - - class IHostController { - public: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual void PreSetRemovedEvent(ams::os::EventType *event) = 0; - #endif - - virtual void Initialize() = 0; - virtual void Finalize() = 0; - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; - virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; - #endif - - virtual void SetWorkBuffer(void *wb, size_t wb_size) = 0; - - virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) = 0; - virtual void Shutdown() = 0; - virtual void PutToSleep() = 0; - virtual Result Awaken() = 0; - - virtual Result SwitchToSdr12(); - - virtual bool IsSupportedBusPower(BusPower bus_power) const = 0; - virtual BusPower GetBusPower() const = 0; - - virtual bool IsSupportedBusWidth(BusWidth bus_width) const = 0; - virtual void SetBusWidth(BusWidth bus_width) = 0; - virtual BusWidth GetBusWidth() const = 0; - - virtual Result SetSpeedMode(SpeedMode speed_mode) = 0; - virtual SpeedMode GetSpeedMode() const = 0; - - virtual u32 GetDeviceClockFrequencyKHz() const = 0; - - virtual void SetPowerSaving(bool en) = 0; - virtual bool IsPowerSavingEnable() const = 0; - - virtual void EnableDeviceClock() = 0; - virtual void DisableDeviceClock() = 0; - virtual bool IsDeviceClockEnable() const = 0; - - virtual u32 GetMaxTransferNumBlocks() const = 0; - - virtual void ChangeCheckTransferInterval(u32 ms) = 0; - virtual void SetDefaultCheckTransferInterval() = 0; - - virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) = 0; - virtual Result IssueStopTransmissionCommand(u32 *out_response) = 0; - - ALWAYS_INLINE Result IssueCommand(const Command *command, TransferData *xfer_data) { - return this->IssueCommand(command, xfer_data, nullptr); - } - - ALWAYS_INLINE Result IssueCommand(const Command *command) { - return this->IssueCommand(command, nullptr, nullptr); - } - - virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const = 0; - virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const = 0; - - virtual bool IsSupportedTuning() const = 0; - virtual Result Tuning(SpeedMode speed_mode, u32 command_index) = 0; - virtual void SaveTuningStatusForHs400() = 0; - virtual Result GetInternalStatus() const = 0; - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.cpp deleted file mode 100644 index bf36180..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.cpp +++ /dev/null @@ -1,998 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_timer.hpp" -#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" -#include "sdmmc_io_impl.board.nintendo_nx.hpp" - -namespace ams::sdmmc::impl { - - /* Lovingly taken from libexosphere. */ - namespace i2c_impl { - - enum Port { - Port_1 = 0, - /* TODO: Support other ports? */ - Port_5 = 4, - - Port_Count = 5, - }; - - constexpr inline const dd::PhysicalAddress I2c5RegistersAddress = UINT64_C(0x7000D000); - constexpr inline const size_t I2c5RegistersSize = 4_KB; - - namespace { - - constexpr inline size_t MaxTransferSize = sizeof(u32); - - constinit std::array g_register_addresses = [] { - std::array arr = {}; - return arr; - }(); - - void LoadConfig(uintptr_t address) { - /* Configure for TIMEOUT and MSTR config load. */ - /* NOTE: Nintendo writes value 1 to reserved bit 5 here. This bit is documented as having no meaning. */ - /* We will reproduce the write just in case it is undocumented. */ - reg::Write(address + I2C_CONFIG_LOAD, I2C_REG_BITS_VALUE(CONFIG_LOAD_RESERVED_BIT_5, 1), - I2C_REG_BITS_ENUM (CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, ENABLE), - I2C_REG_BITS_ENUM (CONFIG_LOAD_SLV_CONFIG_LOAD, DISABLE), - I2C_REG_BITS_ENUM (CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); - - /* Wait up to 20 microseconds for the master config to be loaded. */ - for (int i = 0; i < 20; ++i) { - if (reg::HasValue(address + I2C_CONFIG_LOAD, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, DISABLE))) { - return; - } - util::WaitMicroSeconds(1); - } - } - - void ClearBus(uintptr_t address) { - /* Configure the bus clear register. */ - reg::Write(address + I2C_BUS_CLEAR_CONFIG, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9), - I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_STOP_COND, NO_STOP), - I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE), - I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); - - /* Load the config. */ - LoadConfig(address); - - /* Wait up to 250us (in 25 us increments) until the bus clear is done. */ - for (int i = 0; i < 10; ++i) { - if (reg::HasValue(address + I2C_INTERRUPT_STATUS_REGISTER, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, SET))) { - break; - } - - util::WaitMicroSeconds(25); - } - - /* Read the bus clear status. */ - reg::Read(address + I2C_BUS_CLEAR_STATUS); - } - - void InitializePort(uintptr_t address) { - /* Calculate the divisor. */ - constexpr int Divisor = util::DivideUp(19200, 8 * 400); - - /* Set the divisor. */ - reg::Write(address + I2C_CLK_DIVISOR_REGISTER, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, Divisor - 1), - I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, 1)); - - /* Clear the bus. */ - ClearBus(address); - - /* Clear the status. */ - reg::Write(address + I2C_INTERRUPT_STATUS_REGISTER, reg::Read(address + I2C_INTERRUPT_STATUS_REGISTER)); - } - - bool Write(uintptr_t base_address, Port port, int address, const void *src, size_t src_size, bool unused) { - AMS_UNUSED(port, unused); - - /* Ensure we don't write too much. */ - u32 data = 0; - if (src_size > MaxTransferSize) { - return false; - } - - /* Copy the data to a transfer word. */ - std::memcpy(std::addressof(data), src, src_size); - - - /* Configure the to write the 7-bit address. */ - reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), - I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, WRITE)); - - /* Configure to write the data. */ - reg::Write(base_address + I2C_I2C_CMD_DATA1, data); - - /* Configure to write the correct amount of data. */ - reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), - I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), - I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, WRITE), - I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, src_size - 1)); - - /* Load the configuration. */ - LoadConfig(base_address); - - /* Start the command. */ - reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); - - /* Wait for the command to be done. */ - while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } - - /* Check if the transfer was successful. */ - return reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL)); - } - - bool Read(uintptr_t base_address, Port port, void *dst, size_t dst_size, int address, bool unused) { - AMS_UNUSED(port, unused); - - /* Ensure we don't read too much. */ - if (dst_size > MaxTransferSize) { - return false; - } - - /* Configure the to read the 7-bit address. */ - reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), - I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, READ)); - - /* Configure to read the correct amount of data. */ - reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), - I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), - I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, READ), - I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, dst_size - 1)); - - /* Load the configuration. */ - LoadConfig(base_address); - - /* Start the command. */ - reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); - - /* Wait for the command to be done. */ - while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } - - /* Check that the transfer was successful. */ - if (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL))) { - return false; - } - - /* Read and copy out the data. */ - u32 data = reg::Read(base_address + I2C_I2C_CMD_DATA1); - std::memcpy(dst, std::addressof(data), dst_size); - return true; - } - - } - - void SetRegisterAddress(Port port, uintptr_t address) { - g_register_addresses[port] = address; - } - - void Initialize(Port port) { - InitializePort(g_register_addresses[port]); - } - - bool Query(void *dst, size_t dst_size, Port port, int address, int r) { - const uintptr_t base_address = g_register_addresses[port]; - - /* Select the register we want to read. */ - bool success = Write(base_address, port, address, std::addressof(r), 1, false); - if (success) { - /* If we successfully selected, read data from the register. */ - success = Read(base_address, port, dst, dst_size, address, true); - } - - return success; - } - - bool Send(Port port, int address, int r, const void *src, size_t src_size) { - const uintptr_t base_address = g_register_addresses[port]; - - /* Create a transfer buffer, make sure we can use it. */ - u8 buffer[MaxTransferSize]; - if (src_size > sizeof(buffer) - 1) { - return false; - } - - /* Copy data into the buffer. */ - buffer[0] = static_cast(r); - std::memcpy(buffer + 1, src, src_size); - - return Write(base_address, port, address, buffer, src_size + 1, false); - } - - } - - namespace max7762x { - - /* The only regulator we care about for SD card power is ldo2. */ - constexpr inline const u8 Max77620PwrI2cAddr = 0x3C; - - constexpr inline const u32 Max77620Ldo2MvStep = 50'000; /* 50 mV (50K uV) steps. */ - constexpr inline const u32 Max77620Ldo2MvMin = 800'000; /* 0.8V min voltage. */ - constexpr inline const u32 Max77620Ldo2MvDefault = 1'800'000; /* 1.8V default voltage. */ - constexpr inline const u32 Max77620Ldo2MvMax = 3'300'000; /* 3.3V max voltage. */ - constexpr inline const u8 Max77620Ldo2VoltAddr = 0x27; - constexpr inline const u8 Max77620Ldo2CfgAddr = 0x28; - constexpr inline const u8 Max77620Ldo2VoltMask = 0x3F; - constexpr inline const u8 Max77620Ldo2EnableMask = 0xC0; - constexpr inline const u8 Max77620Ldo2EnableShift = 0x6; - constexpr inline const u8 Max77620Ldo2StatusMask = 0x00; - - namespace { - - #if defined(AMS_SDMMC_THREAD_SAFE) - constinit os::SdkMutex g_i2c_init_mutex; - - #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() std::scoped_lock lk(g_i2c_init_mutex) - - #else - - #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() - - #endif - - constinit bool g_initialized_i2c = false; - - void EnsureI2cInitialized() { - if (AMS_UNLIKELY(!g_initialized_i2c)) { - /* Ensure we have exclusive access to the i2c init status. */ - AMS_SDMMC_LOCK_I2C_INIT_MUTEX(); - - if (AMS_LIKELY(!g_initialized_i2c)) { - i2c_impl::SetRegisterAddress(i2c_impl::Port_5, dd::QueryIoMapping(i2c_impl::I2c5RegistersAddress, i2c_impl::I2c5RegistersSize)); - i2c_impl::Initialize(i2c_impl::Port_5); - g_initialized_i2c = true; - } - } - } - - } - - bool SetVoltageEnabled(bool en) { - /* Ensure that we can use i2c to communicate with the max7762x regulator. */ - EnsureI2cInitialized(); - - /* Read the current value. */ - u8 val; - if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { - return false; - } - - /* Set or clear the enable mask. */ - val &= ~Max77620Ldo2EnableMask; - if (en) { - val |= ((3 << Max77620Ldo2EnableShift) & Max77620Ldo2EnableMask); - } - - /* Write the updated value. */ - if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { - return false; - } - - /* Wait 1ms for change to take. */ - WaitMicroSeconds(1); - - /* Voltage is now enabled/disabled. */ - return true; - } - - bool SetVoltageValue(u32 micro_volts) { - /* Ensure that we can use i2c to communicate with the max7762x regulator. */ - EnsureI2cInitialized(); - - /* Check that the value is within range. */ - if (micro_volts < Max77620Ldo2MvMin || Max77620Ldo2MvMax < micro_volts) { - return false; - } - - /* Determine the mult. */ - const u32 mult = util::DivideUp(micro_volts - Max77620Ldo2MvMin, Max77620Ldo2MvStep); - - /* Read the current value. */ - u8 val; - if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { - return false; - } - - /* Set the new voltage. */ - val &= ~Max77620Ldo2VoltMask; - val |= (mult & Max77620Ldo2VoltMask); - - /* Write the updated value. */ - if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { - return false; - } - - /* Wait 1ms for change to take. */ - WaitMicroSeconds(1); - - /* Voltage is now set. */ - return true; - } - - } - - Result SetSdCardVoltageEnabled(bool en) { - /* TODO: A way for this to be non-fatal? */ - AMS_ABORT_UNLESS(max7762x::SetVoltageEnabled(en)); - - return ResultSuccess(); - } - - Result SetSdCardVoltageValue(u32 micro_volts) { - /* TODO: A way for this to be non-fatal? */ - AMS_ABORT_UNLESS(max7762x::SetVoltageValue(micro_volts)); - - return ResultSuccess(); - } - - namespace gpio_impl { - - namespace { - - constexpr inline dd::PhysicalAddress GpioRegistersPhysicalAddress = 0x6000d000; - constexpr inline size_t GpioRegistersSize = 4_KB; - - enum GpioPadPort { - GpioPadPort_A = 0, - GpioPadPort_B = 1, - GpioPadPort_C = 2, - GpioPadPort_D = 3, - GpioPadPort_E = 4, - GpioPadPort_F = 5, - GpioPadPort_G = 6, - GpioPadPort_H = 7, - GpioPadPort_I = 8, - GpioPadPort_J = 9, - GpioPadPort_K = 10, - GpioPadPort_L = 11, - GpioPadPort_M = 12, - GpioPadPort_N = 13, - GpioPadPort_O = 14, - GpioPadPort_P = 15, - GpioPadPort_Q = 16, - GpioPadPort_R = 17, - GpioPadPort_S = 18, - GpioPadPort_T = 19, - GpioPadPort_U = 20, - GpioPadPort_V = 21, - GpioPadPort_W = 22, - GpioPadPort_X = 23, - GpioPadPort_Y = 24, - GpioPadPort_Z = 25, - GpioPadPort_AA = 26, - GpioPadPort_BB = 27, - GpioPadPort_CC = 28, - GpioPadPort_DD = 29, - GpioPadPort_EE = 30, - GpioPadPort_FF = 31, - }; - - consteval unsigned int GetInternalGpioPadNumber(GpioPadPort port, unsigned int which) { - AMS_ASSUME(which < 8); - - return (static_cast(port) * 8) + which; - } - - enum InternalGpioPadNumber { - InternalGpioPadNumber_E4 = GetInternalGpioPadNumber(GpioPadPort_E, 4), - InternalGpioPadNumber_M0 = GetInternalGpioPadNumber(GpioPadPort_M, 0), - }; - - constexpr int ConvertInternalGpioPadNumberToController(InternalGpioPadNumber number) { - return (number >> 5); - } - - constexpr int ConvertInternalGpioPadNumberToPort(InternalGpioPadNumber number) { - return (number >> 3); - } - - constexpr int ConvertInternalGpioPadNumberToBitIndex(InternalGpioPadNumber number) { - return (number & 7); - } - - constexpr int ConvertPortNumberToOffset(int port_number) { - return (port_number & 3); - } - - struct PadNameToInternalPadNumberEntry { - GpioPadName pad_name; - InternalGpioPadNumber internal_number; - }; - - constexpr inline const PadNameToInternalPadNumberEntry PadNameToInternalPadNumberTable[] = { - { GpioPadName_PowSdEn, InternalGpioPadNumber_E4 }, - }; - - constexpr InternalGpioPadNumber ConvertPadNameToInternalPadNumber(GpioPadName pad) { - const PadNameToInternalPadNumberEntry *target = nullptr; - for (const auto &entry : PadNameToInternalPadNumberTable) { - if (entry.pad_name == pad) { - target = std::addressof(entry); - break; - } - } - - AMS_ABORT_UNLESS(target != nullptr); - return target->internal_number; - } - - enum GpioRegisterType { - GpioRegisterType_GPIO_CNF = 0, - GpioRegisterType_GPIO_OE = 1, - GpioRegisterType_GPIO_OUT = 2, - GpioRegisterType_GPIO_IN = 3, - GpioRegisterType_GPIO_INT_STA = 4, - GpioRegisterType_GPIO_INT_ENB = 5, - GpioRegisterType_GPIO_INT_LVL = 6, - GpioRegisterType_GPIO_INT_CLR = 7, - GpioRegisterType_GPIO_DB_CTRL = 8, - GpioRegisterType_GPIO_DB_CNT = 9, - }; - - constexpr inline uintptr_t MaskedWriteAddressOffset = 0x80; - constexpr inline int MaskedWriteBitOffset = 8; - - constexpr uintptr_t GetGpioRegisterAddress(uintptr_t gpio_address, GpioRegisterType reg_type, InternalGpioPadNumber pad_number) { - const auto controller = ConvertInternalGpioPadNumberToController(pad_number); - const auto port = ConvertInternalGpioPadNumberToPort(pad_number); - const auto offset = ConvertPortNumberToOffset(port); - - switch (reg_type) { - default: - return gpio_address + (0x100 * controller) + (0x10 * reg_type) + (0x4 * offset); - case GpioRegisterType_GPIO_DB_CTRL: - return gpio_address + (0x100 * controller) + (0x10 * GpioRegisterType_GPIO_IN) + (0x4 * offset); - case GpioRegisterType_GPIO_DB_CNT: - return gpio_address + (0x100 * controller) + MaskedWriteAddressOffset + (0x10 * GpioRegisterType_GPIO_INT_CLR) + (0x4 * offset); - } - } - - void SetMaskedBit(uintptr_t pad_address, int index, int value) { - const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; - - reg::Write(mask_address, (1u << (MaskedWriteBitOffset + index)) | (static_cast(value) << index)); - } - - void SetMaskedBits(uintptr_t pad_address, unsigned int mask, unsigned int value) { - const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; - - reg::Write(mask_address, (mask << MaskedWriteBitOffset) | (value)); - } - - } - - void OpenSession(GpioPadName pad) { - /* Convert the pad to an internal number. */ - const auto pad_number = ConvertPadNameToInternalPadNumber(pad); - - /* Get the gpio registers address. */ - const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); - - /* Configure the pad as GPIO by setting the appropriate bit in CNF. */ - const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_CNF, pad_number); - const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); - SetMaskedBit(pad_address, pad_index, 1); - - /* Read the pad address to make sure our configuration takes. */ - reg::Read(pad_address); - } - - void CloseSession(GpioPadName pad) { - /* Nothing needs to be done here, as the only thing official code does is unbind the interrupt event. */ - AMS_UNUSED(pad); - } - - void SetDirection(GpioPadName pad, Direction direction) { - /* Convert the pad to an internal number. */ - const auto pad_number = ConvertPadNameToInternalPadNumber(pad); - - /* Get the gpio registers address. */ - const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); - - /* Configure the pad direction modifying the appropriate bit in OE. */ - const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OE, pad_number); - const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); - SetMaskedBit(pad_address, pad_index, direction); - - /* Read the pad address to make sure our configuration takes. */ - reg::Read(pad_address); - } - - void SetValue(GpioPadName pad, GpioValue value) { - /* Convert the pad to an internal number. */ - const auto pad_number = ConvertPadNameToInternalPadNumber(pad); - - /* Get the gpio registers address. */ - const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); - - /* Configure the pad value modifying the appropriate bit in OUT. */ - const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OUT, pad_number); - const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); - SetMaskedBit(pad_address, pad_index, value); - - /* Read the pad address to make sure our configuration takes. */ - reg::Read(pad_address); - } - - } - - namespace pinmux_impl { - - namespace { - - constexpr auto Sdmmc1ClkCmdDat03PadNumber = gpio_impl::InternalGpioPadNumber_M0; - - constexpr unsigned int Sdmmc1ClkCmdDat03PadMask = 0x3F; - - constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfGpio = 0x3F; - constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfSfio = 0x00; - - constexpr unsigned int Sdmmc1ClkCmdDat03PadOutHigh = 0x3F; - constexpr unsigned int Sdmmc1ClkCmdDat03PadOutLow = 0x00; - - constexpr unsigned int Sdmmc1ClkCmdDat03PadOeOutput = 0x3F; - constexpr unsigned int Sdmmc1ClkCmdDat03PadOeInput = 0x00; - - struct PinmuxDefinition { - u32 reg_offset; - u32 mask_val; - u32 pm_val; - }; - - /* NOTE: We only use the SDMMC1 pins, which are conveniently the first few... */ - constexpr const PinmuxDefinition PinmuxDefinitionMap[] = { - {0x00003000, 0x72FF, 0x01}, /* Sdmmc1Clk */ - {0x00003004, 0x72FF, 0x02}, /* Sdmmc1Cmd */ - {0x00003008, 0x72FF, 0x02}, /* Sdmmc1Dat3 */ - {0x0000300C, 0x72FF, 0x02}, /* Sdmmc1Dat2 */ - {0x00003010, 0x72FF, 0x02}, /* Sdmmc1Dat1 */ - {0x00003014, 0x72FF, 0x01}, /* Sdmmc1Dat0 */ - }; - - enum PinmuxPadIndex { - PinmuxPadIndex_Sdmmc1Clk = 0, - PinmuxPadIndex_Sdmmc1Cmd = 1, - PinmuxPadIndex_Sdmmc1Dat3 = 2, - PinmuxPadIndex_Sdmmc1Dat2 = 3, - PinmuxPadIndex_Sdmmc1Dat1 = 4, - PinmuxPadIndex_Sdmmc1Dat0 = 5, - - PinmuxPadIndex_Count, - }; - - static_assert(util::size(PinmuxDefinitionMap) == PinmuxPadIndex_Count); - - consteval const PinmuxDefinition GetDefinition(PinmuxPadIndex pad_index) { - AMS_ABORT_UNLESS(pad_index < PinmuxPadIndex_Count); - - return PinmuxDefinitionMap[pad_index]; - } - - template - ALWAYS_INLINE u32 UpdatePinmuxPad(uintptr_t pinmux_base_vaddr) { - constexpr const PinmuxDefinition Definition = GetDefinition(PadIndex); - - /* Fetch this PINMUX's register offset */ - constexpr u32 PinmuxRegOffset = Definition.reg_offset; - - /* Fetch this PINMUX's mask value */ - constexpr u32 PinmuxMaskVal = Definition.mask_val; - - /* Get current register ptr. */ - const uintptr_t pinmux_reg = pinmux_base_vaddr + PinmuxRegOffset; - - /* Read from the PINMUX register */ - u32 pinmux_val = reg::Read(pinmux_reg); - - /* This PINMUX register is locked */ - AMS_ABORT_UNLESS((pinmux_val & 0x80) == 0); - - constexpr u32 PmVal = (PinmuxConfigVal & 0x07); - - /* Adjust PM */ - if constexpr (PinmuxConfigMaskVal & 0x07) { - /* Apply additional changes first */ - if constexpr (PmVal == 0x05) { - /* This pin supports PUPD change */ - if constexpr (PinmuxMaskVal & 0x0C) { - /* Change PUPD */ - if ((pinmux_val & 0x0C) != 0x04) { - pinmux_val &= 0xFFFFFFF3; - pinmux_val |= 0x04; - } - } - - /* This pin supports Tristate change */ - if constexpr (PinmuxMaskVal & 0x10) { - /* Change Tristate */ - if (!(pinmux_val & 0x10)) { - pinmux_val |= 0x10; - } - } - - /* This pin supports EInput change */ - if constexpr (PinmuxMaskVal & 0x40) { - /* Change EInput */ - if (pinmux_val & 0x40) { - pinmux_val &= 0xFFFFFFBF; - } - } - } - - /* Translate PM value if necessary */ - constexpr u32 TranslatedPmVal = (PmVal == 0x04 || PmVal == 0x05 || PmVal >= 0x06) ? Definition.pm_val : PmVal; - - /* This pin supports PM change */ - if constexpr (PinmuxMaskVal & 0x03) { - /* Change PM */ - if ((pinmux_val & 0x03) != (TranslatedPmVal & 0x03)) { - pinmux_val &= 0xFFFFFFFC; - pinmux_val |= (TranslatedPmVal & 0x03); - } - } - } - - constexpr u32 PupdConfigVal = (PinmuxConfigVal & 0x18); - - /* Adjust PUPD */ - if constexpr (PinmuxConfigMaskVal & 0x18) { - if constexpr (PupdConfigVal < 0x11) { - /* This pin supports PUPD change */ - if constexpr (PinmuxMaskVal & 0x0C) { - /* Change PUPD */ - if (((pinmux_val >> 0x02) & 0x03) != (PupdConfigVal >> 0x03)) { - pinmux_val &= 0xFFFFFFF3; - pinmux_val |= (PupdConfigVal >> 0x01); - } - } - } - } - - constexpr u32 EodConfigVal = (PinmuxConfigVal & 0x60); - - /* Adjust EOd field */ - if constexpr (PinmuxConfigMaskVal & 0x60) { - if constexpr (EodConfigVal == 0x20) { - /* This pin supports Tristate change */ - if constexpr (PinmuxMaskVal & 0x10) { - /* Change Tristate */ - if (!(pinmux_val & 0x10)) { - pinmux_val |= 0x10; - } - } - - /* This pin supports EInput change */ - if constexpr (PinmuxMaskVal & 0x40) { - /* Change EInput */ - if (!(pinmux_val & 0x40)) { - pinmux_val |= 0x40; - } - } - - /* This pin supports EOd change */ - if constexpr (PinmuxMaskVal & 0x800) { - /* Change EOd */ - if (pinmux_val & 0x800) { - pinmux_val &= 0xFFFFF7FF; - } - } - } else if constexpr (EodConfigVal == 0x40) { - /* This pin supports Tristate change */ - if constexpr (PinmuxMaskVal & 0x10) { - /* Change Tristate */ - if (pinmux_val & 0x10) { - pinmux_val &= 0xFFFFFFEF; - } - } - - /* This pin supports EInput change */ - if constexpr (PinmuxMaskVal & 0x40) { - /* Change EInput */ - if (!(pinmux_val & 0x40)) { - pinmux_val |= 0x40; - } - } - - /* This pin supports EOd change */ - if constexpr (PinmuxMaskVal & 0x800) { - /* Change EOd */ - if (pinmux_val & 0x800) { - pinmux_val &= 0xFFFFF7FF; - } - } - } else if constexpr (EodConfigVal == 0x60) { - /* This pin supports Tristate change */ - if constexpr (PinmuxMaskVal & 0x10) { - /* Change Tristate */ - if (pinmux_val & 0x10) { - pinmux_val &= 0xFFFFFFEF; - } - } - - /* This pin supports EInput change */ - if constexpr (PinmuxMaskVal & 0x40) { - /* Change EInput */ - if (!(pinmux_val & 0x40)) { - pinmux_val |= 0x40; - } - } - - /* This pin supports EOd change */ - if constexpr (PinmuxMaskVal & 0x800) { - /* Change EOd */ - if (!(pinmux_val & 0x800)) { - pinmux_val |= 0x800; - } - } - } else { - /* This pin supports Tristate change */ - if constexpr (PinmuxMaskVal & 0x10) { - /* Change Tristate */ - if (pinmux_val & 0x10) { - pinmux_val &= 0xFFFFFFEF; - } - } - - /* This pin supports EInput change */ - if constexpr (PinmuxMaskVal & 0x40) { - /* Change EInput */ - if (pinmux_val & 0x40) { - pinmux_val &= 0xFFFFFFBF; - } - } - - /* This pin supports EOd change */ - if constexpr (PinmuxMaskVal & 0x800) { - /* Change EOd */ - if (pinmux_val & 0x800) { - pinmux_val &= 0xFFFFF7FF; - } - } - } - } - - constexpr u32 LockConfigVal = (PinmuxConfigVal & 0x80); - - /* Adjust Lock */ - if constexpr (PinmuxConfigMaskVal & 0x80) { - /* This pin supports Lock change */ - if constexpr (PinmuxMaskVal & 0x80) { - /* Change Lock */ - if ((pinmux_val ^ PinmuxConfigVal) & 0x80) { - pinmux_val &= 0xFFFFFF7F; - pinmux_val |= LockConfigVal; - } - } - } - - constexpr u32 IoResetConfigVal = (((PinmuxConfigVal >> 0x08) & 0x1) << 0x10); - - /* Adjust IoReset */ - if constexpr (PinmuxConfigMaskVal & 0x100) { - /* This pin supports IoReset change */ - if constexpr (PinmuxMaskVal & 0x10000) { - /* Change IoReset */ - if (((pinmux_val >> 0x10) ^ (PinmuxConfigVal >> 0x08)) & 0x01) { - pinmux_val &= 0xFFFEFFFF; - pinmux_val |= IoResetConfigVal; - } - } - } - - constexpr u32 ParkConfigVal = (((PinmuxConfigVal >> 0x0A) & 0x1) << 0x5); - - /* Adjust Park */ - if constexpr (PinmuxConfigMaskVal & 0x400) { - /* This pin supports Park change */ - if constexpr (PinmuxMaskVal & 0x20) { - /* Change Park */ - if (((pinmux_val >> 0x05) ^ (PinmuxConfigVal >> 0x0A)) & 0x01) { - pinmux_val &= 0xFFFFFFDF; - pinmux_val |= ParkConfigVal; - } - } - } - - constexpr u32 ElpdrConfigVal = (((PinmuxConfigVal >> 0x0B) & 0x1) << 0x08); - - /* Adjust ELpdr */ - if constexpr (PinmuxConfigMaskVal & 0x800) { - /* This pin supports ELpdr change */ - if constexpr (PinmuxMaskVal & 0x100) { - /* Change ELpdr */ - if (((pinmux_val >> 0x08) ^ (PinmuxConfigVal >> 0x0B)) & 0x01) { - pinmux_val &= 0xFFFFFEFF; - pinmux_val |= ElpdrConfigVal; - } - } - } - - constexpr u32 EhsmConfigVal = (((PinmuxConfigVal >> 0x0C) & 0x1) << 0x09); - - /* Adjust EHsm */ - if constexpr (PinmuxConfigMaskVal & 0x1000) { - /* This pin supports EHsm change */ - if constexpr (PinmuxMaskVal & 0x200) { - /* Change EHsm */ - if (((pinmux_val >> 0x09) ^ (PinmuxConfigVal >> 0x0C)) & 0x01) { - pinmux_val &= 0xFFFFFDFF; - pinmux_val |= EhsmConfigVal; - } - } - } - - constexpr u32 EIoHvConfigVal = (((PinmuxConfigVal >> 0x09) & 0x1) << 0x0A); - - /* Adjust EIoHv */ - if constexpr (PinmuxConfigMaskVal & 0x200) { - /* This pin supports EIoHv change */ - if constexpr (PinmuxMaskVal & 0x400) { - /* Change EIoHv */ - if (((pinmux_val >> 0x0A) ^ (PinmuxConfigVal >> 0x09)) & 0x01) { - pinmux_val &= 0xFFFFFBFF; - pinmux_val |= EIoHvConfigVal; - } - } - } - - constexpr u32 EschmtConfigVal = (((PinmuxConfigVal >> 0x0D) & 0x1) << 0x0C); - - /* Adjust ESchmt */ - if constexpr (PinmuxConfigMaskVal & 0x2000) { - /* This pin supports ESchmt change */ - if constexpr (PinmuxMaskVal & 0x1000) { - /* Change ESchmt */ - if (((pinmux_val >> 0x0C) ^ (PinmuxConfigVal >> 0x0D)) & 0x01) { - pinmux_val &= 0xFFFFEFFF; - pinmux_val |= EschmtConfigVal; - } - } - } - - constexpr u32 PreempConfigVal = (((PinmuxConfigVal >> 0x10) & 0x1) << 0xF); - - /* Adjust Preemp */ - if constexpr (PinmuxConfigMaskVal & 0x10000) { - /* This pin supports Preemp change */ - if constexpr (PinmuxMaskVal & 0x8000) { - /* Change Preemp */ - if (((pinmux_val >> 0x0F) ^ (PinmuxConfigVal >> 0x10)) & 0x01) { - pinmux_val &= 0xFFFF7FFF; - pinmux_val |= PreempConfigVal; - } - } - } - - constexpr u32 DrvTypeConfigVal = (((PinmuxConfigVal >> 0x0E) & 0x3) << 0xD); - - /* Adjust DrvType */ - if constexpr (PinmuxConfigMaskVal & 0xC000) { - /* This pin supports DrvType change */ - if constexpr (PinmuxMaskVal & 0x6000) { - /* Change DrvType */ - if (((pinmux_val >> 0x0D) ^ (PinmuxConfigVal >> 0x0E)) & 0x03) { - pinmux_val &= 0xFFFF9FFF; - pinmux_val |= DrvTypeConfigVal; - } - } - } - - /* Write to the appropriate PINMUX register */ - reg::Write(pinmux_reg, pinmux_val); - - /* Do a dummy read from the PINMUX register */ - pinmux_val = reg::Read(pinmux_reg); - - return pinmux_val; - } - - } - - void SetPinAssignment(PinAssignment assignment) { - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - AMS_UNUSED(apb_address); - - /* Set the pin assignment. */ - switch (assignment) { - case PinAssignment_Sdmmc1OutputHigh: - { - /* Clear Sdmmc1Clk pulldown. */ - UpdatePinmuxPad(apb_address); - - /* Get the gpio registers address. */ - const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as gpio. */ - const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfGpio); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as high. */ - const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutHigh); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as output. */ - const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeOutput); - - /* Read to be sure that our configuration takes. */ - reg::Read(oe_address); - } - break; - case PinAssignment_Sdmmc1ResetState: - { - /* Get the gpio registers address. */ - const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as sfio. */ - const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfSfio); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as low. */ - const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutLow); - - /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as input. */ - const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); - gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeInput); - - /* Read to be sure that our configuration takes. */ - reg::Read(oe_address); - - /* Set Sdmmc1Clk pulldown. */ - UpdatePinmuxPad(apb_address); - } - break; - case PinAssignment_Sdmmc1SchmtEnable: - { - /* Set Schmitt enable for all pins in the group. */ - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - } - break; - case PinAssignment_Sdmmc1SchmtDisable: - { - /* Set Schmitt disable for all pins in the group. */ - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - UpdatePinmuxPad(apb_address); - } - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.hpp deleted file mode 100644 index 89b6230..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_io_impl.board.nintendo_nx.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -namespace ams::sdmmc::impl { - - Result SetSdCardVoltageEnabled(bool en); - Result SetSdCardVoltageValue(u32 micro_volts); - - namespace pinmux_impl { - - enum PinAssignment { - PinAssignment_Sdmmc1OutputHigh = 2, - PinAssignment_Sdmmc1ResetState = 3, - PinAssignment_Sdmmc1SchmtEnable = 4, - PinAssignment_Sdmmc1SchmtDisable = 5, - }; - - void SetPinAssignment(PinAssignment assignment); - - } - - namespace gpio_impl { - - enum GpioValue { - GpioValue_Low = 0, - GpioValue_High = 1 - }; - - enum Direction { - Direction_Input = 0, - Direction_Output = 1, - }; - - enum GpioPadName { - GpioPadName_PowSdEn = 2, - }; - - void OpenSession(GpioPadName pad); - void CloseSession(GpioPadName pad); - - void SetDirection(GpioPadName pad, Direction direction); - void SetValue(GpioPadName pad, GpioValue value); - - - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.cpp deleted file mode 100644 index 7468aab..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.cpp +++ /dev/null @@ -1,728 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_mmc_device_accessor.hpp" -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl { - - #if defined(AMS_SDMMC_THREAD_SAFE) - - #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() std::scoped_lock lk(this->mmc_device.device_mutex) - - #else - - #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() - - #endif - - namespace { - - constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); - - constexpr inline u32 OcrAccessMode_Mask = (3 << 29); - constexpr inline u32 OcrAccessMode_SectorMode = (2 << 29); - - constexpr inline u8 ManufacturerId_Toshiba = 0x11; - - enum DeviceType : u8 { - DeviceType_HighSpeed26MHz = (1u << 0), - DeviceType_HighSpeed52MHz = (1u << 1), - DeviceType_HighSpeedDdr52MHz1_8VOr3_0V = (1u << 2), - DeviceType_HighSpeedDdr52MHz1_2V = (1u << 3), - DeviceType_Hs200Sdr200MHz1_8V = (1u << 4), - DeviceType_Hs200Sdr200MHz1_2V = (1u << 5), - DeviceType_Hs400Sdr200MHz1_8V = (1u << 6), - DeviceType_Hs400Sdr200MHz1_2V = (1u << 7), - }; - - constexpr bool IsToshibaMmc(const u8 *cid) { - /* Check whether the CID's manufacturer id field is Toshiba. */ - AMS_ABORT_UNLESS(cid != nullptr); - return cid[14] == ManufacturerId_Toshiba; - } - - constexpr bool IsLessThanSpecification4(const u8 *csd) { - const u8 spec_vers = ((csd[14] >> 2) & 0xF); - return spec_vers < 4; - } - - constexpr bool IsBkopAutoEnable(const u8 *ext_csd) { - /* Check the AUTO_EN bit of BKOPS_EN. */ - return (ext_csd[163] & (1u << 1)) != 0; - } - - constexpr u8 GetDeviceType(const u8 *ext_csd) { - /* Get the DEVICE_TYPE register. */ - AMS_ABORT_UNLESS(ext_csd != nullptr); - return ext_csd[196]; - } - - constexpr bool IsSupportedHs400(u8 device_type) { - return (device_type & DeviceType_Hs400Sdr200MHz1_8V) != 0; - } - - constexpr bool IsSupportedHs200(u8 device_type) { - return (device_type & DeviceType_Hs200Sdr200MHz1_8V) != 0; - } - - constexpr bool IsSupportedHighSpeed(u8 device_type) { - return (device_type & DeviceType_HighSpeed52MHz) != 0; - } - - constexpr u32 GetMemoryCapacityFromExtCsd(const u32 *ext_csd) { - /* Get the SEC_COUNT register. */ - AMS_ABORT_UNLESS(ext_csd != nullptr); - return ext_csd[212 / sizeof(u32)]; - } - - constexpr u32 GetBootPartitionMemoryCapacityFromExtCsd(const u8 *ext_csd) { - /* Get the BOOT_SIZE_MULT register. */ - AMS_ABORT_UNLESS(ext_csd != nullptr); - return ext_csd[226] * (128_KB / SectorSize); - } - - constexpr Result GetCurrentSpeedModeFromExtCsd(SpeedMode *out, const u8 *ext_csd) { - /* Get the HS_TIMING register. */ - AMS_ABORT_UNLESS(out != nullptr); - AMS_ABORT_UNLESS(ext_csd != nullptr); - - switch (ext_csd[185] & 0xF) { - case 0: *out = SpeedMode_MmcLegacySpeed; break; - case 1: *out = SpeedMode_MmcHighSpeed; break; - case 2: *out = SpeedMode_MmcHs200; break; - case 3: *out = SpeedMode_MmcHs400; break; - default: return sdmmc::ResultUnexpectedMmcExtendedCsdValue(); - } - - return ResultSuccess(); - } - - } - - void MmcDevice::SetOcrAndHighCapacity(u32 ocr) { - /* Set ocr. */ - BaseDevice::SetOcr(ocr); - - /* Set high capacity. */ - BaseDevice::SetHighCapacity((ocr & OcrAccessMode_Mask) == OcrAccessMode_SectorMode); - } - - Result MmcDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const { - /* Get the command argument. */ - u32 arg = OcrAccessMode_SectorMode; - switch (bus_power) { - case BusPower_1_8V: arg |= 0x000080; break; - case BusPower_3_3V: arg |= 0x03F800; break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R3; - Command command(CommandIndex_SendOpCond, arg, CommandResponseType, false); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command))); - - /* Get the response. */ - hc->GetLastResponse(out_ocr, sizeof(*out_ocr), CommandResponseType); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandSetRelativeAddr() const { - /* Get rca. */ - const u32 rca = this->mmc_device.GetRca(); - AMS_ABORT_UNLESS(rca > 0); - - /* Issue comamnd. */ - const u32 arg = rca << 16; - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_SetRelativeAddr, arg, false, DeviceState_Unknown)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandSwitch(CommandSwitch cs) const { - /* Get the command argument. */ - const u32 arg = GetCommandSwitchArgument(cs); - - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Switch, arg, true, DeviceState_Unknown)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandSendExtCsd(void *dst, size_t dst_size) const { - /* Validate the output buffer. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= MmcExtendedCsdSize); - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(CommandIndex_SendExtCsd, 0, CommandResponseType, false); - TransferData xfer_data(dst, MmcExtendedCsdSize, 1, TransferDirection_ReadFromDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->mmc_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandEraseGroupStart(u32 sector_index) const { - /* Get the command argument. */ - const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; - - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupStart, arg, false, DeviceState_Unknown)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandEraseGroupEnd(u32 sector_index) const { - /* Get the command argument. */ - const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; - - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupEnd, arg, false, DeviceState_Tran)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::IssueCommandErase() const { - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Erase, 0, false, DeviceState_Tran)); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::CancelToshibaMmcModel() { - /* Special erase sequence done by Nintendo on Toshiba MMCs. */ - R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsProductionStateAwarenessEnable)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - R_TRY(this->IssueCommandSwitch(CommandSwitch_ClearBitsAutoModeEnable)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessNormal)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::ChangeToReadyState(BusPower bus_power) { - /* Be prepared to wait up to 1.5 seconds to change state. */ - ManualTimer timer(1500); - while (true) { - /* Get the ocr, and check if we're done. */ - u32 ocr; - R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), bus_power)); - if ((ocr & OcrCardPowerUpStatus) != 0) { - this->mmc_device.SetOcrAndHighCapacity(ocr); - return ResultSuccess(); - } - - /* Check if we've timed out. */ - R_UNLESS(timer.Update(), sdmmc::ResultMmcInitializationSoftwareTimeout()); - - /* Try again in 1ms. */ - WaitMicroSeconds(1000); - } - } - - Result MmcDeviceAccessor::ExtendBusWidth(BusWidth max_bw) { - /* If the maximum bus width is 1bit, we can't extend. */ - R_SUCCEED_IF(max_bw == BusWidth_1Bit); - - /* Determine what bus width to switch to. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - BusWidth target_bw = BusWidth_1Bit; - CommandSwitch cs; - if (max_bw == BusWidth_8Bit && hc->IsSupportedBusWidth(BusWidth_8Bit)) { - target_bw = BusWidth_8Bit; - cs = CommandSwitch_WriteBusWidth8Bit; - } else if ((max_bw == BusWidth_8Bit || max_bw == BusWidth_4Bit) && hc->IsSupportedBusWidth(BusWidth_4Bit)) { - target_bw = BusWidth_4Bit; - cs = CommandSwitch_WriteBusWidth4Bit; - } else { - /* Target bus width is 1bit. */ - return ResultSuccess(); - } - - /* Set the bus width. */ - R_TRY(this->IssueCommandSwitch(cs)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - hc->SetBusWidth(target_bw); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::EnableBkopsAuto() { - /* Issue the command. */ - R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsBkopsEnAutoEn)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::ChangeToHighSpeed(bool check_before) { - /* Issue high speed command. */ - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHighSpeed)); - - /* If we should check status before setting mode, do so. */ - if (check_before) { - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - } - - /* Set the host controller to high speed. */ - R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_MmcHighSpeed)); - - /* If we should check status after setting mode, do so. */ - if (!check_before) { - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - } - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::ChangeToHs200() { - /* Issue Hs200 command. */ - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs200)); - - /* Set the host controller to Hs200. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs200)); - - /* Perform tuning using command index 21. */ - R_TRY(hc->Tuning(SpeedMode_MmcHs200, 21)); - - /* Check status. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::ChangeToHs400() { - /* Change first to Hs200. */ - R_TRY(this->ChangeToHs200()); - - /* Save tuning status. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - hc->SaveTuningStatusForHs400(); - - /* Change to high speed. */ - R_TRY(this->ChangeToHighSpeed(false)); - - /* Issue Hs400 command. */ - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteBusWidth8BitDdr)); - R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs400)); - - /* Set the host controller to Hs400. */ - R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs400)); - - /* Check status. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::ExtendBusSpeed(u8 device_type, SpeedMode max_sm) { - /* We want to switch to the highest speed we can. */ - /* Check Hs400/Hs200 first. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - if (hc->IsSupportedTuning() && hc->GetBusPower() == BusPower_1_8V) { - if (hc->GetBusWidth() == BusWidth_8Bit && IsSupportedHs400(device_type) && max_sm == SpeedMode_MmcHs400) { - return this->ChangeToHs400(); - } else if ((hc->GetBusWidth() == BusWidth_8Bit || hc->GetBusWidth() == BusWidth_4Bit) && IsSupportedHs200(device_type) && (max_sm == SpeedMode_MmcHs400 || max_sm == SpeedMode_MmcHs200)) { - return this->ChangeToHs200(); - } - } - - /* Check if we can switch to high speed. */ - if (IsSupportedHighSpeed(device_type)) { - return this->ChangeToHighSpeed(true); - } - - /* We can't, so stay at normal speeds. */ - return ResultSuccess(); - } - - Result MmcDeviceAccessor::StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { - /* Start up at an appropriate bus power. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - const BusPower bp = hc->IsSupportedBusPower(BusPower_1_8V) ? BusPower_1_8V : BusPower_3_3V; - R_TRY(hc->Startup(bp, BusWidth_1Bit, SpeedMode_MmcIdentification, false)); - - /* Wait 1ms for configuration to take. */ - WaitMicroSeconds(1000); - - /* Wait an additional 74 clocks for configuration to take. */ - WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); - - /* Go to idle state. */ - R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); - this->current_partition = MmcPartition_UserData; - - /* Go to ready state. */ - R_TRY(this->ChangeToReadyState(bp)); - - /* Get the CID. */ - R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); - this->mmc_device.SetCid(wb, wb_size); - const bool is_toshiba = IsToshibaMmc(static_cast(wb)); - - /* Issue set relative addr. */ - R_TRY(this->IssueCommandSetRelativeAddr()); - - /* Get the CSD. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); - this->mmc_device.SetCsd(wb, wb_size); - const bool spec_under_4 = IsLessThanSpecification4(static_cast(wb)); - - /* Set the speed mode to legacy. */ - R_TRY(hc->SetSpeedMode(SpeedMode_MmcLegacySpeed)); - - /* Issue select card command. */ - R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); - - /* Set block length to sector size. */ - R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); - - /* If the device SPEC_VERS is less than 4, extended csd/switch aren't supported. */ - if (spec_under_4) { - R_TRY(this->mmc_device.SetLegacyMemoryCapacity()); - - this->mmc_device.SetActive(); - return ResultSuccess(); - } - - /* Extend the bus width to the largest that we can. */ - R_TRY(this->ExtendBusWidth(max_bw)); - - /* Get the extended csd. */ - R_TRY(this->IssueCommandSendExtCsd(wb, wb_size)); - AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(wb), alignof(u32))); - this->mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast(wb))); - - /* If the mmc is manufactured by toshiba, try to enable bkops auto. */ - if (is_toshiba && !IsBkopAutoEnable(static_cast(wb))) { - /* NOTE: Nintendo does not check the result of this. */ - this->EnableBkopsAuto(); - } - - /* Extend the bus speed to as fast as we can. */ - const u8 device_type = GetDeviceType(static_cast(wb)); - R_TRY(this->ExtendBusSpeed(device_type, max_sm)); - - /* Enable power saving. */ - hc->SetPowerSaving(true); - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::OnActivate() { - /* Define the possible startup parameters. */ - constexpr const struct { - BusWidth bus_width; - SpeedMode speed_mode; - } StartupParameters[] = { - #if defined(AMS_SDMMC_ENABLE_MMC_HS400) - { BusWidth_8Bit, SpeedMode_MmcHs400 }, - #else - { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, - #endif - { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, - { BusWidth_1Bit, SpeedMode_MmcHighSpeed }, - }; - - /* Try to start up with each set of parameters. */ - Result result; - for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { - /* Alias the parameters. */ - const auto ¶ms = StartupParameters[i]; - - /* Set our max bus width/speed mode. */ - this->max_bus_width = params.bus_width; - this->max_speed_mode = params.speed_mode; - - /* Try to start up the device. */ - result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); - if (R_SUCCEEDED(result)) { - /* If we previously failed to start up the device, log the error correction. */ - if (i != 0) { - BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); - BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); - } - - return ResultSuccess(); - } - - /* Log that our startup failed. */ - BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); - - /* Shut down the host controller before we try to start up again. */ - BaseDeviceAccessor::GetHostController()->Shutdown(); - } - - /* We failed to start up with all sets of parameters. */ - BaseDeviceAccessor::PushErrorTimeStamp(); - - return result; - } - - Result MmcDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { - /* Get the sector index alignment. */ - u32 sector_index_alignment = 0; - if (!is_read) { - constexpr u32 MmcWriteSectorAlignment = 16_KB / SectorSize; - sector_index_alignment = MmcWriteSectorAlignment; - AMS_ABORT_UNLESS(util::IsAligned(sector_index, MmcWriteSectorAlignment)); - } - - /* Do the read/write. */ - return BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, sector_index_alignment, buf, buf_size, is_read); - } - - Result MmcDeviceAccessor::ReStartup() { - /* Shut down the host controller. */ - BaseDeviceAccessor::GetHostController()->Shutdown(); - - /* Perform start up. */ - Result result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); - if (R_FAILED(result)) { - BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); - return result; - } - - return ResultSuccess(); - } - - void MmcDeviceAccessor::Initialize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* If we've already initialized, we don't need to do anything. */ - if (this->is_initialized) { - return; - } - - /* Set the base device to our mmc device. */ - BaseDeviceAccessor::SetDevice(std::addressof(this->mmc_device)); - - /* Initialize. */ - BaseDeviceAccessor::GetHostController()->Initialize(); - this->is_initialized = true; - } - - void MmcDeviceAccessor::Finalize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* If we've already finalized, we don't need to do anything. */ - if (!this->is_initialized) { - return; - } - this->is_initialized = false; - - /* Deactivate the device. */ - BaseDeviceAccessor::Deactivate(); - - /* Finalize the host controller. */ - BaseDeviceAccessor::GetHostController()->Finalize(); - } - - Result MmcDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { - /* Check that we can write to output. */ - AMS_ABORT_UNLESS(out_speed_mode != nullptr); - - /* Get the current speed mode from the ext csd. */ - R_TRY(GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); - R_TRY(GetCurrentSpeedModeFromExtCsd(out_speed_mode, static_cast(this->work_buffer))); - - return ResultSuccess(); - } - - void MmcDeviceAccessor::PutMmcToSleep() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* If the device isn't awake, we don't need to do anything. */ - if (!this->mmc_device.IsAwake()) { - return; - } - - /* Put the device to sleep. */ - this->mmc_device.PutToSleep(); - - /* If necessary, put the host controller to sleep. */ - if (this->mmc_device.IsActive()) { - BaseDeviceAccessor::GetHostController()->PutToSleep(); - } - } - - void MmcDeviceAccessor::AwakenMmc() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* If the device is awake, we don't need to do anything. */ - if (this->mmc_device.IsAwake()) { - return; - } - - /* Wake the host controller, if we need to.*/ - if (this->mmc_device.IsActive()) { - const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); - if (R_FAILED(result)) { - BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); - } - } - - /* Wake the device. */ - this->mmc_device.Awaken(); - } - - Result MmcDeviceAccessor::SelectMmcPartition(MmcPartition part) { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* Check that we can access the device. */ - R_TRY(this->mmc_device.CheckAccessible()); - - /* Determine the appropriate SWITCH subcommand. */ - CommandSwitch cs; - switch (part) { - case MmcPartition_UserData: cs = CommandSwitch_WritePartitionAccessDefault; break; - case MmcPartition_BootPartition1: cs = CommandSwitch_WritePartitionAccessRwBootPartition1; break; - case MmcPartition_BootPartition2: cs = CommandSwitch_WritePartitionAccessRwBootPartition2; break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Change partition. */ - this->current_partition = MmcPartition_Unknown; - { - R_TRY(this->IssueCommandSwitch(cs)); - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - } - this->current_partition = part; - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::EraseMmc() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* Check that we can access the device. */ - R_TRY(this->mmc_device.CheckAccessible()); - - /* Get the partition capacity. */ - u32 part_capacity; - switch (this->current_partition) { - case MmcPartition_UserData: - part_capacity = this->mmc_device.GetMemoryCapacity(); - break; - case MmcPartition_BootPartition1: - case MmcPartition_BootPartition2: - R_TRY(this->GetMmcBootPartitionCapacity(std::addressof(part_capacity))); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Begin the erase. */ - R_TRY(this->IssueCommandEraseGroupStart(0)); - R_TRY(this->IssueCommandEraseGroupEnd(part_capacity - 1)); - - /* Issue the erase command, allowing 30 seconds for it to complete. */ - ManualTimer timer(30000); - Result result = this->IssueCommandErase(); - R_TRY_CATCH(result) { - R_CATCH(sdmmc::ResultDataTimeoutError) { /* Data timeout error is acceptable. */ } - R_CATCH(sdmmc::ResultCommandCompleteSoftwareTimeout) { /* Command complete software timeout error is acceptable. */ } - R_CATCH(sdmmc::ResultBusySoftwareTimeout) { /* Busy software timeout error is acceptable. */ } - } R_END_TRY_CATCH; - - /* Wait for the erase to finish. */ - while (true) { - /* Check if we're done. */ - result = BaseDeviceAccessor::IssueCommandSendStatus(); - if (R_SUCCEEDED(result)) { - break; - } - - /* Otherwise, check if we should reject the error. */ - if (!sdmmc::ResultUnexpectedDeviceState::Includes(result)) { - return result; - } - - /* Check if timeout has been exceeded. */ - R_UNLESS(timer.Update(), sdmmc::ResultMmcEraseSoftwareTimeout()); - } - - /* If the partition is user data, check if we need to perform toshiba-specific erase. */ - if (this->current_partition == MmcPartition_UserData) { - u8 cid[DeviceCidSize]; - this->mmc_device.GetCid(cid, sizeof(cid)); - if (IsToshibaMmc(cid)) { - /* NOTE: Nintendo does not check the result of this operation. */ - this->CancelToshibaMmcModel(); - } - } - - return ResultSuccess(); - } - - Result MmcDeviceAccessor::GetMmcBootPartitionCapacity(u32 *out_num_sectors) const { - /* Get the capacity from the extended csd. */ - AMS_ABORT_UNLESS(out_num_sectors != nullptr); - R_TRY(this->GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); - - *out_num_sectors = GetBootPartitionMemoryCapacityFromExtCsd(static_cast(this->work_buffer)); - return ResultSuccess(); - } - - Result MmcDeviceAccessor::GetMmcExtendedCsd(void *dst, size_t dst_size) const { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); - - /* Check that we can access the device. */ - R_TRY(this->mmc_device.CheckAccessible()); - - /* Get the csd. */ - u8 csd[DeviceCsdSize]; - this->mmc_device.GetCsd(csd, sizeof(csd)); - - /* Check that the card supports ext csd. */ - R_UNLESS(!IsLessThanSpecification4(csd), sdmmc::ResultMmcNotSupportExtendedCsd()); - - /* Get the ext csd. */ - R_TRY(this->IssueCommandSendExtCsd(dst, dst_size)); - - return ResultSuccess(); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.hpp deleted file mode 100644 index 97ac864..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_mmc_device_accessor.hpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - class MmcDevice : public BaseDevice { - private: - static constexpr u16 Rca = 2; - public: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual os::EventType *GetRemovedEvent() const override { - /* Mmc can't be removed. */ - return nullptr; - } - #endif - - virtual DeviceType GetDeviceType() const override { - return DeviceType_Mmc; - } - - virtual u16 GetRca() const override { - return Rca; - } - - void SetOcrAndHighCapacity(u32 ocr); - }; - - class MmcDeviceAccessor : public BaseDeviceAccessor { - private: - MmcDevice mmc_device; - void *work_buffer; - size_t work_buffer_size; - BusWidth max_bus_width; - SpeedMode max_speed_mode; - MmcPartition current_partition; - bool is_initialized; - private: - enum CommandSwitch { - CommandSwitch_SetBitsProductionStateAwarenessEnable = 0, - CommandSwitch_ClearBitsAutoModeEnable = 1, - CommandSwitch_WriteProductionStateAwarenessNormal = 2, - CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites = 3, - CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites = 4, - CommandSwitch_SetBitsBkopsEnAutoEn = 5, - CommandSwitch_WriteBusWidth1Bit = 6, - CommandSwitch_WriteBusWidth4Bit = 7, - CommandSwitch_WriteBusWidth8Bit = 8, - CommandSwitch_WriteBusWidth8BitDdr = 9, - CommandSwitch_WriteHsTimingLegacySpeed = 10, - CommandSwitch_WriteHsTimingHighSpeed = 11, - CommandSwitch_WriteHsTimingHs200 = 12, - CommandSwitch_WriteHsTimingHs400 = 13, - CommandSwitch_WritePartitionAccessDefault = 14, - CommandSwitch_WritePartitionAccessRwBootPartition1 = 15, - CommandSwitch_WritePartitionAccessRwBootPartition2 = 16, - }; - - static constexpr ALWAYS_INLINE u32 GetCommandSwitchArgument(CommandSwitch cs) { - switch (cs) { - case CommandSwitch_SetBitsProductionStateAwarenessEnable: return 0x01111000; - case CommandSwitch_ClearBitsAutoModeEnable: return 0x02112000; - case CommandSwitch_WriteProductionStateAwarenessNormal: return 0x03850000; - case CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites: return 0x03850100; - case CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites: return 0x03850200; - case CommandSwitch_SetBitsBkopsEnAutoEn: return 0x01A30200; - case CommandSwitch_WriteBusWidth1Bit: return 0x03B70000; - case CommandSwitch_WriteBusWidth4Bit: return 0x03B70100; - case CommandSwitch_WriteBusWidth8Bit: return 0x03B70200; - case CommandSwitch_WriteBusWidth8BitDdr: return 0x03B70600; - case CommandSwitch_WriteHsTimingLegacySpeed: return 0x03B90000; - case CommandSwitch_WriteHsTimingHighSpeed: return 0x03B90100; - case CommandSwitch_WriteHsTimingHs200: return 0x03B90200; - case CommandSwitch_WriteHsTimingHs400: return 0x03B90300; - case CommandSwitch_WritePartitionAccessDefault: return 0x03B30000; - case CommandSwitch_WritePartitionAccessRwBootPartition1: return 0x03B30100; - case CommandSwitch_WritePartitionAccessRwBootPartition2: return 0x03B30200; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - private: - Result IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const; - Result IssueCommandSetRelativeAddr() const; - Result IssueCommandSwitch(CommandSwitch cs) const; - Result IssueCommandSendExtCsd(void *dst, size_t dst_size) const; - Result IssueCommandEraseGroupStart(u32 sector_index) const; - Result IssueCommandEraseGroupEnd(u32 sector_index) const; - Result IssueCommandErase() const; - Result CancelToshibaMmcModel(); - Result ChangeToReadyState(BusPower bus_power); - Result ExtendBusWidth(BusWidth max_bus_width); - Result EnableBkopsAuto(); - Result ChangeToHighSpeed(bool check_before); - Result ChangeToHs200(); - Result ChangeToHs400(); - Result ExtendBusSpeed(u8 device_type, SpeedMode max_sm); - Result StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); - protected: - virtual Result OnActivate() override; - virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; - virtual Result ReStartup() override; - public: - virtual void Initialize() override; - virtual void Finalize() override; - virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; - public: - explicit MmcDeviceAccessor(IHostController *hc) - : BaseDeviceAccessor(hc), work_buffer(nullptr), work_buffer_size(0), - max_bus_width(BusWidth_8Bit), max_speed_mode(SpeedMode_MmcHs400), current_partition(MmcPartition_Unknown), - is_initialized(false) - { - /* ... */ - } - - void SetMmcWorkBuffer(void *wb, size_t wb_size) { - this->work_buffer = wb; - this->work_buffer_size = wb_size; - } - - void PutMmcToSleep(); - void AwakenMmc(); - Result SelectMmcPartition(MmcPartition part); - Result EraseMmc(); - Result GetMmcBootPartitionCapacity(u32 *out_num_sectors) const; - Result GetMmcExtendedCsd(void *dst, size_t dst_size) const; - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.cpp deleted file mode 100644 index 763869d..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_port_gc_asic0.hpp" -#include "sdmmc_select_sdmmc_controller.hpp" - - -namespace ams::sdmmc::impl { - - namespace { - - SdmmcControllerForPortGcAsic0 g_gc_asic0_host_controller; - GcAsicDeviceAccessor g_gc_asic0_device_accessor(std::addressof(g_gc_asic0_host_controller)); - - } - - IHostController *GetHostControllerOfPortGcAsic0() { - return std::addressof(g_gc_asic0_host_controller); - } - - IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0() { - return std::addressof(g_gc_asic0_device_accessor); - } - - GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0() { - return std::addressof(g_gc_asic0_device_accessor); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.hpp deleted file mode 100644 index 8dc7880..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_gc_asic0.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" -#include "sdmmc_i_device_accessor.hpp" -#include "sdmmc_gc_asic_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - IHostController *GetHostControllerOfPortGcAsic0(); - IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0(); - GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0(); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.cpp deleted file mode 100644 index 30cfc86..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_port_mmc0.hpp" -#include "sdmmc_select_sdmmc_controller.hpp" -#include "sdmmc_base_device_accessor.hpp" - - -namespace ams::sdmmc::impl { - - namespace { - - SdmmcControllerForPortMmc0 g_mmc0_host_controller; - MmcDeviceAccessor g_mmc0_device_accessor(std::addressof(g_mmc0_host_controller)); - - } - - IHostController *GetHostControllerOfPortMmc0() { - return std::addressof(g_mmc0_host_controller); - } - - IDeviceAccessor *GetDeviceAccessorOfPortMmc0() { - return std::addressof(g_mmc0_device_accessor); - } - - MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0() { - return std::addressof(g_mmc0_device_accessor); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.hpp deleted file mode 100644 index b675bd4..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_mmc0.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" -#include "sdmmc_i_device_accessor.hpp" -#include "sdmmc_mmc_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - IHostController *GetHostControllerOfPortMmc0(); - IDeviceAccessor *GetDeviceAccessorOfPortMmc0(); - MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0(); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.cpp deleted file mode 100644 index 5288a5e..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_port_sd_card0.hpp" -#include "sdmmc_select_sdmmc_controller.hpp" - - -namespace ams::sdmmc::impl { - - namespace { - - SdmmcControllerForPortSdCard0 g_sd_card0_host_controller; - - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - - constexpr inline u32 SdCard0DebounceMilliSeconds = 128; - DeviceDetector g_sd_card0_detector(gpio::DeviceCode_SdCd, gpio::GpioValue_Low, SdCard0DebounceMilliSeconds); - - SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller), std::addressof(g_sd_card0_detector)); - - #else - - SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller)); - - #endif - - - } - - IHostController *GetHostControllerOfPortSdCard0() { - return std::addressof(g_sd_card0_host_controller); - } - - IDeviceAccessor *GetDeviceAccessorOfPortSdCard0() { - return std::addressof(g_sd_card0_device_accessor); - } - - SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0() { - return std::addressof(g_sd_card0_device_accessor); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.hpp deleted file mode 100644 index 690dbb6..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_port_sd_card0.hpp +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" -#include "sdmmc_i_device_accessor.hpp" -#include "sdmmc_sd_card_device_accessor.hpp" - -namespace ams::sdmmc::impl { - - IHostController *GetHostControllerOfPortSdCard0(); - IDeviceAccessor *GetDeviceAccessorOfPortSdCard0(); - SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0(); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.cpp deleted file mode 100644 index 9bfff1a..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.cpp +++ /dev/null @@ -1,1054 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_sd_card_device_accessor.hpp" -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl { - - #if defined(AMS_SDMMC_THREAD_SAFE) - - #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() std::scoped_lock lk(this->sd_card_device.device_mutex) - - #else - - #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() - - #endif - - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - - #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() R_UNLESS(!this->sd_card_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) - - #else - - #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() - - #endif - - namespace { - - constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); - constexpr inline u32 OcrCardCapacityStatus = (1 << 30); - constexpr inline u32 OcrSwitchingTo1_8VAccepted = (1 << 24); - - constexpr bool IsLessThanSpecification1_1(const u8 *scr) { - AMS_ABORT_UNLESS(scr != nullptr); - - const u8 sd_spec = scr[0] & 0xF; - return sd_spec < 1; - } - - constexpr u32 GetSendOpCmdArgument(bool spec_under_2, bool uhs_i_supported) { - const u32 hcs = !spec_under_2 ? (1u << 30) : (0u << 30); - const u32 xpc = !spec_under_2 ? (1u << 28) : (0u << 28); - const u32 s18r = (!spec_under_2 && uhs_i_supported) ? (1u << 24) : (0u << 24); - return hcs | xpc | s18r | 0x00100000u; - } - - constexpr bool IsLessThanCsdVersion2(const u8 *csd) { - AMS_ABORT_UNLESS(csd != nullptr); - - /* Check whether CSD_STRUCTURE is 0. */ - return ((csd[14] & 0xC0) >> 6) == 0; - } - - constexpr u32 GetMemoryCapacityFromCsd(const u16 *csd) { - AMS_ABORT_UNLESS(csd != nullptr); - - /* Get CSIZE, convert appropriately. */ - const u32 csize = (static_cast(csd[3] & 0x3FFF) << 8) | (static_cast(csd[2] & 0xFF00) >> 8); - return (1 + csize) << 10; - } - - constexpr u8 GetSdBusWidths(const u8 *scr) { - AMS_ABORT_UNLESS(scr != nullptr); - return scr[1] & 0xF; - } - - constexpr bool IsSupportedBusWidth4Bit(u8 sd_bw) { - return (sd_bw & 0x4) != 0; - } - - constexpr bool IsSupportedAccessMode(const u8 *status, SwitchFunctionAccessMode access_mode) { - AMS_ABORT_UNLESS(status != nullptr); - - return (status[13] & (1u << access_mode)) != 0; - } - - constexpr u8 GetAccessModeFromFunctionSelection(const u8 *status) { - AMS_ABORT_UNLESS(status != nullptr); - return (status[16] & 0xF); - } - - constexpr bool IsAccessModeInFunctionSelection(const u8 *status, SwitchFunctionAccessMode mode) { - return GetAccessModeFromFunctionSelection(status) == static_cast(mode); - } - - constexpr u16 GetMaximumCurrentConsumption(const u8 *status) { - AMS_ABORT_UNLESS(status != nullptr); - - return (static_cast(status[0]) << 8) | - (static_cast(status[1]) << 0); - } - - constexpr u32 GetSizeOfProtectedArea(const u8 *sd_status) { - return (static_cast(sd_status[4]) << 24) | - (static_cast(sd_status[5]) << 16) | - (static_cast(sd_status[6]) << 8) | - (static_cast(sd_status[7]) << 0); - } - - Result GetCurrentSpeedMode(SpeedMode *out_sm, const u8 *status, bool is_uhs_i) { - AMS_ABORT_UNLESS(out_sm != nullptr); - - /* Get the access mode. */ - switch (static_cast(GetAccessModeFromFunctionSelection(status))) { - case SwitchFunctionAccessMode_Default: - if (is_uhs_i) { - *out_sm = SpeedMode_SdCardSdr12; - } else { - *out_sm = SpeedMode_SdCardDefaultSpeed; - } - break; - case SwitchFunctionAccessMode_HighSpeed: - if (is_uhs_i) { - *out_sm = SpeedMode_SdCardSdr25; - } else { - *out_sm = SpeedMode_SdCardHighSpeed; - } - break; - case SwitchFunctionAccessMode_Sdr50: - *out_sm = SpeedMode_SdCardSdr50; - break; - case SwitchFunctionAccessMode_Sdr104: - *out_sm = SpeedMode_SdCardSdr104; - break; - case SwitchFunctionAccessMode_Ddr50: - *out_sm = SpeedMode_SdCardDdr50; - break; - default: - return sdmmc::ResultUnexpectedSdCardSwitchFunctionStatus(); - } - - return ResultSuccess(); - } - - } - - void SdCardDevice::SetOcrAndHighCapacity(u32 ocr) { - /* Set ocr. */ - BaseDevice::SetOcr(ocr); - - /* Set high capacity. */ - BaseDevice::SetHighCapacity((ocr & OcrCardCapacityStatus) != 0); - } - - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - void SdCardDeviceAccessor::RemovedCallback() { - /* Signal that the device was removed. */ - this->sd_card_device.SignalRemovedEvent(); - - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Shut down. */ - BaseDeviceAccessor::GetHostController()->Shutdown(); - } - #endif - - Result SdCardDeviceAccessor::IssueCommandSendRelativeAddr(u16 *out_rca) const { - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R6; - Command command(CommandIndex_SendRelativeAddr, 0, CommandResponseType, false); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - - /* Set the output rca. */ - AMS_ABORT_UNLESS(out_rca != nullptr); - *out_rca = static_cast(resp >> 16); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSendIfCond() const { - /* Get the argument. */ - constexpr u32 SendIfCommandArgument = 0x01AAu; - constexpr u32 SendIfCommandArgumentMask = 0x0FFFu; - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R7; - Command command(CommandIndex_SendIfCond, SendIfCommandArgument, CommandResponseType, false); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - - /* Verify that our argument was returned to us. */ - R_UNLESS((resp & SendIfCommandArgumentMask) == (SendIfCommandArgument & SendIfCommandArgumentMask), sdmmc::ResultSdCardValidationError()); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const { - /* Validate the output buffer. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); - - /* Get the argument. */ - constexpr u32 CheckSupportedFunctionArgument = 0x00FFFFFF; - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(CommandIndex_Switch, CheckSupportedFunctionArgument, CommandResponseType, false); - TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const { - /* Validate the output buffer. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); - - /* Get the argument. */ - const u32 arg = (set_function ? (1u << 31) : (0u << 31)) | 0x00FFFFF0 | static_cast(access_mode); - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(CommandIndex_Switch, arg, CommandResponseType, false); - TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandVoltageSwitch() const { - /* Issue the command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_VoltageSwitch, 0, false, DeviceState_Ready)); - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask) const { - /* Get arg. */ - const u32 arg = static_cast(this->sd_card_device.GetRca()) << 16; - - /* Issue the command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(CommandIndex_AppCmd, arg, CommandResponseType, false); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - - /* Mask out the ignored status bits. */ - if (ignore_mask != 0) { - resp &= ~ignore_mask; - } - - /* Check the device status. */ - R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); - - /* Check the app command bit. */ - R_UNLESS((resp & DeviceStatus_AppCmd) != 0, sdmmc::ResultUnexpectedSdCardAcmdDisabled()); - - /* Check the device state. */ - if (expected_state != DeviceState_Unknown) { - R_UNLESS(this->sd_card_device.GetDeviceState(resp) == expected_state, sdmmc::ResultUnexpectedDeviceState()); - } - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSetBusWidth4Bit() const { - /* Issue the application command. */ - constexpr u32 Arg = 0x2; - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetBusWidth, Arg, false, DeviceState_Tran)); - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSdStatus(void *dst, size_t dst_size) const { - /* Validate the output buffer. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= SdCardSdStatusSize); - - /* Issue the application command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(SdApplicationCommandIndex_SdStatus, 0, CommandResponseType, false); - TransferData xfer_data(dst, SdCardSdStatusSize, 1, TransferDirection_ReadFromDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const { - /* Get the argument. */ - const u32 arg = GetSendOpCmdArgument(spec_under_2, uhs_i_supported); - - /* Issue the application command. */ - constexpr ResponseType CommandResponseType = ResponseType_R3; - Command command(SdApplicationCommandIndex_SdSendOpCond, arg, CommandResponseType, false); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command))); - - /* Get the response. */ - hc->GetLastResponse(out_ocr, sizeof(u32), CommandResponseType); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandClearCardDetect() const { - /* Issue the application command. */ - R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetClearCardDetect, 0, false, DeviceState_Tran)); - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::IssueCommandSendScr(void *dst, size_t dst_size) const { - /* Validate the output buffer. */ - AMS_ABORT_UNLESS(dst != nullptr); - AMS_ABORT_UNLESS(dst_size >= SdCardScrSize); - - /* Issue the application command. */ - constexpr ResponseType CommandResponseType = ResponseType_R1; - Command command(SdApplicationCommandIndex_SendScr, 0, CommandResponseType, false); - TransferData xfer_data(dst, SdCardScrSize, 1, TransferDirection_ReadFromDevice); - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); - - /* Get the response. */ - u32 resp; - hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); - R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::EnterUhsIMode() { - /* Send voltage switch command. */ - R_TRY(this->IssueCommandVoltageSwitch()); - - /* Switch to sdr12. */ - R_TRY(BaseDeviceAccessor::GetHostController()->SwitchToSdr12()); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::ChangeToReadyState(bool spec_under_2, bool uhs_i_supported) { - /* Decide on an ignore mask. */ - u32 ignore_mask = spec_under_2 ? static_cast(DeviceStatus_IllegalCommand) : 0u; - - /* Be prepared to wait up to 3.0 seconds to change state. */ - ManualTimer timer(3000); - while (true) { - /* We want to get ocr, which requires our sending an application command. */ - R_TRY(this->IssueCommandAppCmd(DeviceState_Unknown, ignore_mask)); - ignore_mask = 0; - - /* Get the ocr, and check if we're done. */ - u32 ocr; - R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), spec_under_2, uhs_i_supported)); - - if ((ocr & OcrCardPowerUpStatus) != 0) { - this->sd_card_device.SetOcrAndHighCapacity(ocr); - - /* Handle uhs i mode. */ - this->sd_card_device.SetUhsIMode(false); - if (uhs_i_supported && ((ocr & OcrSwitchingTo1_8VAccepted) != 0)) { - R_TRY(this->EnterUhsIMode()); - this->sd_card_device.SetUhsIMode(true); - } - - return ResultSuccess(); - } - - /* Check if we've timed out. */ - R_UNLESS(timer.Update(), sdmmc::ResultSdCardInitializationSoftwareTimeout()); - - /* Try again in 1ms. */ - WaitMicroSeconds(1000); - } - } - - Result SdCardDeviceAccessor::ChangeToStbyStateAndGetRca() { - /* Be prepared to wait up to 1.0 seconds to change state. */ - ManualTimer timer(1000); - while (true) { - /* Get rca. */ - u16 rca; - R_TRY(this->IssueCommandSendRelativeAddr(std::addressof(rca))); - if (rca != 0) { - this->sd_card_device.SetRca(rca); - return ResultSuccess(); - } - - /* Check if we've timed out. */ - R_UNLESS(timer.Update(), sdmmc::ResultSdCardGetValidRcaSoftwareTimeout()); - } - } - - Result SdCardDeviceAccessor::SetMemoryCapacity(const void *csd) { - if (IsLessThanCsdVersion2(static_cast(csd))) { - R_TRY(this->sd_card_device.SetLegacyMemoryCapacity()); - } else { - AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(csd), alignof(u16))); - this->sd_card_device.SetMemoryCapacity(GetMemoryCapacityFromCsd(static_cast(csd))); - } - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetScr(void *dst, size_t dst_size) const { - /* Issue the application command. */ - R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); - R_TRY(this->IssueCommandSendScr(dst, dst_size)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::ExtendBusWidth(BusWidth max_bw, u8 sd_bw) { - /* If the maximum bus width is 1bit, we can't extend. */ - R_SUCCEED_IF(max_bw == BusWidth_1Bit); - - /* If 4bit mode isn't supported, we can't extend. */ - R_SUCCEED_IF(!IsSupportedBusWidth4Bit(sd_bw)); - - /* If the host controller doesn't support 4bit mode, we can't extend. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_SUCCEED_IF(!hc->IsSupportedBusWidth(BusWidth_4Bit)); - - /* Issue the application command to change to 4bit mode. */ - R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); - R_TRY(this->IssueCommandSetBusWidth4Bit()); - - /* Set the host controller's bus width. */ - hc->SetBusWidth(BusWidth_4Bit); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size) { - /* Issue command to check if we can switch access mode. */ - R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, false, access_mode)); - R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardCannotSwitchAccessMode()); - - /* Check if we can accept the resulting current consumption. */ - constexpr u16 AcceptableCurrentLimit = 800; /* mA */ - R_UNLESS(GetMaximumCurrentConsumption(static_cast(wb)) < AcceptableCurrentLimit, sdmmc::ResultSdCardUnacceptableCurrentConsumption()); - - /* Switch the access mode. */ - R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, true, access_mode)); - R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardFailedSwitchAccessMode()); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size) { - /* Check that we're in 4bit bus mode. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_UNLESS(hc->GetBusWidth() == BusWidth_4Bit, sdmmc::ResultSdCardNot4BitBusWidthAtUhsIMode()); - - /* Determine what speed mode/access mode we should switch to. */ - R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); - SwitchFunctionAccessMode target_am; - SpeedMode target_sm; - if (max_sm == SpeedMode_SdCardSdr104 && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr104)) { - target_am = SwitchFunctionAccessMode_Sdr104; - target_sm = SpeedMode_SdCardSdr104; - } else if ((max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50) && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr50)) { - target_am = SwitchFunctionAccessMode_Sdr50; - target_sm = SpeedMode_SdCardSdr50; - } else { - return sdmmc::ResultSdCardNotSupportSdr104AndSdr50(); - } - - /* Switch the access mode. */ - R_TRY(this->SwitchAccessMode(target_am, wb, wb_size)); - - /* Set the host controller speed mode and perform tuning using command index 19. */ - R_TRY(hc->SetSpeedMode(target_sm)); - R_TRY(hc->Tuning(target_sm, 19)); - - /* Check status. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size) { - /* If the maximum speed is default speed, we have nothing to do. */ - R_SUCCEED_IF(max_sm == SpeedMode_SdCardDefaultSpeed); - - /* Otherwise, if the spec is under 1.1, we have nothing to do. */ - R_SUCCEED_IF(spec_under_1_1); - - /* Otherwise, Check if high speed is supported. */ - R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); - R_SUCCEED_IF(!IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_HighSpeed)); - - /* Switch the access mode. */ - R_TRY(this->SwitchAccessMode(SwitchFunctionAccessMode_HighSpeed, wb, wb_size)); - - /* Check status. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); - - /* Set the host controller speed mode. */ - R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_SdCardHighSpeed)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetSdStatus(void *dst, size_t dst_size) const { - /* Issue the application command. */ - R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); - R_TRY(this->IssueCommandSdStatus(dst, dst_size)); - - return ResultSuccess(); - } - - void SdCardDeviceAccessor::TryDisconnectDat3PullUpResistor() const { - /* Issue the application command to clear card detect. */ - /* NOTE: Nintendo accepts a failure. */ - if (R_SUCCEEDED(this->IssueCommandAppCmd(DeviceState_Tran))) { - /* NOTE: Nintendo does not check the result of this. */ - this->IssueCommandClearCardDetect(); - } - - /* NOTE: Nintendo does not check the result of this. */ - BaseDeviceAccessor::IssueCommandSendStatus(); - } - - Result SdCardDeviceAccessor::StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { - /* Start up the host controller. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - R_TRY(hc->Startup(BusPower_3_3V, BusWidth_1Bit, SpeedMode_SdCardIdentification, false)); - - /* Wait 1ms for configuration to take. */ - WaitMicroSeconds(1000); - - /* Wait an additional 74 clocks for configuration to take. */ - WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); - - /* Go to idle state. */ - R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); - - /* Check whether the spec is under 2.0. */ - bool spec_under_2 = false; - R_TRY_CATCH(this->IssueCommandSendIfCond()) { - R_CATCH(sdmmc::ResultResponseTimeoutError) { spec_under_2 = true; } - } R_END_TRY_CATCH; - - /* Set the rca to 0. */ - this->sd_card_device.SetRca(0); - - /* Go to ready state. */ - const bool can_use_uhs_i_mode = (max_bw != BusWidth_1Bit) && (max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50); - const bool uhs_i_supported = hc->IsSupportedTuning() && hc->IsSupportedBusPower(BusPower_1_8V); - R_TRY(this->ChangeToReadyState(spec_under_2, can_use_uhs_i_mode && uhs_i_supported)); - - /* Get the CID. */ - R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); - this->sd_card_device.SetCid(wb, wb_size); - - /* Go to stby state and get the RCA. */ - R_TRY(this->ChangeToStbyStateAndGetRca()); - - /* Get the CSD. */ - R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); - this->sd_card_device.SetCsd(wb, wb_size); - R_TRY(this->SetMemoryCapacity(wb)); - - /* Set the host controller speed mode to default if we're not in uhs i mode. */ - if (!this->sd_card_device.IsUhsIMode()) { - R_TRY(hc->SetSpeedMode(SpeedMode_SdCardDefaultSpeed)); - } - - /* Issue select card command. */ - R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); - - /* Set block length to sector size. */ - R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); - - /* Try to disconnect dat3 pullup resistor. */ - TryDisconnectDat3PullUpResistor(); - - /* Get the SCR. */ - R_TRY(this->GetScr(wb, wb_size)); - const u8 sd_bw = GetSdBusWidths(static_cast(wb)); - const bool spec_under_1_1 = IsLessThanSpecification1_1(static_cast(wb)); - - /* Extend the bus width to the largest that we can. */ - R_TRY(this->ExtendBusWidth(max_bw, sd_bw)); - - /* Extend the bus speed to as fast as we can. */ - if (this->sd_card_device.IsUhsIMode()) { - R_TRY(this->ExtendBusSpeedAtUhsIMode(max_sm, wb, wb_size)); - } else { - R_TRY(this->ExtendBusSpeedAtNonUhsIMode(max_sm, spec_under_1_1, wb, wb_size)); - } - - /* Enable power saving. */ - hc->SetPowerSaving(true); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::OnActivate() { - /* Define the possible startup parameters. */ - constexpr const struct { - BusWidth bus_width; - SpeedMode speed_mode; - } StartupParameters[] = { - #if defined(AMS_SDMMC_ENABLE_SD_UHS_I) - { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, - { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, - { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, - { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, - { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, - #else - { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, - { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, - { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, - { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, - #endif - }; - - /* Try to start up with each set of parameters. */ - Result result; - for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { - /* Alias the parameters. */ - const auto ¶ms = StartupParameters[i]; - - /* Set our max bus width/speed mode. */ - this->max_bus_width = params.bus_width; - this->max_speed_mode = params.speed_mode; - - /* Try to start up the device. */ - result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); - if (R_SUCCEEDED(result)) { - /* If we previously failed to start up the device, log the error correction. */ - if (i != 0) { - BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); - BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); - } - - return ResultSuccess(); - } - - /* Check if we were removed. */ - AMS_SDMMC_CHECK_SD_CARD_REMOVED(); - - /* Log that our startup failed. */ - BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); - - /* Shut down the host controller before we try to start up again. */ - BaseDeviceAccessor::GetHostController()->Shutdown(); - } - - /* We failed to start up with all sets of parameters. */ - /* Check the csd for errors. */ - if (sdmmc::ResultUnexpectedDeviceCsdValue::Includes(result)) { - u32 csd[DeviceCsdSize / sizeof(u32)]; - this->sd_card_device.GetCsd(csd, sizeof(csd)); - BaseDeviceAccessor::PushErrorLog(false, "%06X%08X%08X%08X", csd[3] & 0x00FFFFFF, csd[2], csd[1], csd[0]); - } - BaseDeviceAccessor::PushErrorTimeStamp(); - - /* Check if we failed because the sd card is removed. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { - WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); - AMS_SDMMC_CHECK_SD_CARD_REMOVED(); - } - #endif - - return result; - } - - Result SdCardDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { - /* Do the read/write. */ - const Result result = BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, 0, buf, buf_size, is_read); - - /* Check if we failed because the sd card is removed. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { - WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); - AMS_SDMMC_CHECK_SD_CARD_REMOVED(); - } - #endif - - return result; - } - - Result SdCardDeviceAccessor::ReStartup() { - /* Shut down the host controller. */ - BaseDeviceAccessor::GetHostController()->Shutdown(); - - /* Perform start up. */ - Result result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); - if (R_FAILED(result)) { - AMS_SDMMC_CHECK_SD_CARD_REMOVED(); - - BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); - return result; - } - - return ResultSuccess(); - } - - void SdCardDeviceAccessor::Initialize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* If we've already initialized, we don't need to do anything. */ - if (this->is_initialized) { - return; - } - - /* Set the base device to our sd card device. */ - BaseDeviceAccessor::SetDevice(std::addressof(this->sd_card_device)); - - /* Initialize. */ - IHostController *hc = BaseDeviceAccessor::GetHostController(); - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - { - /* TODO: We probably want this (and other sd card detection stuff) to be conditional pcv control active. */ - /* This will be a requirement to support sd card access with detector in stratosphere before PCV is alive. */ - this->sd_card_device.InitializeRemovedEvent(); - hc->PreSetRemovedEvent(this->sd_card_device.GetRemovedEvent()); - CallbackInfo ci = { - .inserted_callback = nullptr, - .inserted_callback_arg = this, - .removed_callback = RemovedCallbackEntry, - .removed_callback_arg = this, - }; - this->sd_card_detector->Initialize(std::addressof(ci)); - } - #endif - hc->Initialize(); - - /* Mark ourselves as initialized. */ - this->is_initialized = true; - } - - void SdCardDeviceAccessor::Finalize() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* If we've already finalized, we don't need to do anything. */ - if (!this->is_initialized) { - return; - } - this->is_initialized = false; - - /* Deactivate the device. */ - BaseDeviceAccessor::Deactivate(); - - /* Finalize the host controller. */ - BaseDeviceAccessor::GetHostController()->Finalize(); - - /* Finalize the detector. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - { - this->sd_card_detector->Finalize(); - this->sd_card_device.FinalizeRemovedEvent(); - } - #endif - } - - Result SdCardDeviceAccessor::Activate() { - /* Activate the detector. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're awake. */ - R_UNLESS(this->sd_card_device.IsAwake(), sdmmc::ResultNotAwakened()); - - /* Check that we're not already active. */ - R_SUCCEED_IF(this->sd_card_device.IsActive()); - - /* Clear the removed event. */ - this->sd_card_device.ClearRemovedEvent(); - - /* Check that the SD card is inserted. */ - R_UNLESS(this->sd_card_detector->IsInserted(), sdmmc::ResultNoDevice()); - } - #endif - - /* Activate the base device. */ - return BaseDeviceAccessor::Activate(); - } - - Result SdCardDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { - /* Check that we can write to output. */ - AMS_ABORT_UNLESS(out_speed_mode != nullptr); - - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->sd_card_device.CheckAccessible()); - - /* Check whether we're specification 1 (and thus default speed). */ - R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); - if (IsLessThanSpecification1_1(static_cast(this->work_buffer))) { - *out_speed_mode = SpeedMode_SdCardDefaultSpeed; - return ResultSuccess(); - } - - /* Get the current speed mode. */ - R_TRY(this->IssueCommandCheckSupportedFunction(this->work_buffer, this->work_buffer_size)); - R_TRY(GetCurrentSpeedMode(out_speed_mode, static_cast(this->work_buffer), this->sd_card_device.IsUhsIMode())); - - return ResultSuccess(); - } - - void SdCardDeviceAccessor::PutSdCardToSleep() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* If the device isn't awake, we don't need to do anything. */ - if (!this->sd_card_device.IsAwake()) { - return; - } - - /* Put the device to sleep. */ - this->sd_card_device.PutToSleep(); - - /* Put the detector to sleep. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - this->sd_card_detector->PutToSleep(); - #endif - - /* If necessary, put the host controller to sleep. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) - #else - if (this->sd_card_device.IsActive()) - #endif - { - BaseDeviceAccessor::GetHostController()->PutToSleep(); - } - } - - void SdCardDeviceAccessor::AwakenSdCard() { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* If the device is awake, we don't need to do anything. */ - if (this->sd_card_device.IsAwake()) { - return; - } - - /* Wake the host controller, if we need to.*/ - bool force_det = false; - - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) - #else - if (this->sd_card_device.IsActive()) - #endif - { - const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); - if (R_SUCCEEDED(result)) { - force_det = R_FAILED(BaseDeviceAccessor::IssueCommandSendStatus()); - } else { - BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); - force_det = true; - } - } - - /* Wake the detector. */ - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - this->sd_card_detector->Awaken(force_det); - #else - AMS_UNUSED(force_det); - #endif - - /* Wake the device. */ - this->sd_card_device.Awaken(); - } - - Result SdCardDeviceAccessor::GetSdCardScr(void *dst, size_t dst_size) const { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->sd_card_device.CheckAccessible()); - - /* Get the SCR. */ - R_TRY(this->GetScr(dst, dst_size)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->sd_card_device.CheckAccessible()); - - /* Check whether we're specification 1 (and thus can't switch). */ - R_TRY(this->GetScr(dst, dst_size)); - R_UNLESS(!IsLessThanSpecification1_1(static_cast(dst)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); - - /* Get the status. */ - if (switch_function == SdCardSwitchFunction_CheckSupportedFunction) { - R_TRY(this->IssueCommandCheckSupportedFunction(dst, dst_size)); - } else { - SwitchFunctionAccessMode am; - switch (switch_function) { - case SdCardSwitchFunction_CheckDefault: am = SwitchFunctionAccessMode_Default; break; - case SdCardSwitchFunction_CheckHighSpeed: am = SwitchFunctionAccessMode_HighSpeed; break; - case SdCardSwitchFunction_CheckSdr50: am = SwitchFunctionAccessMode_Sdr50; break; - case SdCardSwitchFunction_CheckSdr104: am = SwitchFunctionAccessMode_Sdr104; break; - case SdCardSwitchFunction_CheckDdr50: am = SwitchFunctionAccessMode_Ddr50; break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - R_TRY(this->IssueCommandSwitchAccessMode(dst, dst_size, false, am)); - } - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->sd_card_device.CheckAccessible()); - - /* Check whether we're specification 1 (and thus can't switch). */ - R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); - R_UNLESS(!IsLessThanSpecification1_1(static_cast(this->work_buffer)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); - - /* Determine the access mode. */ - SwitchFunctionAccessMode am; - switch (speed_mode) { - case SpeedMode_SdCardSdr12: - case SpeedMode_SdCardDefaultSpeed: - am = SwitchFunctionAccessMode_Default; - break; - case SpeedMode_SdCardSdr25: - case SpeedMode_SdCardHighSpeed: - am = SwitchFunctionAccessMode_HighSpeed; - break; - case SpeedMode_SdCardSdr50: - am = SwitchFunctionAccessMode_Sdr50; - break; - case SpeedMode_SdCardSdr104: - am = SwitchFunctionAccessMode_Sdr104; - break; - case SpeedMode_SdCardDdr50: - am = SwitchFunctionAccessMode_Ddr50; - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Check that the mode is supported. */ - R_TRY(this->IssueCommandSwitchAccessMode(this->work_buffer, this->work_buffer_size, false, am)); - R_UNLESS(IsSupportedAccessMode(static_cast(this->work_buffer), am), sdmmc::ResultSdCardNotSupportAccessMode()); - - /* Get the current consumption. */ - AMS_ABORT_UNLESS(out_current_consumption != nullptr); - *out_current_consumption = GetMaximumCurrentConsumption(static_cast(this->work_buffer)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetSdCardSdStatus(void *dst, size_t dst_size) const { - /* Acquire exclusive access to the device. */ - AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); - - /* Check that we're accessible. */ - R_TRY(this->sd_card_device.CheckAccessible()); - - /* Get the status. */ - R_TRY(this->GetSdStatus(dst, dst_size)); - - return ResultSuccess(); - } - - Result SdCardDeviceAccessor::GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const { - AMS_ABORT_UNLESS(out_num_sectors != nullptr); - - /* Get the sd status. */ - R_TRY(this->GetSdCardSdStatus(this->work_buffer, this->work_buffer_size)); - const u32 size_of_protected_area = GetSizeOfProtectedArea(static_cast(this->work_buffer)); - - /* Get the csd. */ - u8 csd[DeviceCsdSize]; - this->sd_card_device.GetCsd(csd, sizeof(csd)); - - /* Handle based on csd version. */ - if (IsLessThanCsdVersion2(csd)) { - /* Get c_size_mult and read_bl_len. */ - u8 c_size_mult, read_bl_len; - this->sd_card_device.GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); - - /* Validate the parameters. */ - R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); - - /* Calculate capacity. */ - *out_num_sectors = size_of_protected_area << ((read_bl_len + c_size_mult + 2) - 9); - } else { - /* SIZE_OF_PROTECTED_AREA is in bytes. */ - *out_num_sectors = size_of_protected_area / SectorSize; - } - - return ResultSuccess(); - } - - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.hpp deleted file mode 100644 index 4f37b0c..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_card_device_accessor.hpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" - -#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) -#include "sdmmc_device_detector.hpp" -#endif - -namespace ams::sdmmc::impl { - - class SdCardDevice : public BaseDevice { - private: - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - mutable os::EventType removed_event; - #endif - u16 rca; - bool is_valid_rca; - bool is_uhs_i_mode; - public: - SdCardDevice() : rca(0) { - this->OnDeactivate(); - } - - virtual void Deactivate() override { - this->OnDeactivate(); - BaseDevice::Deactivate(); - } - - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - virtual os::EventType *GetRemovedEvent() const override { - return std::addressof(this->removed_event); - } - #elif defined(AMS_SDMMC_USE_OS_EVENTS) - virtual os::EventType *GetRemovedEvent() const override { - /* Mmc can't be removed. */ - return nullptr; - } - #endif - - virtual DeviceType GetDeviceType() const override { - return DeviceType_SdCard; - } - - virtual u16 GetRca() const override { - AMS_ABORT_UNLESS(this->is_valid_rca); - return this->rca; - } - - void OnDeactivate() { - this->is_valid_rca = false; - this->is_uhs_i_mode = false; - } - - void SetRca(u16 v) { - this->rca = v; - this->is_valid_rca = true; - } - - void SetOcrAndHighCapacity(u32 ocr); - - void SetUhsIMode(bool en) { - this->is_uhs_i_mode = en; - } - - bool IsUhsIMode() const { - return this->is_uhs_i_mode; - } - }; - - enum SdCardApplicationCommandIndex : std::underlying_type::type { - SdApplicationCommandIndex_SetBusWidth = 6, - - SdApplicationCommandIndex_SdStatus = 13, - - SdApplicationCommandIndex_SendNumWriteBlocks = 22, - SdApplicationCommandIndex_SetWriteBlockEraseCount = 23, - - SdApplicationCommandIndex_SdSendOpCond = 41, - SdApplicationCommandIndex_SetClearCardDetect = 42, - - SdApplicationCommandIndex_SendScr = 51, - }; - - enum SwitchFunctionAccessMode { - SwitchFunctionAccessMode_Default = 0, - SwitchFunctionAccessMode_HighSpeed = 1, - SwitchFunctionAccessMode_Sdr50 = 2, - SwitchFunctionAccessMode_Sdr104 = 3, - SwitchFunctionAccessMode_Ddr50 = 4, - }; - - class SdCardDeviceAccessor : public BaseDeviceAccessor { - private: - SdCardDevice sd_card_device; - void *work_buffer; - size_t work_buffer_size; - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - DeviceDetector *sd_card_detector; - #endif - BusWidth max_bus_width; - SpeedMode max_speed_mode; - bool is_initialized; - private: - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - void RemovedCallback(); - - static void RemovedCallbackEntry(void *arg) { - static_cast(arg)->RemovedCallback(); - } - #endif - - Result IssueCommandSendRelativeAddr(u16 *out_rca) const; - Result IssueCommandSendIfCond() const; - Result IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const; - Result IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const; - Result IssueCommandVoltageSwitch() const; - Result IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask = 0) const; - Result IssueCommandSetBusWidth4Bit() const; - Result IssueCommandSdStatus(void *dst, size_t dst_size) const; - Result IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const; - Result IssueCommandClearCardDetect() const; - Result IssueCommandSendScr(void *dst, size_t dst_size) const; - - Result EnterUhsIMode(); - Result ChangeToReadyState(bool spec_under_2, bool uhs_i_supported); - Result ChangeToStbyStateAndGetRca(); - Result SetMemoryCapacity(const void *csd); - Result GetScr(void *dst, size_t dst_size) const; - Result ExtendBusWidth(BusWidth max_bw, u8 sd_bw); - Result SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size); - Result ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size); - Result ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size); - Result GetSdStatus(void *dst, size_t dst_size) const; - Result StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); - - void TryDisconnectDat3PullUpResistor() const; - protected: - virtual Result OnActivate() override; - virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; - virtual Result ReStartup() override; - public: - virtual void Initialize() override; - virtual void Finalize() override; - virtual Result Activate() override; - virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; - public: - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - explicit SdCardDeviceAccessor(IHostController *hc, DeviceDetector *dd) : BaseDeviceAccessor(hc), sd_card_detector(dd) - #else - explicit SdCardDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc) - #endif - { - this->work_buffer = nullptr; - this->work_buffer_size = 0; - this->max_bus_width = BusWidth_4Bit; - this->max_speed_mode = SpeedMode_SdCardSdr104; - this->is_initialized = false; - } - - void SetSdCardWorkBuffer(void *wb, size_t wb_size) { - this->work_buffer = wb; - this->work_buffer_size = wb_size; - } - - void PutSdCardToSleep(); - void AwakenSdCard(); - Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const; - Result GetSdCardScr(void *dst, size_t dst_size) const; - Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const; - Result GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const; - Result GetSdCardSdStatus(void *dst, size_t dst_size) const; - - bool IsSdCardInserted() { - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - return this->sd_card_detector->IsInserted(); - #else - AMS_ABORT("IsSdCardInserted without SdCardDetector"); - #endif - } - - bool IsSdCardRemoved() { - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - return this->sd_card_device.IsRemoved(); - #else - AMS_ABORT("IsSdCardRemoved without SdCardDetector"); - #endif - } - - void RegisterSdCardDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - return this->sd_card_detector->RegisterDetectionEventCallback(cb, arg); - #else - AMS_UNUSED(cb, arg); - AMS_ABORT("RegisterSdCardDetectionEventCallback without SdCardDetector"); - #endif - } - - void UnregisterSdCardDetectionEventCallback() { - #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) - return this->sd_card_detector->UnregisterDetectionEventCallback(); - #else - AMS_ABORT("UnregisterSdCardDetectionEventCallback without SdCardDetector"); - #endif - } - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.cpp deleted file mode 100644 index f03d153..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.cpp +++ /dev/null @@ -1,1039 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_sd_host_standard_controller.hpp" -#include "sdmmc_timer.hpp" - -#if defined(ATMOSPHERE_IS_STRATOSPHERE) - #include -#endif - - -namespace ams::sdmmc::impl { - - namespace { - - constexpr inline u32 ControllerReactionTimeoutMilliSeconds = 2000; - constexpr inline u32 CommandTimeoutMilliSeconds = 2000; - constexpr inline u32 DefaultCheckTransferIntervalMilliSeconds = 1500; - constexpr inline u32 BusyTimeoutMilliSeconds = 2000; - - } - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - void SdHostStandardController::ResetBufferInfos() { - for (auto &info : this->buffer_infos) { - info.buffer_address = 0; - info.buffer_size = 0; - } - } - - dd::DeviceVirtualAddress SdHostStandardController::GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size) { - /* Try to find the buffer in our registered regions. */ - dd::DeviceVirtualAddress device_addr = 0; - for (const auto &info : this->buffer_infos) { - if (info.buffer_address <= buffer && (buffer + buffer_size) <= (info.buffer_address + info.buffer_size)) { - device_addr = info.buffer_device_virtual_address + (buffer - info.buffer_address); - break; - } - } - - /* Ensure that we found the buffer. */ - AMS_ABORT_UNLESS(device_addr != 0); - return device_addr; - } - #endif - - void SdHostStandardController::EnsureControl() { - /* Perform a read of clock control to be sure previous configuration takes. */ - reg::Read(this->registers->clock_control); - } - - Result SdHostStandardController::EnableInternalClock() { - /* Enable internal clock. */ - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, OSCILLATE)); - this->EnsureControl(); - - /* Wait for the internal clock to become stable. */ - { - ManualTimer timer(ControllerReactionTimeoutMilliSeconds); - while (true) { - /* Check if the clock is steady. */ - if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, READY))) { - break; - } - - /* If not, check for timeout. */ - R_UNLESS(timer.Update(), sdmmc::ResultInternalClockStableSoftwareTimeout()); - } - } - - /* Configure to use host controlled divided clock. */ - reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER)); - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK)); - - /* Set host version 4.0.0 enable. */ - reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, VERSION_4)); - - /* Set host 64 bit addressing enable. */ - AMS_ABORT_UNLESS(reg::HasValue(this->registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED))); - reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING)); - - /* Select SDMA mode. */ - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DMA_SELECT, SDMA)); - - /* Configure timeout control to use the maximum timeout value (TMCLK * 2^27) */ - reg::ReadWrite(this->registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110)); - - return ResultSuccess(); - } - - void SdHostStandardController::SetBusPower(BusPower bus_power) { - /* Check that we support the bus power. */ - AMS_ABORT_UNLESS(this->IsSupportedBusPower(bus_power)); - - /* Set the appropriate power. */ - switch (bus_power) { - case BusPower_Off: - reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF)); - break; - case BusPower_1_8V: - reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V)); - reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); - break; - case BusPower_3_3V: - reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V)); - reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void SdHostStandardController::EnableInterruptStatus() { - /* Set the status register interrupt enables. */ - reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); - reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); - - /* Read/write the interrupt enables to be sure they take. */ - reg::Write(this->registers->normal_int_enable, reg::Read(this->registers->normal_int_enable)); - reg::Write(this->registers->error_int_enable, reg::Read(this->registers->error_int_enable)); - - /* If we're using interrupt events, configure appropriately. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - /* Clear interrupts. */ - this->ClearInterrupt(); - - /* Enable the interrupt signals. */ - reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); - reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); - } - #endif - } - - void SdHostStandardController::DisableInterruptStatus() { - /* If we're using interrupt events, configure appropriately. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - /* Disable the interrupt signals. */ - reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); - reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); - } - #endif - - /* Mask the status register interrupt enables. */ - reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); - reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); - } - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - Result SdHostStandardController::WaitInterrupt(u32 timeout_ms) { - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Wait for the interrupt to be signaled. */ - os::WaitableHolderType *signaled_holder = os::TimedWaitAny(std::addressof(this->waitable_manager), TimeSpan::FromMilliSeconds(timeout_ms)); - if (signaled_holder == std::addressof(this->interrupt_event_holder)) { - /* We received the interrupt. */ - return ResultSuccess(); - } else if (signaled_holder == std::addressof(this->removed_event_holder)) { - /* The device was removed. */ - return sdmmc::ResultDeviceRemoved(); - } else { - /* Timeout occurred. */ - return sdmmc::ResultWaitInterruptSoftwareTimeout(); - } - } - - void SdHostStandardController::ClearInterrupt() { - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Clear the interrupt event. */ - os::ClearInterruptEvent(this->interrupt_event); - } - #endif - - void SdHostStandardController::SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data) { - /* Ensure the transfer data is valid. */ - AMS_ABORT_UNLESS(xfer_data->block_size != 0); - AMS_ABORT_UNLESS(xfer_data->num_blocks != 0); - - /* Determine the number of blocks. */ - const u16 num_blocks = std::min(xfer_data->num_blocks, SdHostStandardRegisters::BlockCountMax); - - /* Determine the address/how many blocks to transfer. */ - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - const u64 address = this->GetDeviceVirtualAddress(reinterpret_cast(xfer_data->buffer), xfer_data->block_size * num_blocks); - const u16 num_xfer_blocks = num_blocks; - #else - const u64 address = reinterpret_cast(xfer_data->buffer); - const u16 num_xfer_blocks = num_blocks; - #endif - - /* Verify the address is usable. */ - AMS_ABORT_UNLESS(util::IsAligned(address, BufferDeviceVirtualAddressAlignment)); - - /* Configure for sdma. */ - reg::Write(this->registers->adma_address, static_cast(address >> 0)); - reg::Write(this->registers->upper_adma_address, static_cast(address >> BITSIZEOF(u32))); - - /* Set our next sdma address. */ - this->next_sdma_address = util::AlignDown(address + SdmaBufferBoundary, SdmaBufferBoundary); - - /* Configure block size. */ - AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); - reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 512_KB), - SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast(xfer_data->block_size))); - - /* Configure transfer blocks. */ - reg::Write(this->registers->block_count, num_xfer_blocks); - if (out_num_transferred_blocks != nullptr) { - *out_num_transferred_blocks = num_xfer_blocks; - } - - /* Configure transfer mode. */ - reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM (TRANSFER_MODE_DMA_ENABLE, ENABLE), - SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_BLOCK_COUNT_ENABLE, (xfer_data->is_multi_block_transfer), ENABLE, DISABLE), - SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_MULTI_BLOCK_SELECT, (xfer_data->is_multi_block_transfer), MULTI_BLOCK, SINGLE_BLOCK), - SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, (xfer_data->transfer_direction == TransferDirection_ReadFromDevice), READ, WRITE), - SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE)); - } - - void SdHostStandardController::SetTransferForTuning() { - /* Get the tuning block size. */ - u16 tuning_block_size; - switch (this->GetBusWidth()) { - case BusWidth_4Bit: - tuning_block_size = 64; - break; - case BusWidth_8Bit: - tuning_block_size = 128; - break; - case BusWidth_1Bit: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Configure block size. */ - AMS_ABORT_UNLESS(tuning_block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); - reg::Write(this->registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size)); - - /* Configure transfer blocks. */ - reg::Write(this->registers->block_count, 1); - - /* Configure transfer mode. */ - reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, READ)); - } - - void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) { - /* Encode the command value. */ - u16 command_val = 0; - - /* Encode the response type. */ - switch (command->response_type) { - case ResponseType_R0: - command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, NO_RESPONSE), - SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), - SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); - break; - case ResponseType_R1: - case ResponseType_R6: - case ResponseType_R7: - command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_RESPONSE_TYPE, command->is_busy, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE, RESPONSE_LENGTH_48), - SD_REG_BITS_ENUM (COMMAND_CRC_CHECK, ENABLE), - SD_REG_BITS_ENUM (COMMAND_INDEX_CHECK, ENABLE)); - break; - case ResponseType_R2: - command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_136), - SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, ENABLE), - SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); - break; - case ResponseType_R3: - command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_48), - SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), - SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Encode the data present select. */ - command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_DATA_PRESENT, has_xfer_data, DATA_PRESENT, NO_DATA_PRESENT)); - - /* Encode the command index. */ - AMS_ABORT_UNLESS(command->command_index <= SdHostStandardCommandIndexMax); - command_val |= reg::Encode(SD_REG_BITS_VALUE(COMMAND_COMMAND_INDEX, command->command_index)); - - /* Write the command and argument. */ - reg::Write(this->registers->argument, command->command_argument); - reg::Write(this->registers->command, command_val); - } - - void SdHostStandardController::SetCommandForTuning(u32 command_index) { - Command command(command_index, 0, ResponseType_R1, false); - return this->SetCommand(std::addressof(command), true); - } - - Result SdHostStandardController::ResetCmdDatLine() { - /* Set the software reset cmd/dat bits. */ - reg::ReadWrite(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET), - SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, RESET)); - - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Wait until reset is done. */ - { - ManualTimer timer(ControllerReactionTimeoutMilliSeconds); - while (true) { - /* Check if the target has been removed. */ - R_TRY(this->CheckRemoved()); - - /* Check if command inhibit is no longer present. */ - if (reg::HasValue(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, WORK), - SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, WORK))) - { - break; - } - - /* Otherwise, check if we've timed out. */ - if (!timer.Update()) { - return sdmmc::ResultAbortTransactionSoftwareTimeout(); - } - } - } - - return ResultSuccess(); - } - - Result SdHostStandardController::AbortTransaction() { - R_TRY(this->ResetCmdDatLine()); - return ResultSuccess(); - } - - Result SdHostStandardController::WaitWhileCommandInhibit(bool has_dat) { - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Wait while command inhibit cmd is set. */ - { - ManualTimer timer(ControllerReactionTimeoutMilliSeconds); - while (true) { - /* Check if the target has been removed. */ - R_TRY(this->CheckRemoved()); - - /* Check if command inhibit is no longer present. */ - if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, READY))) { - break; - } - - /* Otherwise, check if we've timed out. */ - if (!timer.Update()) { - this->AbortTransaction(); - return sdmmc::ResultCommandInhibitCmdSoftwareTimeout(); - } - } - } - - /* Wait while command inhibit dat is set. */ - if (has_dat) { - ManualTimer timer(ControllerReactionTimeoutMilliSeconds); - while (true) { - /* Check if the target has been removed. */ - R_TRY(this->CheckRemoved()); - - /* Check if command inhibit is no longer present. */ - if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, READY))) { - break; - } - - /* Otherwise, check if we've timed out. */ - if (!timer.Update()) { - this->AbortTransaction(); - return sdmmc::ResultCommandInhibitDatSoftwareTimeout(); - } - } - } - - return ResultSuccess(); - } - - Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) { - /* Read the statuses. */ - volatile u16 normal_int_status = reg::Read(this->registers->normal_int_status); - volatile u16 error_int_status = reg::Read(this->registers->error_int_status); - volatile u16 auto_cmd_err_status = reg::Read(this->registers->acmd12_err); - - /* Set the output status, if necessary. */ - if (out_normal_int_status != nullptr) { - *out_normal_int_status = normal_int_status; - } - - /* If we don't have an error interrupt, just use the normal status. */ - if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, NO_ERROR))) { - /* If the wait mask has any bits set, we're done waiting for the interrupt. */ - const u16 masked_status = (normal_int_status & wait_mask); - R_UNLESS(masked_status != 0, sdmmc::ResultNoWaitedInterrupt()); - - /* Write the masked value to the status register to ensure consistent state. */ - reg::Write(this->registers->normal_int_status, masked_status); - return ResultSuccess(); - } - - /* We have an error interrupt. Write the status to the register to ensure consistent state. */ - reg::Write(this->registers->error_int_status, error_int_status); - - /* Check the error interrupt status bits, and return appropriate errors. */ - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, NO_ERROR)), sdmmc::ResultResponseIndexError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, NO_ERROR)), sdmmc::ResultResponseEndBitError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, NO_ERROR)), sdmmc::ResultResponseCrcError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, NO_ERROR)), sdmmc::ResultResponseTimeoutError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, NO_ERROR)), sdmmc::ResultDataEndBitError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, NO_ERROR)), sdmmc::ResultDataCrcError()); - R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, NO_ERROR)), sdmmc::ResultDataTimeoutError()); - - /* Check for auto cmd errors. */ - if (reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, ERROR))) { - R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, NO_ERROR)), sdmmc::ResultAutoCommandResponseIndexError()); - R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, NO_ERROR)), sdmmc::ResultAutoCommandResponseEndBitError()); - R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, NO_ERROR)), sdmmc::ResultAutoCommandResponseCrcError()); - R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, NO_ERROR)), sdmmc::ResultAutoCommandResponseTimeoutError()); - - /* An known auto cmd error occurred. */ - return sdmmc::ResultSdHostStandardUnknownAutoCmdError(); - } else { - /* Unknown error occurred. */ - return sdmmc::ResultSdHostStandardUnknownError(); - } - } - - Result SdHostStandardController::WaitCommandComplete() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - /* Wait for interrupt. */ - Result result = this->WaitInterrupt(CommandTimeoutMilliSeconds); - if (R_SUCCEEDED(result)) { - /* If we succeeded, check/clear our interrupt status. */ - result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); - this->ClearInterrupt(); - - if (R_FAILED(result)) { - this->AbortTransaction(); - } - - return result; - } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { - /* Otherwise, check if the device was removed. */ - return result; - } else { - /* If the device wasn't removed, cancel our transaction. */ - this->AbortTransaction(); - return sdmmc::ResultCommandCompleteSoftwareTimeout(); - } - } - #else - { - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Wait while command is not complete. */ - { - ManualTimer timer(CommandTimeoutMilliSeconds); - while (true) { - /* Check and clear the interrupt status. */ - const auto result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); - - /* If we succeeded, we're done. */ - if (R_SUCCEEDED(result)) { - return ResultSuccess(); - } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { - /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ - if (!timer.Update()) { - this->AbortTransaction(); - return sdmmc::ResultCommandCompleteSoftwareTimeout(); - } - } else { - /* Otherwise, we have a generic failure. */ - this->AbortTransaction(); - return result; - } - } - } - } - #endif - } - - Result SdHostStandardController::WaitTransferComplete() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - /* Wait while transfer is not complete. */ - while (true) { - /* Get the last block count. */ - const u16 last_block_count = reg::Read(this->registers->block_count); - - /* Wait for interrupt. */ - Result result = this->WaitInterrupt(this->check_transfer_interval_ms); - if (R_SUCCEEDED(result)) { - /* If we succeeded, check/clear our interrupt status. */ - volatile u16 normal_int_status; - result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), - SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); - - this->ClearInterrupt(); - - /* If the interrupt succeeded, check status. */ - if (R_SUCCEEDED(result)) { - /* If the transfer is complete, we're done. */ - if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { - return ResultSuccess(); - } - - /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ - if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { - reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); - reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); - - this->next_sdma_address += SdmaBufferBoundary; - } - } else { - /* Abort the transaction. */ - this->AbortTransaction(); - return result; - } - - return result; - } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { - /* Otherwise, check if the device was removed. */ - return result; - } else { - /* Otherwise, timeout if the transfer hasn't advanced. */ - if (last_block_count != reg::Read(this->registers->block_count)) { - this->AbortTransaction(); - return sdmmc::ResultTransferCompleteSoftwareTimeout(); - } - } - } - } - #else - { - /* Wait while transfer is not complete. */ - while (true) { - /* Get the last block count. */ - const u16 last_block_count = reg::Read(this->registers->block_count); - - /* Wait until transfer times out. */ - { - ManualTimer timer(this->check_transfer_interval_ms); - while (true) { - /* Check/clear our interrupt status. */ - volatile u16 normal_int_status; - const auto result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), - SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); - - /* If the check succeeded, check status. */ - if (R_SUCCEEDED(result)) { - /* If the transfer is complete, we're done. */ - if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { - return ResultSuccess(); - } - - /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ - if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { - reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); - reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); - - this->next_sdma_address += SdmaBufferBoundary; - } - } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { - /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ - if (!timer.Update()) { - /* Only timeout if the transfer hasn't advanced. */ - if (last_block_count != reg::Read(this->registers->block_count)) { - this->AbortTransaction(); - return sdmmc::ResultTransferCompleteSoftwareTimeout(); - } - break; - } - } else { - /* Otherwise, we have a generic failure. */ - this->AbortTransaction(); - return result; - } - } - } - } - } - #endif - } - - Result SdHostStandardController::WaitWhileBusy() { - /* Ensure that we control the registers. */ - this->EnsureControl(); - - /* Wait while busy. */ - { - ManualTimer timer(BusyTimeoutMilliSeconds); - while (true) { - /* Check if the target has been removed. */ - R_TRY(this->CheckRemoved()); - - /* If the DAT0 line signal is level high, we're done. */ - if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) { - return ResultSuccess(); - } - - /* Otherwise, check if we're timed out. */ - if (!timer.Update()) { - this->AbortTransaction(); - return sdmmc::ResultBusySoftwareTimeout(); - } - } - } - } - - void SdHostStandardController::GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { - /* Check that we can write the response. */ - AMS_ABORT_UNLESS(out_response != nullptr); - - /* Get the response appropriately. */ - switch (response_type) { - case ResponseType_R1: - case ResponseType_R3: - case ResponseType_R6: - case ResponseType_R7: - /* 32-bit response. */ - AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); - out_response[0] = reg::Read(this->registers->response[0]); - break; - case ResponseType_R2: - /* 128-bit response. */ - AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); - out_response[0] = reg::Read(this->registers->response[0]); - out_response[1] = reg::Read(this->registers->response[1]); - out_response[2] = reg::Read(this->registers->response[2]); - out_response[3] = reg::Read(this->registers->response[3]); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - Result SdHostStandardController::IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { - /* Wait until we can issue the command. */ - R_TRY(this->WaitWhileCommandInhibit((xfer_data != nullptr) || command->is_busy)); - - /* Configure for the transfer. */ - u32 num_transferred_blocks = 0; - if (xfer_data != nullptr) { - /* Setup the transfer, and get the number of blocks. */ - this->SetTransfer(std::addressof(num_transferred_blocks), xfer_data); - - /* Ensure the device sees consistent data with the cpu. */ - dd::FlushDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); - } - - /* Issue the command with interrupt status enabled. */ - { - this->EnableInterruptStatus(); - ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; - - /* Set the command. */ - this->SetCommand(command, xfer_data != nullptr); - - /* Wait for the command to complete. */ - R_TRY(this->WaitCommandComplete()); - - /* Process any response. */ - if (command->response_type != ResponseType_R0) { - this->last_response_type = command->response_type; - this->GetResponse(this->last_response, sizeof(this->last_response), this->last_response_type); - } - - /* Wait for data to be transferred. */ - if (xfer_data != nullptr) { - R_TRY(this->WaitTransferComplete()); - } - } - - /* If data was transferred, ensure we're in a consistent state. */ - if (xfer_data != nullptr) { - /* Ensure the cpu sees consistent data with the device. */ - if (xfer_data->transfer_direction == TransferDirection_ReadFromDevice) { - dd::InvalidateDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); - } - - /* Set the number of transferred blocks. */ - if (out_num_transferred_blocks != nullptr) { - *out_num_transferred_blocks = num_transferred_blocks; - } - - /* Process stop transition command. */ - this->last_stop_transmission_response = this->registers->response[3]; - } - - /* Wait until we're no longer busy. */ - if (command->is_busy || (xfer_data != nullptr)) { - R_TRY(this->WaitWhileBusy()); - } - - return ResultSuccess(); - } - - Result SdHostStandardController::IssueStopTransmissionCommandWithDeviceClock(u32 *out_response) { - /* Wait until we can issue the command. */ - R_TRY(this->WaitWhileCommandInhibit(false)); - - /* Issue the command with interrupt status enabled. */ - constexpr ResponseType StopTransmissionCommandResponseType = ResponseType_R1; - { - this->EnableInterruptStatus(); - ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; - - /* Set the command. */ - Command command(CommandIndex_StopTransmission, 0, StopTransmissionCommandResponseType, true); - this->SetCommand(std::addressof(command), false); - - /* Wait for the command to complete. */ - R_TRY(this->WaitCommandComplete()); - } - - /* Process response. */ - this->GetResponse(out_response, sizeof(u32), StopTransmissionCommandResponseType); - - /* Wait until we're done. */ - R_TRY(this->WaitWhileBusy()); - - return ResultSuccess(); - } - - SdHostStandardController::SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size) { - /* Translate the physical address to a address. */ - const uintptr_t registers_addr = dd::QueryIoMapping(registers_phys_addr, registers_size); - - /* Set registers. */ - AMS_ABORT_UNLESS(registers_addr != 0); - this->registers = reinterpret_cast(registers_addr); - - /* Reset DMA buffers, if we have any. */ - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - this->ResetBufferInfos(); - #endif - - /* Clear removed event, if we have one. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - this->removed_event = nullptr; - #endif - - /* Clear dma address. */ - this->next_sdma_address = 0; - this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; - - /* Clear clock/power trackers. */ - this->device_clock_frequency_khz = 0; - this->is_power_saving_enable = false; - this->is_device_clock_enable = false; - - /* Clear last response. */ - this->last_response_type = ResponseType_R0; - std::memset(this->last_response, 0, sizeof(this->last_response)); - this->last_stop_transmission_response = 0; - } - - void SdHostStandardController::Initialize() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - os::InitializeWaitableManager(std::addressof(this->waitable_manager)); - - AMS_ABORT_UNLESS(this->interrupt_event != nullptr); - os::InitializeWaitableHolder(std::addressof(this->interrupt_event_holder), this->interrupt_event); - os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->interrupt_event_holder)); - - if (this->removed_event != nullptr) { - os::InitializeWaitableHolder(std::addressof(this->removed_event_holder), this->removed_event); - os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->removed_event_holder)); - } - } - #endif - } - - void SdHostStandardController::Finalize() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - if (this->removed_event != nullptr) { - os::UnlinkWaitableHolder(std::addressof(this->removed_event_holder)); - os::FinalizeWaitableHolder(std::addressof(this->removed_event_holder)); - } - - os::UnlinkWaitableHolder(std::addressof(this->interrupt_event_holder)); - os::FinalizeWaitableHolder(std::addressof(this->interrupt_event_holder)); - - os::FinalizeWaitableManager(std::addressof(this->waitable_manager)); - } - #endif - } - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - void SdHostStandardController::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - /* Find and set a free info. */ - for (auto &info : this->buffer_infos) { - if (info.buffer_address == 0) { - info = { - .buffer_address = buffer, - .buffer_size = buffer_size, - .buffer_device_virtual_address = buffer_device_virtual_address, - }; - return; - } - } - - AMS_ABORT("Out of BufferInfos\n"); - } - - void SdHostStandardController::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - /* Find and clear the buffer info. */ - for (auto &info : this->buffer_infos) { - if (info.buffer_address == 0) { - AMS_ABORT_UNLESS(info.buffer_size == buffer_size); - AMS_ABORT_UNLESS(info.buffer_device_virtual_address == buffer_device_virtual_address); - - info.buffer_address = 0; - info.buffer_size = 0; - return; - } - } - - AMS_ABORT("BufferInfo not found\n"); - } - #endif - - void SdHostStandardController::SetWorkBuffer(void *wb, size_t wb_size) { - AMS_UNUSED(wb, wb_size); - AMS_ABORT("WorkBuffer is not needed\n"); - } - - BusPower SdHostStandardController::GetBusPower() const { - /* Check if the bus has power. */ - if (reg::HasValue(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON))) { - /* If it does, return the corresponding power. */ - switch (reg::GetValue(this->registers->power_control, SD_REG_BITS_MASK(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1))) { - case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_1_8V: - return BusPower_1_8V; - case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_3_3V: - return BusPower_3_3V; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } else { - /* It doesn't, so it's off. */ - return BusPower_Off; - } - } - - void SdHostStandardController::SetBusWidth(BusWidth bus_width) { - /* Check that we support the bus width. */ - AMS_ABORT_UNLESS(this->IsSupportedBusWidth(bus_width)); - - /* Set the appropriate data transfer width. */ - switch (bus_width) { - case BusWidth_1Bit: - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT)); - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); - break; - case BusWidth_4Bit: - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT)); - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); - break; - case BusWidth_8Bit: - reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - BusWidth SdHostStandardController::GetBusWidth() const { - /* Check if the bus is using eight-bit extended data transfer. */ - if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT))) { - return BusWidth_8Bit; - } else { - /* Bus is configured as USE_DATA_TRANSFER_WIDTH, so check if it's four bit. */ - if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT))) { - return BusWidth_4Bit; - } else { - return BusWidth_1Bit; - } - } - } - - void SdHostStandardController::SetPowerSaving(bool en) { - /* Set whether we're power saving enable. */ - this->is_power_saving_enable = en; - - /* Configure accordingly. */ - if (this->is_power_saving_enable) { - /* We want to disable SD clock if it's enabled. */ - if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) { - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - } - } else { - /* We want to enable SD clock if it's disabled and we're supposed to enable device clock. */ - if (this->is_device_clock_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - } - } - } - - void SdHostStandardController::EnableDeviceClock() { - /* If we're not in power-saving mode and the device clock is disabled, enable it. */ - if (!this->is_power_saving_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - } - this->is_device_clock_enable = true; - } - - void SdHostStandardController::DisableDeviceClock() { - /* Unconditionally disable the device clock. */ - this->is_device_clock_enable = false; - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - } - - void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) { - this->check_transfer_interval_ms = ms; - } - - void SdHostStandardController::SetDefaultCheckTransferInterval() { - this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; - } - - Result SdHostStandardController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { - /* We need to have device clock enabled to issue commands. */ - AMS_ABORT_UNLESS(this->is_device_clock_enable); - - /* Check if we need to temporarily re-enable the device clock. */ - const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - - /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ - if (clock_disabled) { - /* Turn on the clock. */ - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - - /* Ensure that our configuration takes. */ - this->EnsureControl(); - - /* Wait 8 device clocks to be sure that it's usable. */ - WaitClocks(8, this->device_clock_frequency_khz); - } - ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; - - /* Issue the command. */ - { - /* After we issue the command, we need to wait 8 device clocks. */ - ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; - - return this->IssueCommandWithDeviceClock(command, xfer_data, out_num_transferred_blocks); - } - } - - Result SdHostStandardController::IssueStopTransmissionCommand(u32 *out_response) { - /* We need to have device clock enabled to issue commands. */ - AMS_ABORT_UNLESS(this->is_device_clock_enable); - - /* Check if we need to temporarily re-enable the device clock. */ - const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - - /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ - if (clock_disabled) { - /* Turn on the clock. */ - reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - - /* Ensure that our configuration takes. */ - this->EnsureControl(); - - /* Wait 8 device clocks to be sure that it's usable. */ - WaitClocks(8, this->device_clock_frequency_khz); - } - ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; - - /* Issue the command. */ - { - /* After we issue the command, we need to wait 8 device clocks. */ - ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; - - return this->IssueStopTransmissionCommandWithDeviceClock(out_response); - } - } - - void SdHostStandardController::GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { - /* Check that we can get the response. */ - AMS_ABORT_UNLESS(out_response != nullptr); - AMS_ABORT_UNLESS(response_type == this->last_response_type); - - /* Get the response appropriately. */ - switch (response_type) { - case ResponseType_R1: - case ResponseType_R3: - case ResponseType_R6: - case ResponseType_R7: - /* 32-bit response. */ - AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); - out_response[0] = this->last_response[0]; - break; - case ResponseType_R2: - /* 128-bit response. */ - AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); - out_response[0] = this->last_response[0]; - out_response[1] = this->last_response[1]; - out_response[2] = this->last_response[2]; - out_response[3] = this->last_response[3]; - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - void SdHostStandardController::GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const { - /* Check that we can get the response. */ - AMS_ABORT_UNLESS(out_response != nullptr); - AMS_ABORT_UNLESS(response_size >= sizeof(u32)); - - /* Get the response. */ - out_response[0] = this->last_stop_transmission_response; - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.hpp deleted file mode 100644 index 7bdf0a3..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_controller.hpp +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" -#include "sdmmc_sd_host_standard_registers.hpp" - -namespace ams::sdmmc::impl { - - class SdHostStandardController : public IHostController { - protected: - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - struct BufferInfo { - uintptr_t buffer_address; - size_t buffer_size; - dd::DeviceVirtualAddress buffer_device_virtual_address; - }; - - static constexpr inline auto NumBufferInfos = 3; - #endif - protected: - SdHostStandardRegisters *registers; - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - BufferInfo buffer_infos[NumBufferInfos]; - #endif - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - os::WaitableManagerType waitable_manager; - os::InterruptEventType *interrupt_event; - os::WaitableHolderType interrupt_event_holder; - os::EventType *removed_event; - os::WaitableHolderType removed_event_holder; - #endif - - u64 next_sdma_address; - u32 check_transfer_interval_ms; - - u32 device_clock_frequency_khz; - bool is_power_saving_enable; - bool is_device_clock_enable; - - ResponseType last_response_type; - u32 last_response[4]; - u32 last_stop_transmission_response; - protected: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - void PreSetInterruptEvent(os::InterruptEventType *ie) { - this->interrupt_event = ie; - } - - bool IsRemoved() const { - return this->removed_event != nullptr && os::TryWaitEvent(this->removed_event); - } - #endif - - void SetDeviceClockFrequencyKHz(u32 khz) { - this->device_clock_frequency_khz = khz; - } - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - void ResetBufferInfos(); - dd::DeviceVirtualAddress GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size); - #endif - - void EnsureControl(); - Result EnableInternalClock(); - void SetBusPower(BusPower bus_power); - - void EnableInterruptStatus(); - void DisableInterruptStatus(); - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - Result WaitInterrupt(u32 timeout_ms); - void ClearInterrupt(); - #endif - - void SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data); - void SetTransferForTuning(); - - void SetCommand(const Command *command, bool has_xfer_data); - void SetCommandForTuning(u32 command_index); - - Result ResetCmdDatLine(); - Result AbortTransaction(); - Result WaitWhileCommandInhibit(bool has_dat); - Result CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask); - Result WaitCommandComplete(); - Result WaitTransferComplete(); - Result WaitWhileBusy(); - - void GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const; - - Result IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks); - Result IssueStopTransmissionCommandWithDeviceClock(u32 *out_response); - - ALWAYS_INLINE Result CheckRemoved() { - #if defined(AMS_SDMMC_USE_OS_EVENTS) - R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); - #endif - - return ResultSuccess(); - } - public: - SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size); - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual void PreSetRemovedEvent(os::EventType *e) override { - this->removed_event = e; - } - #endif - - virtual void Initialize() override; - virtual void Finalize() override; - - #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; - virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; - #endif - - virtual void SetWorkBuffer(void *wb, size_t wb_size) override; - - virtual Result SwitchToSdr12() override { - AMS_ABORT("SwitchToSdr12 not supported\n"); - } - - virtual BusPower GetBusPower() const override; - - virtual void SetBusWidth(BusWidth bus_width) override; - virtual BusWidth GetBusWidth() const override; - - virtual u32 GetDeviceClockFrequencyKHz() const override { - return this->device_clock_frequency_khz; - } - - virtual void SetPowerSaving(bool en) override; - virtual bool IsPowerSavingEnable() const override { - return this->is_power_saving_enable; - } - - virtual void EnableDeviceClock() override; - virtual void DisableDeviceClock() override; - virtual bool IsDeviceClockEnable() const override { - return this->is_device_clock_enable; - } - - virtual u32 GetMaxTransferNumBlocks() const override { - return SdHostStandardRegisters::BlockCountMax; - } - - virtual void ChangeCheckTransferInterval(u32 ms) override; - virtual void SetDefaultCheckTransferInterval() override; - - virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; - virtual Result IssueStopTransmissionCommand(u32 *out_response) override; - - virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const override; - virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const override; - - virtual bool IsSupportedTuning() const override { - return false; - } - - virtual Result Tuning(SpeedMode speed_mode, u32 command_index) { - AMS_UNUSED(speed_mode, command_index); - AMS_ABORT("Tuning not supported\n"); - } - - virtual void SaveTuningStatusForHs400() { - AMS_ABORT("SaveTuningStatusForHs400 not supported\n"); - } - - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_registers.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_registers.hpp deleted file mode 100644 index e104374..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sd_host_standard_registers.hpp +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -namespace ams::sdmmc::impl { - - struct SdHostStandardRegisters { - volatile uint32_t dma_address; - volatile uint16_t block_size; - volatile uint16_t block_count; - volatile uint32_t argument; - volatile uint16_t transfer_mode; - volatile uint16_t command; - volatile uint32_t response[0x4]; - volatile uint32_t buffer; - volatile uint32_t present_state; - volatile uint8_t host_control; - volatile uint8_t power_control; - volatile uint8_t block_gap_control; - volatile uint8_t wake_up_control; - volatile uint16_t clock_control; - volatile uint8_t timeout_control; - volatile uint8_t software_reset; - volatile uint16_t normal_int_status; - volatile uint16_t error_int_status; - volatile uint16_t normal_int_enable; - volatile uint16_t error_int_enable; - volatile uint16_t normal_signal_enable; - volatile uint16_t error_signal_enable; - volatile uint16_t acmd12_err; - volatile uint16_t host_control2; - volatile uint32_t capabilities; - volatile uint32_t capabilities_1; - volatile uint32_t max_current; - volatile uint32_t _0x4c; - volatile uint16_t set_acmd12_error; - volatile uint16_t set_int_error; - volatile uint8_t adma_error; - volatile uint8_t _0x56[0x3]; - volatile uint32_t adma_address; - volatile uint32_t upper_adma_address; - volatile uint16_t preset_for_init; - volatile uint16_t preset_for_default; - volatile uint16_t preset_for_high; - volatile uint16_t preset_for_sdr12; - volatile uint16_t preset_for_sdr25; - volatile uint16_t preset_for_sdr50; - volatile uint16_t preset_for_sdr104; - volatile uint16_t preset_for_ddr50; - volatile uint32_t _0x70[0x23]; - volatile uint16_t slot_int_status; - volatile uint16_t host_version; - - static constexpr inline u16 BlockCountMax = 0xFFFF; - }; - static_assert(sizeof(SdHostStandardRegisters) == 0x100); - - constexpr inline size_t SdmaBufferBoundary = 512_KB; - - #define SD_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SD_HOST_STANDARD, NAME) - #define SD_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SD_HOST_STANDARD, NAME, VALUE) - #define SD_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SD_HOST_STANDARD, NAME, ENUM) - #define SD_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SD_HOST_STANDARD, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) - - #define DEFINE_SD_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SD_HOST_STANDARD, NAME, __OFFSET__, __WIDTH__) - #define DEFINE_SD_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE) - #define DEFINE_SD_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) - #define DEFINE_SD_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) - #define DEFINE_SD_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) - - DEFINE_SD_REG(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, 0, 12); - DEFINE_SD_REG_THREE_BIT_ENUM(BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 12, 4_KB, 8_KB, 16_KB, 32_KB, 64_KB, 128_KB, 256_KB, 512_KB); - constexpr inline size_t SdHostStandardBlockSizeTransferBlockSizeMax = 0xFFF; - - DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DMA_ENABLE, 0, DISABLE, ENABLE); - DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_BLOCK_COUNT_ENABLE, 1, DISABLE, ENABLE); - DEFINE_SD_REG_TWO_BIT_ENUM(TRANSFER_MODE_AUTO_CMD_ENABLE, 2, DISABLE, CMD12_ENABLE, CMD23_ENABLE, AUTO_SELECT); - DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, 4, WRITE, READ); - DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_MULTI_BLOCK_SELECT, 5, SINGLE_BLOCK, MULTI_BLOCK); - - DEFINE_SD_REG_TWO_BIT_ENUM(COMMAND_RESPONSE_TYPE, 0, NO_RESPONSE, RESPONSE_LENGTH_136, RESPONSE_LENGTH_48, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE); - DEFINE_SD_REG_BIT_ENUM(COMMAND_CRC_CHECK, 3, DISABLE, ENABLE); - DEFINE_SD_REG_BIT_ENUM(COMMAND_INDEX_CHECK, 4, DISABLE, ENABLE); - DEFINE_SD_REG_BIT_ENUM(COMMAND_DATA_PRESENT, 5, NO_DATA_PRESENT, DATA_PRESENT); - DEFINE_SD_REG(COMMAND_COMMAND_INDEX, 8, 6); - constexpr inline size_t SdHostStandardCommandIndexMax = 0x3F; - - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, 0, READY, NOT_READY); - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, 1, READY, NOT_READY); - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, 20, LOW, HIGH); - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT1_LINE_SIGNAL_LEVEL, 21, LOW, HIGH); - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT2_LINE_SIGNAL_LEVEL, 22, LOW, HIGH); - DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT3_LINE_SIGNAL_LEVEL, 23, LOW, HIGH); - DEFINE_SD_REG(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 20, 4); - - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, 1, ONE_BIT, FOUR_BIT); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, 2, NORMAL_SPEED, HIGH_SPEED); - DEFINE_SD_REG_TWO_BIT_ENUM(HOST_CONTROL_DMA_SELECT, 3, SDMA, RESERVED1, ADMA2, ADMA2_OR_ADMA3); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, 5, USE_DATA_TRANSFER_WIDTH, EIGHT_BIT); - - DEFINE_SD_REG_BIT_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, 0, OFF, ON); - DEFINE_SD_REG_THREE_BIT_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1, RESERVED0, RESERVED1, RESERVED2, RESERVED3, RESERVED4, 1_8V, 3_0V, 3_3V); - - DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, 0, STOP, OSCILLATE); - DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, 1, NOT_READY, READY); - DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, 2, DISABLE, ENABLE); - DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, 5, DIVIDED_CLOCK, PROGRAMMABLE_CLOCK); - DEFINE_SD_REG(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, 6, 2); - DEFINE_SD_REG(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, 8, 8); - - DEFINE_SD_REG(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0, 4); - - DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_ALL, 0, WORK, RESET); - DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_CMD, 1, WORK, RESET); - DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_DAT, 2, WORK, RESET); - - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, 0, NOT_COMPLETE, COMPLETE); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, 1, NOT_COMPLETE, COMPLETE); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, 3, NOT_GENERATED, GENERATED); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, 15, NO_ERROR, ERROR); - - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, 0, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, 1, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, 2, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, 3, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, 4, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, 5, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, 6, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, 8, NO_ERROR, ERROR); - - DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, 1, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, 2, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, 3, NO_ERROR, ERROR); - DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, 4, NO_ERROR, ERROR); - - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, 0, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, 1, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, 3, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, 5, MASKED, ENABLED); - - #define SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ - SD_REG_BITS_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, __ENUM__), \ - SD_REG_BITS_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, __ENUM__), \ - SD_REG_BITS_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, __ENUM__) - - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, 1, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, 5, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, 6, MASKED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, 8, MASKED, ENABLED); - - #define SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, __ENUM__), \ - SD_REG_BITS_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, __ENUM__) - - - DEFINE_SD_REG_THREE_BIT_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, 0, SDR12, SDR25, SDR50, SDR104, DDR50, HS400, RSVD6, UHS_II); - - constexpr inline auto SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_HS200 = SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_SDR104; - - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3, 3_3V_SIGNALING, 1_8V_SIGNALING); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_EXECUTE_TUNING, 6, TUNING_COMPLETED, EXECUTE_TUNING); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, 7, USING_FIXED_CLOCK, USING_TUNED_CLOCK); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, 12, VERSION_300_COMPATIBLE, VERSION_4); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 13, 32_BIT_ADDRESSING, 64_BIT_ADDRESSING); - DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, 15, HOST_DRIVER, AUTOMATIC_SELECTION); - - DEFINE_SD_REG_BIT_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, 28, NOT_SUPPORTED, SUPPORTED); - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp deleted file mode 100644 index 3b16008..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp +++ /dev/null @@ -1,1311 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" -#include "sdmmc_io_impl.board.nintendo_nx.hpp" -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl { - - /* FOR REFERENCE: board-specific sdmmc registers. */ - //struct SdmmcRegisters { - // /* Standard registers. */ - // volatile SdHostStandardRegisters sd_host_standard_registers; - // - // /* Vendor specific registers */ - // volatile uint32_t vendor_clock_cntrl; - // volatile uint32_t vendor_sys_sw_cntrl; - // volatile uint32_t vendor_err_intr_status; - // volatile uint32_t vendor_cap_overrides; - // volatile uint32_t vendor_boot_cntrl; - // volatile uint32_t vendor_boot_ack_timeout; - // volatile uint32_t vendor_boot_dat_timeout; - // volatile uint32_t vendor_debounce_count; - // volatile uint32_t vendor_misc_cntrl; - // volatile uint32_t max_current_override; - // volatile uint32_t max_current_override_hi; - // volatile uint32_t _0x12c[0x20]; - // volatile uint32_t vendor_io_trim_cntrl; - // - // /* Start of sdmmc2/sdmmc4 only */ - // volatile uint32_t vendor_dllcal_cfg; - // volatile uint32_t vendor_dll_ctrl0; - // volatile uint32_t vendor_dll_ctrl1; - // volatile uint32_t vendor_dllcal_cfg_sta; - // /* End of sdmmc2/sdmmc4 only */ - // - // volatile uint32_t vendor_tuning_cntrl0; - // volatile uint32_t vendor_tuning_cntrl1; - // volatile uint32_t vendor_tuning_status0; - // volatile uint32_t vendor_tuning_status1; - // volatile uint32_t vendor_clk_gate_hysteresis_count; - // volatile uint32_t vendor_preset_val0; - // volatile uint32_t vendor_preset_val1; - // volatile uint32_t vendor_preset_val2; - // volatile uint32_t sdmemcomppadctrl; - // volatile uint32_t auto_cal_config; - // volatile uint32_t auto_cal_interval; - // volatile uint32_t auto_cal_status; - // volatile uint32_t io_spare; - // volatile uint32_t sdmmca_mccif_fifoctrl; - // volatile uint32_t timeout_wcoal_sdmmca; - // volatile uint32_t _0x1fc; - //}; - - DEFINE_SD_REG_BIT_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, 2, NORMAL, OVERRIDE); - DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TAP_VAL, 16, 8); - DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TRIM_VAL, 24, 5); - - DEFINE_SD_REG(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 8, 6); - - DEFINE_SD_REG(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 2, 1); - - DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, 31, DISABLE, ENABLE); - - DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, 31, DONE, RUNNING); - - DEFINE_SD_REG(VENDOR_TUNING_CNTRL0_MUL_M, 6, 7); - DEFINE_SD_REG_THREE_BIT_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, 13, TRIES_40, TRIES_64, TRIES_128, TRIES_192, TRIES_256, RESERVED5, RESERVED6, RESERVED7); - DEFINE_SD_REG_BIT_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, 17, NOT_UPDATED_BY_HW, UPDATED_BY_HW); - - DEFINE_SD_REG(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, 0, 4); - DEFINE_SD_REG(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 31, 1); - - DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, 0, 7); - DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, 8, 7); - DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, 29, DISABLED, ENABLED); - DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, 31, DISABLED, ENABLED); - - DEFINE_SD_REG(AUTO_CAL_STATUS_AUTO_CAL_PULLUP, 0, 7); - DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, 31, INACTIVE, ACTIVE); - - DEFINE_SD_REG_BIT_ENUM(IO_SPARE_SPARE_OUT_3, 19, TWO_CYCLE_DELAY, ONE_CYCLE_DELAY); - - namespace { - - constexpr inline u32 TuningCommandTimeoutMilliSeconds = 5; - - constexpr void GetDividerSetting(u32 *out_target_clock_frequency_khz, u16 *out_x, SpeedMode speed_mode) { - switch (speed_mode) { - case SpeedMode_MmcIdentification: - *out_target_clock_frequency_khz = 26000; - *out_x = 66; - break; - case SpeedMode_MmcLegacySpeed: - *out_target_clock_frequency_khz = 26000; - *out_x = 1; - break; - case SpeedMode_MmcHighSpeed: - *out_target_clock_frequency_khz = 52000; - *out_x = 1; - break; - case SpeedMode_MmcHs200: - *out_target_clock_frequency_khz = 200000; - *out_x = 1; - break; - case SpeedMode_MmcHs400: - *out_target_clock_frequency_khz = 200000; - *out_x = 1; - break; - case SpeedMode_SdCardIdentification: - *out_target_clock_frequency_khz = 25000; - *out_x = 64; - break; - case SpeedMode_SdCardDefaultSpeed: - *out_target_clock_frequency_khz = 25000; - *out_x = 1; - break; - case SpeedMode_SdCardHighSpeed: - *out_target_clock_frequency_khz = 50000; - *out_x = 1; - break; - case SpeedMode_SdCardSdr12: - *out_target_clock_frequency_khz = 25000; - *out_x = 1; - break; - case SpeedMode_SdCardSdr50: - *out_target_clock_frequency_khz = 100000; - *out_x = 1; - break; - case SpeedMode_SdCardSdr104: - *out_target_clock_frequency_khz = 200000; - *out_x = 1; - break; - case SpeedMode_GcAsicFpgaSpeed: - *out_target_clock_frequency_khz = 40800; - *out_x = 1; - break; - case SpeedMode_GcAsicSpeed: - *out_target_clock_frequency_khz = 200000; - *out_x = 2; - break; - case SpeedMode_SdCardSdr25: - case SpeedMode_SdCardDdr50: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - } - - namespace { - - #if defined(AMS_SDMMC_THREAD_SAFE) - constinit os::SdkMutex g_soc_mutex; - - #define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex) - - #else - - #define AMS_SDMMC_LOCK_SOC_MUTEX() - - #endif - - constinit bool g_determined_soc = false; - constinit bool g_is_soc_mariko = false; - - } - - bool IsSocMariko() { - if (!g_determined_soc) { - /* Ensure we have exclusive access to the soc variables. */ - AMS_SDMMC_LOCK_SOC_MUTEX(); - - /* Check the SocType. */ - #if defined(ATMOSPHERE_IS_EXOSPHERE) - { - g_is_soc_mariko = fuse::GetSocType() == fuse::SocType_Mariko; - } - #elif defined(ATMOSPHERE_IS_MESOSPHERE) - { - MESOSPHERE_TODO("Detect mariko via KSystemControl call?"); - } - #elif defined(ATMOSPHERE_IS_STRATOSPHERE) - { - /* Connect to spl for the duration of our check. */ - spl::Initialize(); - ON_SCOPE_EXIT { spl::Finalize(); }; - - g_is_soc_mariko = spl::GetSocType() == spl::SocType_Mariko; - } - #else - #error "Unknown execution context for ams::sdmmc::impl::IsSocMariko" - #endif - - /* Note that we determined the soc. */ - g_determined_soc = true; - } - - return g_is_soc_mariko; - } - - void SdmmcController::ReleaseReset(SpeedMode speed_mode) { - /* Get the clock reset module. */ - const auto module = this->GetClockResetModule(); - - /* If the module is available, disable clock. */ - if (ClockResetController::IsAvailable(module)) { - SdHostStandardController::DisableDeviceClock(); - SdHostStandardController::EnsureControl(); - } - - /* Get the correct divider setting for the speed mode. */ - u32 target_clock_frequency_khz; - u16 x; - GetDividerSetting(std::addressof(target_clock_frequency_khz), std::addressof(x), speed_mode); - - /* Release reset. */ - ClockResetController::ReleaseReset(module, target_clock_frequency_khz); - } - - void SdmmcController::AssertReset() { - return ClockResetController::AssertReset(this->GetClockResetModule()); - } - - Result SdmmcController::StartupCore(BusPower bus_power) { - /* Set schmitt trigger. */ - this->SetSchmittTrigger(bus_power); - - /* Select one-cycle delay version of cmd_oen. */ - reg::ReadWrite(this->sdmmc_registers->io_spare, SD_REG_BITS_ENUM(IO_SPARE_SPARE_OUT_3, ONE_CYCLE_DELAY)); - - /* Select regulated reference voltage for trimmer and DLL supply. */ - reg::ReadWrite(this->sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0)); - - /* Configure outbound tap value. */ - reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue())); - - /* Configure SPI_MODE_CLKEN_OVERRIDE. */ - reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, NORMAL)); - - /* Set slew codes. */ - this->SetSlewCodes(); - - /* Set vref sel. */ - reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetVrefSelValue())); - - /* Perform drive strength calibration at the new power. */ - this->SetDriveCodeOffsets(bus_power); - this->CalibrateDriveStrength(bus_power); - - /* Enable internal clock. */ - R_TRY(SdHostStandardController::EnableInternalClock()); - - return ResultSuccess(); - } - - Result SdmmcController::SetClockTrimmer(SpeedMode speed_mode, u8 tap_value) { - /* If speed mode is Hs400, set the dqs trim value. */ - if (speed_mode == SpeedMode_MmcHs400) { - reg::ReadWrite(this->sdmmc_registers->vendor_cap_overrides, SD_REG_BITS_VALUE(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 40)); - } - - /* Configure tap value as updated by software. */ - reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, NOT_UPDATED_BY_HW)); - - /* Set the inbound tap value. */ - reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TAP_VAL, tap_value)); - - /* Reset the cmd/dat line. */ - R_TRY(SdHostStandardController::ResetCmdDatLine()); - - return ResultSuccess(); - } - - u8 SdmmcController::GetCurrentTapValue() { - return static_cast(reg::GetValue(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_MASK(VENDOR_CLOCK_CNTRL_TAP_VAL))); - } - - Result SdmmcController::CalibrateDll() { - /* Check if we need to temporarily re-enable the device clock. */ - const bool clock_disabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - - /* Ensure that the clock is enabled for the period we're using it. */ - if (clock_disabled) { - /* Turn on the clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - } - ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; - - /* Begin calibration. */ - reg::ReadWrite(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE)); - - /* Wait up to 5ms for calibration to begin. */ - { - ManualTimer timer(5); - while (true) { - /* If calibration is done, we're done. */ - if (!reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE))) { - break; - } - - /* Otherwise, check if we've timed out. */ - R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllCalibrationSoftwareTimeout()); - } - } - - /* Wait up to 10ms for calibration to complete. */ - { - ManualTimer timer(10); - while (true) { - /* If calibration is done, we're done. */ - if (reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg_sta, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, DONE))) { - break; - } - - /* Otherwise, check if we've timed out. */ - R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllApplicationSoftwareTimeout()); - } - } - - return ResultSuccess(); - } - - Result SdmmcController::SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value) { - /* Check if we need to temporarily disable the device clock. */ - const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - - /* Ensure that the clock is disabled for the period we're using it. */ - if (clock_enabled) { - /* Turn off the clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - } - - /* Set clock trimmer. */ - /* NOTE: Nintendo does not re-enable the clock if this fails... */ - R_TRY(this->SetClockTrimmer(speed_mode, tap_value)); - - /* Configure for the desired speed mode. */ - switch (speed_mode) { - case SpeedMode_MmcIdentification: - case SpeedMode_SdCardIdentification: - case SpeedMode_MmcLegacySpeed: - case SpeedMode_SdCardDefaultSpeed: - /* Set as normal speed, 3.3V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); - break; - case SpeedMode_MmcHighSpeed: - case SpeedMode_SdCardHighSpeed: - /* Set as high speed, 3.3V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); - break; - case SpeedMode_MmcHs200: - /* Set as HS200, 1.8V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); - break; - case SpeedMode_MmcHs400: - /* Set as HS400, 1.8V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); - break; - case SpeedMode_SdCardSdr12: - /* Set as SDR12, 1.8V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); - break; - case SpeedMode_SdCardSdr50: - case SpeedMode_SdCardSdr104: - case SpeedMode_GcAsicFpgaSpeed: - case SpeedMode_GcAsicSpeed: - /* Set as SDR104, 1.8V. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104)); - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - SdHostStandardController::EnsureControl(); - - /* Get the divider setting. */ - u32 target_source_clock_frequency_khz; - u16 x; - GetDividerSetting(std::addressof(target_source_clock_frequency_khz), std::addressof(x), speed_mode); - - /* Set the clock frequency. */ - u32 actual_source_clock_frequency_khz; - ClockResetController::SetClockFrequencyKHz(std::addressof(actual_source_clock_frequency_khz), this->GetClockResetModule(), target_source_clock_frequency_khz); - - /* Set the device clock frequency. */ - const u32 actual_device_clock_frequency_khz = util::DivideUp(actual_source_clock_frequency_khz, x); - SdHostStandardController::SetDeviceClockFrequencyKHz(actual_device_clock_frequency_khz); - - /* Check that the divider is correct. */ - AMS_ABORT_UNLESS((x == 1) || util::IsAligned(x, 2)); - - /* Write the divider val to clock control. */ - const u16 n = x / 2; - const u16 upper_n = n >> 8; - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_VALUE(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, n), - SD_REG_BITS_VALUE(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, upper_n)); - - /* Re-enable the clock, if we should. */ - if (clock_enabled) { - /* Turn on the clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - } - - /* If speed mode is Hs400, calibrate dll. */ - if (speed_mode == SpeedMode_MmcHs400) { - R_TRY(this->CalibrateDll()); - } - - /* Set the current speed mode. */ - this->current_speed_mode = speed_mode; - - return ResultSuccess(); - } - - Result SdmmcController::IssueTuningCommand(u32 command_index) { - /* Check that we're not power saving enable. */ - AMS_ABORT_UNLESS(!SdHostStandardController::IsPowerSavingEnable()); - - /* Wait until command inhibit is done. */ - R_TRY(SdHostStandardController::WaitWhileCommandInhibit(true)); - - /* Set transfer for tuning. */ - SdHostStandardController::SetTransferForTuning(); - - /* If necessary, clear interrupt and enable buffer read ready signal. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - this->ClearInterrupt(); - - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); - } - #endif - - /* Set the buffer read ready enable, and read status to ensure it takes. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); - reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, reg::Read(this->sdmmc_registers->sd_host_standard_registers.normal_int_status)); - - /* Issue command with clock disabled. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - { - SdHostStandardController::SetCommandForTuning(command_index); - - SdHostStandardController::EnsureControl(); - WaitMicroSeconds(1); - SdHostStandardController::AbortTransaction(); - } - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - - /* When we're done waiting, ensure that we clean up appropriately. */ - ON_SCOPE_EXIT { - /* Clear the buffer read ready signal, if we should. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); - #endif - - /* Clear the buffer read ready enable. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); - - /* Wait 8 clocks to ensure configuration takes. */ - SdHostStandardController::EnsureControl(); - WaitClocks(8, SdHostStandardController::GetDeviceClockFrequencyKHz()); - }; - - /* Wait for the command to finish. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - const auto result = SdHostStandardController::WaitInterrupt(TuningCommandTimeoutMilliSeconds); - if (R_SUCCEEDED(result)) { - /* If we succeeded, clear the interrupt. */ - reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); - this->ClearInterrupt(); - return ResultSuccess(); - } else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) { - SdHostStandardController::AbortTransaction(); - return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); - } else { - return result; - } - } - #else - { - SdHostStandardController::EnsureControl(); - ManualTimer timer(TuningCommandTimeoutMilliSeconds); - while (true) { - /* Check if we received the interrupt. */ - if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED))) { - /* If we did, acknowledge it. */ - reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); - return ResultSuccess(); - } - - /* Otherwise, check if we timed out. */ - if (!timer.Update()) { - SdHostStandardController::AbortTransaction(); - return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); - } - } - } - #endif - } - - void SdmmcController::SetDriveCodeOffsets(BusPower bus_power) { - /* Get the offsets. */ - u8 pd, pu; - this->GetAutoCalOffsets(std::addressof(pd), std::addressof(pu), bus_power); - - /* Set the offsets. */ - reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd), - SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu)); - - /* Wait for 1ms to ensure that our configuration takes. */ - /* NOTE/HACK: Nintendo does not (need) to do this, but they use SvcSleepThread for waits. */ - /* It's unclear why this wait is necessary, but not doing it causes drive strength calibration to fail if done immediately afterwards. */ - util::WaitMicroSeconds(1000); - } - - void SdmmcController::CalibrateDriveStrength(BusPower bus_power) { - /* Reset drive strength calibration status. */ - this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); - - /* Check if we need to temporarily disable the device clock. */ - const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - - /* Ensure that the clock is disabled for the period we're using it. */ - if (clock_enabled) { - /* Turn off the clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - } - - /* Calibrate with the clock disabled. */ - { - /* Set SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ - if (reg::HasValue(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) { - reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 1)); - SdHostStandardController::EnsureControl(); - WaitMicroSeconds(1); - } - - /* Calibrate with SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD set. */ - { - /* Begin autocal. */ - reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, ENABLED), - SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, ENABLED)); - SdHostStandardController::EnsureControl(); - WaitMicroSeconds(2); - - /* Wait up to 10ms for auto cal to complete. */ - ManualTimer timer(10); - while (true) { - /* Check if auto cal is inactive. */ - if (reg::HasValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, INACTIVE))) { - /* Check the pullup status. */ - const u32 pullup = (reg::GetValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F; - if (pullup == 0x1F) { - this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd(); - } - if (pullup == 0) { - this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen(); - } - - break; - } - - /* Otherwise, check if we've timed out. */ - if (!timer.Update()) { - this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout(); - - this->SetDriveStrengthToDefaultValues(bus_power); - reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, DISABLED)); - break; - } - } - } - - /* Clear SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ - reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0)); - } - - /* Re-enable the clock, if we should. */ - if (clock_enabled) { - /* Turn on the clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - } - - /* If calibration didn't receive a replacement error, set internal state to success. */ - if (sdmmc::ResultDriveStrengthCalibrationNotCompleted::Includes(this->drive_strength_calibration_status)) { - this->drive_strength_calibration_status = ResultSuccess(); - } - } - - Result SdmmcController::Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) { - /* Verify that we're awake. */ - AMS_ABORT_UNLESS(this->is_awake); - - /* Release the controller from reset. */ - this->ReleaseReset(speed_mode); - - /* Mark that we're not shutdown. */ - this->is_shutdown = false; - - /* Power on the controller. */ - R_TRY(this->PowerOn(bus_power)); - - /* Start up for the specific power. */ - R_TRY(this->StartupCore(bus_power)); - - /* Set our current power/width/speed. */ - SdHostStandardController::SetBusWidth(bus_width); - SdHostStandardController::SetBusPower(bus_power); - R_TRY(this->SetSpeedMode(speed_mode)); - this->SetPowerSaving(power_saving_enable); - - /* Enable clock to the device. */ - SdHostStandardController::EnableDeviceClock(); - - /* Ensure that we can control the device. */ - SdHostStandardController::EnsureControl(); - - return ResultSuccess(); - } - - void SdmmcController::Shutdown() { - /* If we're already shut down, there's nothing to do. */ - if (this->is_shutdown) { - return; - } - - /* If we're currently awake, we need to disable clock/power. */ - if (this->is_awake) { - SdHostStandardController::DisableDeviceClock(); - SdHostStandardController::SetBusPower(BusPower_Off); - SdHostStandardController::EnsureControl(); - } - - /* Power off. */ - this->PowerOff(); - - /* If awake, assert reset. */ - if (this->is_awake) { - this->AssertReset(); - } - - /* Mark that we're shutdown. */ - this->is_shutdown = true; - } - - void SdmmcController::PutToSleep() { - /* If we're already shut down or asleep, there's nothing to do. */ - if (this->is_shutdown || !this->is_awake) { - return; - } - - /* Save values before sleep. */ - this->bus_power_before_sleep = SdHostStandardController::GetBusPower(); - this->bus_width_before_sleep = SdHostStandardController::GetBusWidth(); - this->speed_mode_before_sleep = this->current_speed_mode; - this->tap_value_before_sleep = this->GetCurrentTapValue(); - this->is_powersaving_enable_before_sleep = SdHostStandardController::IsPowerSavingEnable(); - - /* Disable clock/power to the device. */ - SdHostStandardController::DisableDeviceClock(); - SdHostStandardController::SetBusPower(BusPower_Off); - SdHostStandardController::EnsureControl(); - - /* Assert reset. */ - this->AssertReset(); - - /* Mark that we're asleep. */ - this->is_awake = false; - } - - Result SdmmcController::Awaken() { - /* If we're shut down, or if we're awake already, there's nothing to do. */ - R_SUCCEED_IF(this->is_shutdown); - R_SUCCEED_IF(this->is_awake); - - /* Mark that we're awake. */ - this->is_awake = true; - - /* Clear pad parked status. */ - this->ClearPadParked(); - - /* Release reset. */ - this->ReleaseReset(this->speed_mode_before_sleep); - - /* Start up for the correct power. */ - R_TRY(this->StartupCore(this->bus_power_before_sleep)); - - /* Configure values to what they were before sleep. */ - SdHostStandardController::SetBusWidth(this->bus_width_before_sleep); - SdHostStandardController::SetBusPower(this->bus_power_before_sleep); - R_TRY(this->SetSpeedModeWithTapValue(this->speed_mode_before_sleep, this->tap_value_before_sleep)); - this->SetPowerSaving(this->is_powersaving_enable_before_sleep); - - /* Enable clock to the device. */ - SdHostStandardController::EnableDeviceClock(); - SdHostStandardController::EnsureControl(); - - return ResultSuccess(); - } - - Result SdmmcController::SwitchToSdr12() { - /* Disable clock. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); - - /* Check that the dat lines are all low. */ - R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch()); - - /* Set Speed Mode. */ - R_TRY(this->SetSpeedMode(SpeedMode_SdCardSdr12)); - - /* Set voltage to 1.8V. */ - SdHostStandardController::EnsureControl(); - R_TRY(this->LowerBusPower()); - this->SetSchmittTrigger(BusPower_1_8V); - - /* Perform drive strength calibration at the new power. */ - this->SetDriveCodeOffsets(BusPower_1_8V); - this->CalibrateDriveStrength(BusPower_1_8V); - - /* Set the bus power in standard controller. */ - SdHostStandardController::SetBusPower(BusPower_1_8V); - - /* Wait up to 5ms for the switch to take. */ - SdHostStandardController::EnsureControl(); - WaitMicroSeconds(5000); - - /* Check that we switched to 1.8V. */ - R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)), sdmmc::ResultSdHostStandardFailSwitchTo1_8V()); - - /* Enable clock, and wait 1ms. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); - SdHostStandardController::EnsureControl(); - WaitMicroSeconds(1000); - - /* Check that the dat lines are all high. */ - R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch()); - - return ResultSuccess(); - } - - Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) { - /* Get the tap value. */ - u8 tap_value; - if (speed_mode == SpeedMode_MmcHs400) { - AMS_ABORT_UNLESS(this->is_valid_tap_value_for_hs_400); - tap_value = this->tap_value_for_hs_400; - } else { - tap_value = this->GetDefaultInboundTapValue(); - } - - /* Set the speed mode. */ - R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value)); - - return ResultSuccess(); - } - - void SdmmcController::SetPowerSaving(bool en) { - /* If necessary, calibrate the drive strength. */ - if (this->IsNeedPeriodicDriveStrengthCalibration() && !en && SdHostStandardController::IsDeviceClockEnable()) { - this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); - } - - return SdHostStandardController::SetPowerSaving(en); - } - - void SdmmcController::EnableDeviceClock() { - /* If necessary, calibrate the drive strength. */ - if (this->IsNeedPeriodicDriveStrengthCalibration() && !SdHostStandardController::IsPowerSavingEnable()) { - this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); - } - - return SdHostStandardController::EnableDeviceClock(); - } - - Result SdmmcController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { - /* If necessary, calibrate the drive strength. */ - if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { - this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); - } - - return SdHostStandardController::IssueCommand(command, xfer_data, out_num_transferred_blocks); - } - - Result SdmmcController::IssueStopTransmissionCommand(u32 *out_response) { - /* If necessary, calibrate the drive strength. */ - if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { - this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); - } - - return SdHostStandardController::IssueStopTransmissionCommand(out_response); - } - - Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) { - /* Clear vendor tuning control 1. */ - reg::Write(this->sdmmc_registers->vendor_tuning_cntrl1, 0); - - /* Determine/configure the number of tries. */ - int num_tries; - switch (speed_mode) { - case SpeedMode_MmcHs200: - case SpeedMode_MmcHs400: - case SpeedMode_SdCardSdr104: - num_tries = 128; - reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_128)); - break; - case SpeedMode_SdCardSdr50: - case SpeedMode_GcAsicFpgaSpeed: - case SpeedMode_GcAsicSpeed: - num_tries = 256; - reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_256)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Configure the multiplier. */ - reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_VALUE(VENDOR_TUNING_CNTRL0_MUL_M, 1)); - - /* Configure tap value to be updated by hardware. */ - reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, UPDATED_BY_HW)); - - /* Configure to execute tuning. */ - reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING)); - - /* Perform tuning num_tries times. */ - for (int i = 0; /* ... */; ++i) { - /* Check if we've been removed. */ - R_TRY(this->CheckRemoved()); - - /* Issue the command. */ - this->IssueTuningCommand(command_index); - - /* Check if tuning is done. */ - if (i >= num_tries) { - break; - } - - if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, TUNING_COMPLETED))) { - break; - } - } - - /* Check if we're using the tuned clock. */ - R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed()); - - return ResultSuccess(); - } - - void SdmmcController::SaveTuningStatusForHs400() { - /* Save the current tap value. */ - this->tap_value_for_hs_400 = GetCurrentTapValue(); - this->is_valid_tap_value_for_hs_400 = true; - } - - Result Sdmmc1Controller::PowerOnForRegisterControl(BusPower bus_power) { - AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); - - /* Nintendo sets the current bus power regardless of whether the call succeeds. */ - ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; }; - - /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ - R_TRY(this->power_controller->PowerOn(BusPower_3_3V)); - - return ResultSuccess(); - } - - void Sdmmc1Controller::PowerOffForRegisterControl() { - /* If we're already off, there's nothing to do. */ - if (this->current_bus_power == BusPower_Off) { - return; - } - - /* If we're at 3.3V, lower to 1.8V. */ - if (this->current_bus_power == BusPower_3_3V) { - /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ - this->power_controller->LowerBusPower(); - - /* Set our bus power. */ - this->current_bus_power = BusPower_1_8V; - } - - /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ - pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh); - - - /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ - this->power_controller->PowerOff(); - - /* Set our bus power. */ - this->current_bus_power = BusPower_Off; - - /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ - pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1ResetState); - } - - Result Sdmmc1Controller::LowerBusPowerForRegisterControl() { - /* Nintendo sets the current bus power regardless of whether the call succeeds. */ - ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; }; - - /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ - R_TRY(this->power_controller->LowerBusPower()); - - return ResultSuccess(); - } - - void Sdmmc1Controller::SetSchmittTriggerForRegisterControl(BusPower bus_power) { - SdHostStandardController::EnsureControl(); - - if (IsSocMariko()) { - /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ - pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); - } else { - switch (bus_power) { - case BusPower_1_8V: - /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ - pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); - break; - case BusPower_3_3V: - /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ - pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable); - break; - case BusPower_Off: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - } - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - Result Sdmmc1Controller::PowerOnForPcvControl(BusPower bus_power) { - AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); - - /* Nintendo sets the current bus power regardless of whether the call succeeds. */ - ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; }; - - /* TODO: return pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ - return ResultSuccess(); - } - - void Sdmmc1Controller::PowerOffForPcvControl() { - /* If we're already off, there's nothing to do. */ - if (this->current_bus_power == BusPower_Off) { - return; - } - - /* If we're at 3.3V, lower to 1.8V. */ - { - /* TODO: pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ - this->current_bus_power = BusPower_1_8V; - } - - /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ - - /* TODO: pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ - this->current_bus_power = BusPower_Off; - - /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ - - } - - Result Sdmmc1Controller::LowerBusPowerForPcvControl() { - /* Nintendo sets the current bus power regardless of whether the call succeeds. */ - ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; }; - - /* TODO: return pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ - return ResultSuccess(); - } - - void Sdmmc1Controller::SetSchmittTriggerForPcvControl(BusPower bus_power) { - SdHostStandardController::EnsureControl(); - - if (IsSocMariko()) { - /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ - } else { - switch (bus_power) { - case BusPower_1_8V: - /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ - break; - case BusPower_3_3V: - /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ - break; - case BusPower_Off: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - } - #endif - - Result Sdmmc1Controller::PowerOn(BusPower bus_power) { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - if (this->is_pcv_control) { - return this->PowerOnForPcvControl(bus_power); - } else - #endif - { - return this->PowerOnForRegisterControl(bus_power); - } - } - - void Sdmmc1Controller::PowerOff() { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - if (this->is_pcv_control) { - return this->PowerOffForPcvControl(); - } else - #endif - { - return this->PowerOffForRegisterControl(); - } - } - - Result Sdmmc1Controller::LowerBusPower() { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - if (this->is_pcv_control) { - return this->LowerBusPowerForPcvControl(); - } else - #endif - { - return this->LowerBusPowerForRegisterControl(); - } - } - - void Sdmmc1Controller::SetSchmittTrigger(BusPower bus_power) { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - if (this->is_pcv_control) { - return this->SetSchmittTriggerForPcvControl(bus_power); - } else - #endif - { - return this->SetSchmittTriggerForRegisterControl(bus_power); - } - } - - void Sdmmc1Controller::Initialize() { - return this->InitializeForRegisterControl(); - } - - void Sdmmc1Controller::Finalize() { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - if (this->is_pcv_control) { - return this->FinalizeForPcvControl(); - } else - #endif - { - return this->FinalizeForRegisterControl(); - } - } - - void Sdmmc1Controller::InitializeForRegisterControl() { - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - /* Mark ourselves as initialized by register control. */ - this->is_pcv_control = false; - #endif - - /* pinmux::Initialize(); */ - /* This just opens a session handle to pinmux service, no work to do. */ - - /* pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ - /* This just sets the session's internal value to the pin group name, so nothing to do here either. */ - - /* pcv::Initialize(); */ - /* This initializes a lot of globals in pcv, most of which we don't care about. */ - /* However, we do care about the Sdmmc1PowerController. */ - AMS_ABORT_UNLESS(this->power_controller == nullptr); - this->power_controller = new (GetPointer(this->power_controller_storage)) PowerController; - - /* Perform base initialization. */ - SdmmcController::Initialize(); - } - - void Sdmmc1Controller::FinalizeForRegisterControl() { - /* Perform base finalization. */ - SdmmcController::Finalize(); - - /* pcv::Finalize(); */ - /* As with initialize, we mostly don't care about the globals this touches. */ - /* However, we do want to finalize the Sdmmc1PowerController. */ - AMS_ABORT_UNLESS(this->power_controller != nullptr); - this->power_controller->~PowerController(); - this->power_controller = nullptr; - - /* pinmux::CloseSession(std::addressof(this->pinmux_session)); */ - /* This does nothing. */ - - /* pinmux::Finalize(); */ - /* This does nothing. */ - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - /* Mark ourselves as initialized by register control. */ - this->is_pcv_control = false; - #endif - } - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - void Sdmmc1Controller::InitializeForPcvControl() { - /* Mark ourselves as initialized by pcv control. */ - this->is_pcv_control = true; - - /* TODO: pinmux::Initialize(); */ - /* TODO: pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ - /* TODO: pcv::Initialize(); */ - - /* Perform base initialization. */ - SdmmcController::Initialize(); - } - - void Sdmmc1Controller::FinalizeForPcvControl() { - /* Perform base finalization. */ - SdmmcController::Finalize(); - - /* TODO: pcv::Finalize(); */ - /* TODO: pinmux::CloseSession(std::addressof(this->pinmux_session)); */ - /* TODO: pinmux::Finalize(); */ - - /* Mark ourselves as initialized by register control. */ - this->is_pcv_control = false; - } - #endif - - namespace { - - constexpr inline dd::PhysicalAddress PmcRegistersPhysicalAddress = 0x7000E400; - - } - - Result Sdmmc1Controller::PowerController::ControlVddioSdmmc1(BusPower bus_power) { - /* Configure appropriate voltage. */ - switch (bus_power) { - case BusPower_Off: - R_TRY(SetSdCardVoltageEnabled(false)); - break; - case BusPower_1_8V: - R_TRY(SetSdCardVoltageValue(1'800'000)); - R_TRY(SetSdCardVoltageEnabled(true)); - break; - case BusPower_3_3V: - R_TRY(SetSdCardVoltageValue(3'300'000)); - R_TRY(SetSdCardVoltageEnabled(true)); - break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - return ResultSuccess(); - } - - void Sdmmc1Controller::PowerController::SetSdmmcIoMode(bool is_3_3V) { - /* Determine the address we're updating. */ - constexpr dd::PhysicalAddress ApbdevPmcPwrDetValAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_PWR_DET_VAL; - - /* Read the current value. */ - u32 value = dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); - - /* Mask out the existing bits. */ - value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(PWR_DET_VAL_SDMMC1))); - - /* ORR in the new bits. */ - value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(PWR_DET_VAL_SDMMC1, is_3_3V, ENABLE, DISABLE)); - - /* Write the new value. */ - dd::WriteIoRegister(ApbdevPmcPwrDetValAddress, value); - - /* Read the value back to be sure our write takes. */ - dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); - } - - void Sdmmc1Controller::PowerController::ControlRailSdmmc1Io(bool is_power_on) { - /* Determine the address we're updating. */ - constexpr dd::PhysicalAddress ApbdevPmcNoIoPowerAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_NO_IOPOWER; - - /* Read the current value. */ - u32 value = dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); - - /* Mask out the existing bits. */ - value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(NO_IOPOWER_SDMMC1))); - - /* ORR in the new bits. */ - value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(NO_IOPOWER_SDMMC1, is_power_on, DISABLE, ENABLE)); - - /* Write the new value. */ - dd::WriteIoRegister(ApbdevPmcNoIoPowerAddress, value); - - /* Read the value back to be sure our write takes. */ - dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); - } - - Sdmmc1Controller::PowerController::PowerController() : current_bus_power(BusPower_Off) { - /* gpio::Initialize(); */ - /* ... */ - - /* Open gpio session. */ - /* gpio::OpenSession(std::addressof(this->gpio_pad_session), gpio::GpioPadName_PowSdEn); */ - gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn); - - /* Configure the gpio as low/output. */ - /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */ - gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); - - /* gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Output); */ - gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output); - } - - Sdmmc1Controller::PowerController::~PowerController() { - /* gpio::CloseSession(std::addressof(this->gpio_pad_session)); */ - gpio_impl::CloseSession(gpio_impl::GpioPadName_PowSdEn); - - /* gpio::Finalize(); */ - /* ... */ - } - - Result Sdmmc1Controller::PowerController::PowerOn(BusPower bus_power) { - /* Bus power should be off, and if it's not we don't need to do anything. */ - AMS_ASSERT(this->current_bus_power == BusPower_Off); - R_SUCCEED_IF(this->current_bus_power != BusPower_Off); - - /* Power on requires the target bus power be 3.3V. */ - AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); - - /* Enable the rail. */ - this->ControlRailSdmmc1Io(true); - - /* Set the SD power GPIO to high. */ - /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_High); */ - gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_High); - - /* Wait 10ms for power change to take. */ - WaitMicroSeconds(10000); - - /* Configure Sdmmc1 IO as 3.3V. */ - this->SetSdmmcIoMode(true); - R_TRY(this->ControlVddioSdmmc1(BusPower_3_3V)); - - /* Wait 130 us for changes to take. */ - WaitMicroSeconds(130); - - /* Update our current bus power. */ - this->current_bus_power = bus_power; - - return ResultSuccess(); - } - - Result Sdmmc1Controller::PowerController::PowerOff() { - /* Bus power should be on, and if it's not we don't need to do anything. */ - AMS_ASSERT(this->current_bus_power != BusPower_Off); - R_SUCCEED_IF(this->current_bus_power == BusPower_Off); - - /* Bus power should be 1.8V. */ - /* NOTE: the result returned here is 0x8C0 (regulator::ResultIllegalRequest()) on newer firmwares. */ - AMS_ASSERT(this->current_bus_power == BusPower_1_8V); - R_UNLESS(this->current_bus_power == BusPower_1_8V, pcv::ResultIllegalRequest()); - - /* Disable vddio, and wait 4 ms. */ - this->ControlVddioSdmmc1(BusPower_Off); - WaitMicroSeconds(4000); - - /* Set the SD power GPIO to low. */ - /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */ - gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); - - /* Wait 239ms for the gpio config to take. */ - WaitMicroSeconds(239000); - - /* Disable the rail. */ - this->ControlRailSdmmc1Io(false); - this->SetSdmmcIoMode(true); - - /* Update our current bus power. */ - this->current_bus_power = BusPower_Off; - - return ResultSuccess(); - } - - Result Sdmmc1Controller::PowerController::LowerBusPower() { - /* Bus power should be 3.3V, and if it's not we don't need to do anything. */ - AMS_ASSERT(this->current_bus_power == BusPower_3_3V); - R_SUCCEED_IF(this->current_bus_power != BusPower_3_3V); - - /* Configure as 1.8V, then wait 150us for it to take. */ - R_TRY(this->ControlVddioSdmmc1(BusPower_1_8V)); - WaitMicroSeconds(150); - this->SetSdmmcIoMode(false); - - /* Update our current bus power. */ - this->current_bus_power = BusPower_1_8V; - - return ResultSuccess(); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp deleted file mode 100644 index d007d9d..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp +++ /dev/null @@ -1,682 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 "sdmmc_sd_host_standard_controller.hpp" -#include "sdmmc_clock_reset_controller.hpp" - -namespace ams::sdmmc::impl { - - bool IsSocMariko(); - - constexpr inline size_t SdmmcRegistersSize = 0x200; - - constexpr inline dd::PhysicalAddress ApbMiscRegistersPhysicalAddress = UINT64_C(0x70000000); - constexpr inline size_t ApbMiscRegistersSize = 16_KB; - - class SdmmcController : public SdHostStandardController { - private: - struct SdmmcRegisters { - /* Standard registers. */ - volatile SdHostStandardRegisters sd_host_standard_registers; - - /* Vendor specific registers */ - volatile uint32_t vendor_clock_cntrl; - volatile uint32_t vendor_sys_sw_cntrl; - volatile uint32_t vendor_err_intr_status; - volatile uint32_t vendor_cap_overrides; - volatile uint32_t vendor_boot_cntrl; - volatile uint32_t vendor_boot_ack_timeout; - volatile uint32_t vendor_boot_dat_timeout; - volatile uint32_t vendor_debounce_count; - volatile uint32_t vendor_misc_cntrl; - volatile uint32_t max_current_override; - volatile uint32_t max_current_override_hi; - volatile uint32_t _0x12c[0x20]; - volatile uint32_t vendor_io_trim_cntrl; - - /* Start of sdmmc2/sdmmc4 only */ - volatile uint32_t vendor_dllcal_cfg; - volatile uint32_t vendor_dll_ctrl0; - volatile uint32_t vendor_dll_ctrl1; - volatile uint32_t vendor_dllcal_cfg_sta; - /* End of sdmmc2/sdmmc4 only */ - - volatile uint32_t vendor_tuning_cntrl0; - volatile uint32_t vendor_tuning_cntrl1; - volatile uint32_t vendor_tuning_status0; - volatile uint32_t vendor_tuning_status1; - volatile uint32_t vendor_clk_gate_hysteresis_count; - volatile uint32_t vendor_preset_val0; - volatile uint32_t vendor_preset_val1; - volatile uint32_t vendor_preset_val2; - volatile uint32_t sdmemcomppadctrl; - volatile uint32_t auto_cal_config; - volatile uint32_t auto_cal_interval; - volatile uint32_t auto_cal_status; - volatile uint32_t io_spare; - volatile uint32_t sdmmca_mccif_fifoctrl; - volatile uint32_t timeout_wcoal_sdmmca; - volatile uint32_t _0x1fc; - }; - static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize); - private: - SdmmcRegisters *sdmmc_registers; - bool is_shutdown; - bool is_awake; - SpeedMode current_speed_mode; - BusPower bus_power_before_sleep; - BusWidth bus_width_before_sleep; - SpeedMode speed_mode_before_sleep; - u8 tap_value_before_sleep; - bool is_powersaving_enable_before_sleep; - u8 tap_value_for_hs_400; - bool is_valid_tap_value_for_hs_400; - Result drive_strength_calibration_status; - private: - void ReleaseReset(SpeedMode speed_mode); - void AssertReset(); - Result StartupCore(BusPower bus_power); - Result SetClockTrimmer(SpeedMode speed_mode, u8 tap_value); - u8 GetCurrentTapValue(); - Result CalibrateDll(); - Result SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value); - Result IssueTuningCommand(u32 command_index); - protected: - void SetDriveCodeOffsets(BusPower bus_power); - void CalibrateDriveStrength(BusPower bus_power); - - virtual void SetPad() = 0; - - virtual ClockResetController::Module GetClockResetModule() const = 0; - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual int GetInterruptNumber() const = 0; - virtual os::InterruptEventType *GetInterruptEvent() const = 0; - #endif - - virtual bool IsNeedPeriodicDriveStrengthCalibration() = 0; - virtual void ClearPadParked() = 0; - virtual Result PowerOn(BusPower bus_power) = 0; - virtual void PowerOff() = 0; - virtual Result LowerBusPower() = 0; - virtual void SetSchmittTrigger(BusPower bus_power) = 0; - virtual u8 GetOutboundTapValue() const = 0; - virtual u8 GetDefaultInboundTapValue() const = 0; - virtual u8 GetVrefSelValue() const = 0; - virtual void SetSlewCodes() = 0; - virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const = 0; - virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) = 0; - public: - explicit SdmmcController(dd::PhysicalAddress registers_phys_addr) : SdHostStandardController(registers_phys_addr, SdmmcRegistersSize) { - /* Set sdmmc registers. */ - static_assert(offsetof(SdmmcRegisters, sd_host_standard_registers) == 0); - this->sdmmc_registers = reinterpret_cast(this->registers); - - this->is_shutdown = true; - this->is_awake = true; - this->is_valid_tap_value_for_hs_400 = false; - this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); - this->tap_value_for_hs_400 = 0; - this->current_speed_mode = SpeedMode_MmcIdentification; - this->bus_power_before_sleep = BusPower_Off; - this->bus_width_before_sleep = BusWidth_1Bit; - this->speed_mode_before_sleep = SpeedMode_MmcIdentification; - this->tap_value_before_sleep = 0; - this->is_powersaving_enable_before_sleep = false; - } - - virtual void Initialize() override { - /* Set pad. */ - this->SetPad(); - - /* Initialize our clock/reset module. */ - ClockResetController::Initialize(this->GetClockResetModule()); - - /* If necessary, initialize our interrupt event. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - os::InterruptEventType *interrupt_event = this->GetInterruptEvent(); - os::InitializeInterruptEvent(interrupt_event, this->GetInterruptNumber(), os::EventClearMode_ManualClear); - SdHostStandardController::PreSetInterruptEvent(interrupt_event); - } - #endif - - /* Perform base initialization. */ - SdHostStandardController::Initialize(); - } - - virtual void Finalize() override { - /* Perform base finalization. */ - SdHostStandardController::Finalize(); - - /* If necessary, finalize our interrupt event. */ - #if defined(AMS_SDMMC_USE_OS_EVENTS) - { - os::FinalizeInterruptEvent(this->GetInterruptEvent()); - } - #endif - - /* Finalize our clock/reset module. */ - ClockResetController::Finalize(this->GetClockResetModule()); - } - - virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) override; - virtual void Shutdown() override; - virtual void PutToSleep() override; - virtual Result Awaken() override; - virtual Result SwitchToSdr12() override; - virtual Result SetSpeedMode(SpeedMode speed_mode) override; - - virtual SpeedMode GetSpeedMode() const override { - return this->current_speed_mode; - } - - virtual void SetPowerSaving(bool en) override; - virtual void EnableDeviceClock() override; - - virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; - virtual Result IssueStopTransmissionCommand(u32 *out_response) override; - - virtual bool IsSupportedTuning() const override { - return true; - } - - virtual Result Tuning(SpeedMode speed_mode, u32 command_index) override; - virtual void SaveTuningStatusForHs400() override; - - virtual Result GetInternalStatus() const override { - return this->drive_strength_calibration_status; - } - }; - - constexpr inline dd::PhysicalAddress Sdmmc1RegistersPhysicalAddress = UINT64_C(0x700B0000); - - class Sdmmc1Controller : public SdmmcController { - private: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - static constinit inline os::InterruptEventType s_interrupt_event{}; - #endif - - /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */ - class PowerController { - NON_COPYABLE(PowerController); - NON_MOVEABLE(PowerController); - private: - BusPower current_bus_power; - private: - Result ControlVddioSdmmc1(BusPower bus_power); - void SetSdmmcIoMode(bool is_3_3V); - void ControlRailSdmmc1Io(bool is_power_on); - public: - PowerController(); - ~PowerController(); - - Result PowerOn(BusPower bus_power); - Result PowerOff(); - Result LowerBusPower(); - }; - private: - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - /* TODO: pinmux::PinmuxSession pinmux_session; */ - #endif - BusPower current_bus_power; - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - bool is_pcv_control; - #endif - TYPED_STORAGE(PowerController) power_controller_storage; - PowerController *power_controller; - private: - Result PowerOnForRegisterControl(BusPower bus_power); - void PowerOffForRegisterControl(); - Result LowerBusPowerForRegisterControl(); - void SetSchmittTriggerForRegisterControl(BusPower bus_power); - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - Result PowerOnForPcvControl(BusPower bus_power); - void PowerOffForPcvControl(); - Result LowerBusPowerForPcvControl(); - void SetSchmittTriggerForPcvControl(BusPower bus_power); - #endif - protected: - virtual void SetPad() override { - /* Nothing is needed here. */ - } - - virtual ClockResetController::Module GetClockResetModule() const override { - return ClockResetController::Module_Sdmmc1; - } - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual int GetInterruptNumber() const override { - return 46; - } - - virtual os::InterruptEventType *GetInterruptEvent() const override { - return std::addressof(s_interrupt_event); - } - #endif - - virtual bool IsNeedPeriodicDriveStrengthCalibration() override { - return !IsSocMariko(); - } - - virtual void ClearPadParked() override { - /* Nothing is needed here. */ - } - - virtual Result PowerOn(BusPower bus_power) override; - virtual void PowerOff() override; - virtual Result LowerBusPower() override; - - virtual void SetSchmittTrigger(BusPower bus_power) override; - - virtual u8 GetOutboundTapValue() const override { - if (IsSocMariko()) { - return 0xE; - } else { - return 0x2; - } - } - - virtual u8 GetDefaultInboundTapValue() const override { - if (IsSocMariko()) { - return 0xB; - } else { - return 0x4; - } - } - - virtual u8 GetVrefSelValue() const override { - if (IsSocMariko()) { - return 0x0; - } else { - return 0x7; - } - } - - virtual void SetSlewCodes() override { - if (IsSocMariko()) { - /* Do nothing. */ - } else { - /* Ensure that we can control registers. */ - SdHostStandardController::EnsureControl(); - - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Write the slew values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ - reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 0x1), - APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 0x1)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); - } - } - - virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { - /* Ensure that we can write the offsets. */ - AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); - AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); - - /* Set the offsets. */ - if (IsSocMariko()) { - switch (bus_power) { - case BusPower_1_8V: - *out_auto_cal_pd_offset = 6; - *out_auto_cal_pu_offset = 6; - break; - case BusPower_3_3V: - *out_auto_cal_pd_offset = 0; - *out_auto_cal_pu_offset = 0; - break; - case BusPower_Off: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } else { - switch (bus_power) { - case BusPower_1_8V: - *out_auto_cal_pd_offset = 0x7B; - *out_auto_cal_pu_offset = 0x7B; - break; - case BusPower_3_3V: - *out_auto_cal_pd_offset = 0x7D; - *out_auto_cal_pu_offset = 0; - break; - case BusPower_Off: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - } - - virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { - /* Ensure that we can control registers. */ - SdHostStandardController::EnsureControl(); - - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Determine the drive code values. */ - u8 drvdn, drvup; - if (IsSocMariko()) { - drvdn = 0x8; - drvup = 0x8; - } else { - switch (bus_power) { - case BusPower_1_8V: - drvdn = 0xF; - drvup = 0xB; - break; - case BusPower_3_3V: - drvdn = 0xC; - drvup = 0xC; - break; - case BusPower_Off: - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - /* Write the drv up/down values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ - reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, drvdn), - APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, drvup)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); - } - public: - Sdmmc1Controller() : SdmmcController(Sdmmc1RegistersPhysicalAddress) { - this->current_bus_power = BusPower_Off; - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - this->is_pcv_control = false; - #endif - this->power_controller = nullptr; - } - - virtual void Initialize() override; - virtual void Finalize() override; - - void InitializeForRegisterControl(); - void FinalizeForRegisterControl(); - - #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - void InitializeForPcvControl(); - void FinalizeForPcvControl(); - #endif - - virtual bool IsSupportedBusPower(BusPower bus_power) const override { - switch (bus_power) { - case BusPower_Off: return true; - case BusPower_1_8V: return true; - case BusPower_3_3V: return true; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { - switch (bus_width) { - case BusWidth_1Bit: return true; - case BusWidth_4Bit: return true; - case BusWidth_8Bit: return false; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - }; - - class Sdmmc2And4Controller : public SdmmcController { - protected: - virtual bool IsNeedPeriodicDriveStrengthCalibration() override { - return false; - } - - virtual Result PowerOn(BusPower bus_power) override { - /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ - AMS_UNUSED(bus_power); - return ResultSuccess(); - } - - virtual void PowerOff() override { - /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ - } - - virtual Result LowerBusPower() override { - AMS_ABORT("Sdmmc2And4Controller cannot lower bus power\n"); - } - - virtual void SetSchmittTrigger(BusPower bus_power) override { - /* Do nothing. */ - AMS_UNUSED(bus_power); - } - - virtual u8 GetOutboundTapValue() const override { - if (IsSocMariko()) { - return 0xD; - } else { - return 0x8; - } - } - - virtual u8 GetDefaultInboundTapValue() const override { - if (IsSocMariko()) { - return 0xB; - } else { - return 0x0; - } - } - - virtual u8 GetVrefSelValue() const override { - return 0x7; - } - - virtual void SetSlewCodes() override { - /* Do nothing. */ - } - - virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { - /* Ensure that we can write the offsets. */ - AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); - AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); - - /* Sdmmc2And4Controller only supports 1.8v. */ - AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); - - /* Set the offsets. */ - *out_auto_cal_pd_offset = 5; - *out_auto_cal_pu_offset = 5; - } - public: - explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) { - /* ... */ - } - - virtual bool IsSupportedBusPower(BusPower bus_power) const override { - switch (bus_power) { - case BusPower_Off: return true; - case BusPower_1_8V: return true; - case BusPower_3_3V: return false; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - - virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { - switch (bus_width) { - case BusWidth_1Bit: return true; - case BusWidth_4Bit: return true; - case BusWidth_8Bit: return true; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - } - }; - - constexpr inline dd::PhysicalAddress Sdmmc2RegistersPhysicalAddress = UINT64_C(0x700B0200); - - class Sdmmc2Controller : public Sdmmc2And4Controller { - private: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - static constinit inline os::InterruptEventType s_interrupt_event{}; - #endif - protected: - virtual void SetPad() override { - /* Nothing is needed here. */ - } - - virtual ClockResetController::Module GetClockResetModule() const override { - return ClockResetController::Module_Sdmmc2; - } - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual int GetInterruptNumber() const override { - return 47; - } - - virtual os::InterruptEventType *GetInterruptEvent() const override { - return std::addressof(s_interrupt_event); - } - #endif - - virtual void ClearPadParked() override { - if (IsSocMariko()) { - /* Nothing is needed here. */ - } else { - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Clear all MISC2PMC_EMMC2_*_PARK bits. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 0)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); - } - } - - virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { - /* SDMMC2 only supports 1.8v. */ - AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); - - /* Ensure that we can control registers. */ - SdHostStandardController::EnsureControl(); - - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - if (IsSocMariko()) { - /* Write the drv up/down values to APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL. */ - reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 0xA), - APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 0xA)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL); - } else { - /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 0x10), - APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 0x10)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); - } - } - public: - Sdmmc2Controller() : Sdmmc2And4Controller(Sdmmc2RegistersPhysicalAddress) { - /* ... */ - } - }; - - constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600); - - class Sdmmc4Controller : public Sdmmc2And4Controller { - private: - #if defined(AMS_SDMMC_USE_OS_EVENTS) - static constinit inline os::InterruptEventType s_interrupt_event{}; - #endif - protected: - virtual void SetPad() override { - if (IsSocMariko()) { - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Enable Schmitt Trigger in emmc4 iobrick. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, ENABLE)); - - /* Clear CMD_PULLU, CLK_PULLD, DQS_PULLD. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 0), - APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 0), - APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 0)); - - /* Read again to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL); - } else { - /* On Erista, we can just leave the reset value intact. */ - } - } - - virtual ClockResetController::Module GetClockResetModule() const override { - return ClockResetController::Module_Sdmmc4; - } - - #if defined(AMS_SDMMC_USE_OS_EVENTS) - virtual int GetInterruptNumber() const override { - return 63; - } - - virtual os::InterruptEventType *GetInterruptEvent() const override { - return std::addressof(s_interrupt_event); - } - #endif - - virtual void ClearPadParked() override { - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Clear all MISC2PMC_EMMC4_*_PARK bits. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 0)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); - } - - virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { - /* SDMMC4 only supports 1.8v. */ - AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); - - /* Ensure that we can control registers. */ - SdHostStandardController::EnsureControl(); - - /* Get the apb registers address. */ - const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); - - /* Determine the drv up/down values. */ - u8 drvdn, drvup; - if (IsSocMariko()) { - drvdn = 0xA; - drvup = 0xA; - } else { - drvdn = 0x10; - drvup = 0x10; - } - - /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ - reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, drvdn), - APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, drvup)); - - /* Read to be sure our config takes. */ - reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); - } - public: - Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) { - /* ... */ - } - }; - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_select_sdmmc_controller.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_select_sdmmc_controller.hpp deleted file mode 100644 index 9d3b943..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_select_sdmmc_controller.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) - - #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" - - namespace ams::sdmmc::impl { - - using SdmmcControllerForPortSdCard0 = Sdmmc1Controller; - using SdmmcControllerForPortGcAsic0 = Sdmmc2Controller; - using SdmmcControllerForPortMmc0 = Sdmmc4Controller; - - } - -#else - #error "Unknown board for ams::sdmmc::SdmmcController" -#endif diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.cpp deleted file mode 100644 index ae90332..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "sdmmc_timer.hpp" - -namespace ams::sdmmc::impl { - - namespace { - - #if defined(AMS_SDMMC_USE_OS_TIMER) - void SpinWaitMicroSeconds(u32 us) { - const os::Tick timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMicroSeconds(us)) + os::Tick(1); - while (true) { - if (os::GetSystemTick() > timeout_tick) { - break; - } - } - } - - ALWAYS_INLINE void DataSynchronizationBarrier() { - #if defined(ATMOSPHERE_ARCH_ARM64) - __asm__ __volatile__("dsb sy" ::: "memory"); - #elif defined(ATMOSPHERE_ARCH_ARM) - __asm__ __volatile__("dsb" ::: "memory"); - #else - #error "Unknown architecture for DataSynchronizationBarrier" - #endif - } - - ALWAYS_INLINE void InstructionSynchronizationBarrier() { - #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) - __asm__ __volatile__("isb" ::: "memory"); - #else - #error "Unknown architecture for InstructionSynchronizationBarrier" - #endif - } - #endif - - } - - void WaitMicroSeconds(u32 us) { - #if defined(AMS_SDMMC_USE_OS_TIMER) - /* Ensure that nothing is reordered before we wait. */ - DataSynchronizationBarrier(); - InstructionSynchronizationBarrier(); - - /* If the time is small, spinloop, otherwise pend ourselves. */ - if (us < 100) { - SpinWaitMicroSeconds(us); - } else { - os::SleepThread(TimeSpan::FromMicroSeconds(us)); - } - - /* Ensure that nothing is reordered after we wait. */ - DataSynchronizationBarrier(); - InstructionSynchronizationBarrier(); - #elif defined(AMS_SDMMC_USE_UTIL_TIMER) - util::WaitMicroSeconds(us); - #else - #error "Unknown context for ams::sdmmc::impl::WaitMicroSeconds" - #endif - } - - void WaitClocks(u32 num_clocks, u32 clock_frequency_khz) { - AMS_ABORT_UNLESS(clock_frequency_khz > 0); - WaitMicroSeconds(util::DivideUp(1000 * num_clocks, clock_frequency_khz)); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.hpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.hpp deleted file mode 100644 index 5d81495..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/impl/sdmmc_timer.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 - -namespace ams::sdmmc::impl { - - void WaitMicroSeconds(u32 us); - void WaitClocks(u32 num_clocks, u32 clock_frequency_khz); - - #if defined(AMS_SDMMC_USE_OS_TIMER) - class ManualTimer { - private: - os::Tick timeout_tick; - bool is_timed_out; - public: - explicit ManualTimer(u32 ms) { - this->timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(ms)); - this->is_timed_out = false; - } - - bool Update() { - if (this->is_timed_out) { - return false; - } - - this->is_timed_out = os::GetSystemTick() > this->timeout_tick; - return true; - } - }; - #elif defined(AMS_SDMMC_USE_UTIL_TIMER) - class ManualTimer { - private: - u32 timeout_us; - bool is_timed_out; - public: - explicit ManualTimer(u32 ms) { - this->timeout_us = util::GetMicroSeconds() + (ms * 1000); - this->is_timed_out = false; - } - - bool Update() { - if (this->is_timed_out) { - return false; - } - - this->is_timed_out = util::GetMicroSeconds() > this->timeout_us; - return true; - } - }; - #else - #error "Unknown context for ams::sdmmc::ManualTimer" - #endif - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_common.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_common.cpp deleted file mode 100644 index 598e048..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_common.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "impl/sdmmc_i_host_controller.hpp" -#include "impl/sdmmc_i_device_accessor.hpp" -#include "impl/sdmmc_clock_reset_controller.hpp" -#include "impl/sdmmc_port_mmc0.hpp" -#include "impl/sdmmc_port_sd_card0.hpp" -#include "impl/sdmmc_port_gc_asic0.hpp" - -namespace ams::sdmmc { - - namespace { - - impl::IHostController *GetHostController(Port port) { - /* Get the controller. */ - impl::IHostController *host_controller = nullptr; - switch (port) { - case Port_Mmc0: host_controller = impl::GetHostControllerOfPortMmc0(); break; - case Port_SdCard0: host_controller = impl::GetHostControllerOfPortSdCard0(); break; - case Port_GcAsic0: host_controller = impl::GetHostControllerOfPortGcAsic0(); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Ensure it's valid */ - AMS_ABORT_UNLESS(host_controller != nullptr); - return host_controller; - } - - impl::IDeviceAccessor *GetDeviceAccessor(Port port) { - /* Get the accessor. */ - impl::IDeviceAccessor *device_accessor = nullptr; - switch (port) { - case Port_Mmc0: device_accessor = impl::GetDeviceAccessorOfPortMmc0(); break; - case Port_SdCard0: device_accessor = impl::GetDeviceAccessorOfPortSdCard0(); break; - case Port_GcAsic0: device_accessor = impl::GetDeviceAccessorOfPortGcAsic0(); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Ensure it's valid */ - AMS_ABORT_UNLESS(device_accessor != nullptr); - return device_accessor; - } - - } - - - - void Initialize(Port port) { - return GetDeviceAccessor(port)->Initialize(); - } - - void Finalize(Port port) { - return GetDeviceAccessor(port)->Finalize(); - } - -#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) - void SwitchToPcvClockResetControl() { - return impl::ClockResetController::SwitchToPcvControl(); - } -#endif - -#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) - void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - return GetDeviceAccessor(port)->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); - } - - void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { - return GetDeviceAccessor(port)->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); - } -#endif - - void ChangeCheckTransferInterval(Port port, u32 ms) { - return GetHostController(port)->ChangeCheckTransferInterval(ms); - } - void SetDefaultCheckTransferInterval(Port port) { - return GetHostController(port)->SetDefaultCheckTransferInterval(); - } - - Result Activate(Port port) { - return GetDeviceAccessor(port)->Activate(); - } - - void Deactivate(Port port) { - return GetDeviceAccessor(port)->Deactivate(); - } - - Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors) { - return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, dst, dst_size, true); - } - - Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size) { - return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, const_cast(src), src_size, false); - } - - Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { - return GetDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); - } - - Result GetDeviceSpeedMode(SpeedMode *out, Port port) { - return GetDeviceAccessor(port)->GetSpeedMode(out); - } - - Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port) { - return GetDeviceAccessor(port)->GetMemoryCapacity(out_num_sectors); - } - - Result GetDeviceStatus(u32 *out_device_status, Port port) { - return GetDeviceAccessor(port)->GetDeviceStatus(out_device_status); - } - - Result GetDeviceCid(void *out, size_t out_size, Port port) { - return GetDeviceAccessor(port)->GetCid(out, out_size); - } - - Result GetDeviceCsd(void *out, size_t out_size, Port port) { - return GetDeviceAccessor(port)->GetCsd(out, out_size); - } - - void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port) { - return GetDeviceAccessor(port)->GetAndClearErrorInfo(out_error_info, out_log_size, out_log_buffer, log_buffer_size); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_gc_asic.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_gc_asic.cpp deleted file mode 100644 index 2071fd3..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_gc_asic.cpp +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "impl/sdmmc_gc_asic_device_accessor.hpp" -#include "impl/sdmmc_port_mmc0.hpp" -#include "impl/sdmmc_port_sd_card0.hpp" -#include "impl/sdmmc_port_gc_asic0.hpp" - -namespace ams::sdmmc { - - namespace { - - impl::GcAsicDeviceAccessor *GetGcAsicDeviceAccessor(Port port) { - /* Get the accessor. */ - impl::GcAsicDeviceAccessor *gc_asic_device_accessor = nullptr; - switch (port) { - case Port_GcAsic0: gc_asic_device_accessor = impl::GetGcAsicDeviceAccessorOfPortGcAsic0(); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Ensure it's valid */ - AMS_ABORT_UNLESS(gc_asic_device_accessor != nullptr); - return gc_asic_device_accessor; - } - - } - - void PutGcAsicToSleep(Port port) { - return GetGcAsicDeviceAccessor(port)->PutGcAsicToSleep(); - } - - Result AwakenGcAsic(Port port) { - return GetGcAsicDeviceAccessor(port)->AwakenGcAsic(); - } - - Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size) { - return GetGcAsicDeviceAccessor(port)->WriteGcAsicOperation(op_buf, op_buf_size); - } - - Result FinishGcAsicOperation(Port port) { - return GetGcAsicDeviceAccessor(port)->FinishGcAsicOperation(); - } - - Result AbortGcAsicOperation(Port port) { - return GetGcAsicDeviceAccessor(port)->AbortGcAsicOperation(); - } - - Result SleepGcAsic(Port port) { - return GetGcAsicDeviceAccessor(port)->SleepGcAsic(); - } - - Result UpdateGcAsicKey(Port port) { - return GetGcAsicDeviceAccessor(port)->UpdateGcAsicKey(); - } - - void SignalGcRemovedEvent(Port port) { - return GetGcAsicDeviceAccessor(port)->SignalGcRemovedEvent(); - } - - void ClearGcRemovedEvent(Port port) { - return GetGcAsicDeviceAccessor(port)->ClearGcRemovedEvent(); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_mmc.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_mmc.cpp deleted file mode 100644 index 76fc571..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_mmc.cpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "impl/sdmmc_mmc_device_accessor.hpp" -#include "impl/sdmmc_port_mmc0.hpp" -#include "impl/sdmmc_port_sd_card0.hpp" -#include "impl/sdmmc_port_gc_asic0.hpp" - -namespace ams::sdmmc { - - namespace { - - impl::MmcDeviceAccessor *GetMmcDeviceAccessor(Port port) { - /* Get the accessor. */ - impl::MmcDeviceAccessor *mmc_device_accessor = nullptr; - switch (port) { - case Port_Mmc0: mmc_device_accessor = impl::GetMmcDeviceAccessorOfPortMmc0(); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Ensure it's valid */ - AMS_ABORT_UNLESS(mmc_device_accessor != nullptr); - return mmc_device_accessor; - } - - } - - void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size) { - return GetMmcDeviceAccessor(port)->SetMmcWorkBuffer(buffer, buffer_size); - } - - void PutMmcToSleep(Port port) { - return GetMmcDeviceAccessor(port)->PutMmcToSleep(); - } - - void AwakenMmc(Port port) { - return GetMmcDeviceAccessor(port)->AwakenMmc(); - } - - Result SelectMmcPartition(Port port, MmcPartition mmc_partition) { - return GetMmcDeviceAccessor(port)->SelectMmcPartition(mmc_partition); - } - - Result EraseMmc(Port port) { - return GetMmcDeviceAccessor(port)->EraseMmc(); - } - - Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port) { - return GetMmcDeviceAccessor(port)->GetMmcBootPartitionCapacity(out_num_sectors); - } - - Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port) { - return GetMmcDeviceAccessor(port)->GetMmcExtendedCsd(out_buffer, buffer_size); - } - - Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { - return GetMmcDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); - } - -} diff --git a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_sd_card.cpp b/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_sd_card.cpp deleted file mode 100644 index c10a38d..0000000 --- a/libraries/libvapours/source/sdmmc/board/nintendo/nx/sdmmc_sd_card.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2018-2020 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 . - */ -#if defined(ATMOSPHERE_IS_STRATOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_MESOSPHERE) -#include -#elif defined(ATMOSPHERE_IS_EXOSPHERE) -#include -#else -#include -#endif -#include "impl/sdmmc_sd_card_device_accessor.hpp" -#include "impl/sdmmc_port_mmc0.hpp" -#include "impl/sdmmc_port_sd_card0.hpp" -#include "impl/sdmmc_port_gc_asic0.hpp" - -namespace ams::sdmmc { - - namespace { - - impl::SdCardDeviceAccessor *GetSdCardDeviceAccessor(Port port) { - /* Get the accessor. */ - impl::SdCardDeviceAccessor *sd_card_device_accessor = nullptr; - switch (port) { - case Port_SdCard0: sd_card_device_accessor = impl::GetSdCardDeviceAccessorOfPortSdCard0(); break; - AMS_UNREACHABLE_DEFAULT_CASE(); - } - - /* Ensure it's valid */ - AMS_ABORT_UNLESS(sd_card_device_accessor != nullptr); - return sd_card_device_accessor; - } - - } - - void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size) { - return GetSdCardDeviceAccessor(port)->SetSdCardWorkBuffer(buffer, buffer_size); - } - - void PutSdCardToSleep(Port port) { - return GetSdCardDeviceAccessor(port)->PutSdCardToSleep(); - } - - void AwakenSdCard(Port port) { - return GetSdCardDeviceAccessor(port)->AwakenSdCard(); - } - - Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port) { - return GetSdCardDeviceAccessor(port)->GetSdCardProtectedAreaCapacity(out_num_sectors); - } - - Result GetSdCardScr(void *dst, size_t dst_size, Port port) { - return GetSdCardDeviceAccessor(port)->GetSdCardScr(dst, dst_size); - } - - Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function) { - return GetSdCardDeviceAccessor(port)->GetSdCardSwitchFunctionStatus(dst, dst_size, switch_function); - } - - Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode) { - return GetSdCardDeviceAccessor(port)->GetSdCardCurrentConsumption(out_current_consumption, speed_mode); - } - - Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port) { - return GetSdCardDeviceAccessor(port)->GetSdCardSdStatus(dst, dst_size); - } - - Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { - return GetSdCardDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); - } - - bool IsSdCardInserted(Port port) { - return GetSdCardDeviceAccessor(port)->IsSdCardInserted(); - } - - bool IsSdCardRemoved(Port port) { - return GetSdCardDeviceAccessor(port)->IsSdCardRemoved(); - } - - - void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg) { - return GetSdCardDeviceAccessor(port)->RegisterSdCardDetectionEventCallback(callback, arg); - } - - void UnregisterSdCardDetectionEventCallback(Port port) { - return GetSdCardDeviceAccessor(port)->UnregisterSdCardDetectionEventCallback(); - } - -} - diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp new file mode 100644 index 0000000..c9234e9 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() std::scoped_lock lk(this->base_device->device_mutex) + + #else + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() + + #endif + + void BaseDevice::GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const { + AMS_ABORT_UNLESS(out_c_size_mult != nullptr); + AMS_ABORT_UNLESS(out_read_bl_len != nullptr); + + /* Extract C_SIZE_MULT and READ_BL_LEN from the CSD. */ + *out_c_size_mult = static_cast((this->csd[2] >> 7) & 0x7); + *out_read_bl_len = static_cast((this->csd[4] >> 8) & 0xF); + } + + Result BaseDevice::SetLegacyMemoryCapacity() { + /* Get csize from the csd. */ + const u32 c_size = ((this->csd[3] >> 6) & 0x3FF) | ((this->csd[4] & 0x3) << 10); + + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + this->GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Set memory capacity. */ + this->memory_capacity = (c_size + 1) << ((read_bl_len + c_size_mult + 2) - 9); + this->is_valid_memory_capacity = true; + + return ResultSuccess(); + } + + Result BaseDevice::CheckDeviceStatus(u32 r1_resp) const { + /* Check if there are any errors at all. */ + R_SUCCEED_IF((r1_resp & DeviceStatus_ErrorMask) == 0); + + /* Check errors individually. */ + #define AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(__ERROR__) R_UNLESS((r1_resp & DeviceStatus_##__ERROR__) == 0, sdmmc::ResultDeviceStatus##__ERROR__()) + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(ComCrcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(DeviceEccFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(Error); + + if (this->GetDeviceType() == DeviceType_Mmc) { + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(SwitchError); + } + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressMisaligned); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(BlockLenError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseSeqError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseParam); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpViolation); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(LockUnlockFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CidCsdOverwrite); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseReset); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(IllegalCommand); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressOutOfRange); + + #undef AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR + + return ResultSuccess(); + } + + DeviceState BaseDevice::GetDeviceState(u32 r1_resp) const { + return static_cast((r1_resp & DeviceStatus_CurrentStateMask) >> DeviceStatus_CurrentStateShift); + } + + Result BaseDeviceAccessor::IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, command_arg, CommandResponseType, is_busy); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + this->host_controller->GetLastResponse(out_response, sizeof(u32), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (status_ignore_mask != 0) { + *out_response &= ~status_ignore_mask; + } + + /* Check the r1 response for errors. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + R_TRY(this->base_device->CheckDeviceStatus(*out_response)); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(this->base_device->GetDeviceState(*out_response) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandGoIdleState() const { + /* Issue the command. */ + Command command(CommandIndex_GoIdleState, 0, ResponseType_R0, false); + return this->host_controller->IssueCommand(std::addressof(command)); + } + + Result BaseDeviceAccessor::IssueCommandAllSendCid(void *dst, size_t dst_size) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_AllSendCid, 0, CommandResponseType, false); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + this->host_controller->GetLastResponse(static_cast(dst), DeviceCidSize, CommandResponseType); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandSelectCard() const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + return this->IssueCommandAndCheckR1(CommandIndex_SelectCard, arg, true, DeviceState_Unknown); + } + + Result BaseDeviceAccessor::IssueCommandSendCsd(void *dst, size_t dst_size) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_SendCsd, arg, CommandResponseType, false); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + this->host_controller->GetLastResponse(static_cast(dst), DeviceCsdSize, CommandResponseType); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + return this->IssueCommandAndCheckR1(out_device_status, CommandIndex_SendStatus, arg, false, DeviceState_Tran, status_ignore_mask); + } + + Result BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize() const { + /* Issue the command. */ + return this->IssueCommandAndCheckR1(CommandIndex_SetBlockLen, SectorSize, false, DeviceState_Tran); + } + + Result BaseDeviceAccessor::IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Get the argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = this->base_device->IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Get the command index and transfer direction. */ + const u32 command_index = is_read ? CommandIndex_ReadMultipleBlock : CommandIndex_WriteMultipleBlock; + const auto xfer_direction = is_read ? TransferDirection_ReadFromDevice : TransferDirection_WriteToDevice; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, arg, CommandResponseType, false); + TransferData xfer_data(buf, SectorSize, num_sectors, xfer_direction, true, true); + Result result = this->host_controller->IssueCommand(std::addressof(command), std::addressof(xfer_data), out_num_transferred_blocks); + + /* Handle the failure case. */ + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* By default, we'll want to return the result we just got. */ + Result result_to_return = result; + + /* Stop transmission. */ + u32 resp = 0; + result = this->host_controller->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + result = this->base_device->CheckDeviceStatus(resp & (~DeviceStatus_IllegalCommand)); + if (R_FAILED(result)) { + result_to_return = result; + } + } + + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Get the device status. */ + u32 device_status = 0; + result = this->IssueCommandSendStatus(std::addressof(device_status), DeviceStatus_IllegalCommand); + + /* If there's a device status error we don't already have, we prefer to return it. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(result_to_return) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + result_to_return = result; + } + + /* Return the result we chose. */ + return result_to_return; + } + + /* Get the responses. */ + u32 resp, st_resp; + this->host_controller->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + this->host_controller->GetLastStopTransmissionResponse(std::addressof(st_resp), sizeof(st_resp)); + + /* Check the device status. */ + R_TRY(this->base_device->CheckDeviceStatus(resp)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Check the device status. */ + R_TRY(this->base_device->CheckDeviceStatus(st_resp & ~status_ignore_mask)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Issue the read/write command. */ + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + R_TRY(this->IssueCommandMultipleBlock(out_num_transferred_blocks, sector_index, num_sectors, buf, is_read)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(this->base_device != nullptr); + if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Get and check the status. */ + u32 device_status; + R_TRY(this->IssueCommandSendStatus(std::addressof(device_status), status_ignore_mask)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read) { + /* Verify that we can send the command. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + + /* If we want to read zero sectors, there's no work for us to do. */ + R_SUCCEED_IF(num_sectors == 0); + + /* Check that the buffer is big enough for the sectors we're reading. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Read sectors repeatedly until we've read all the ones we want. */ + u32 cur_sector_index = sector_index; + u32 remaining_sectors = num_sectors; + u8 *cur_buf = static_cast(buf); + while (remaining_sectors > 0) { + /* Determine how many sectors we can read in this iteration. */ + u32 cur_sectors = remaining_sectors; + if (sector_index_alignment > 0) { + AMS_ABORT_UNLESS((cur_sector_index % sector_index_alignment) == 0); + + const u32 max_sectors = this->host_controller->GetMaxTransferNumBlocks(); + if (remaining_sectors > max_sectors) { + cur_sectors = max_sectors - (max_sectors % sector_index_alignment); + } + } + + /* Try to perform the read/write. */ + u32 num_transferred_blocks = 0; + Result result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Retry the read/write. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Re-startup the connection, to see if that helps. */ + R_TRY(this->ReStartup()); + + /* Retry the read/write a third time. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Log that we failed after a re-startup. */ + this->PushErrorLog(true, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + return result; + } + + /* Log that we succeeded after a retry. */ + this->PushErrorLog(true, "%s %X %X:0", is_read ? "R" : "W", cur_sector_index, cur_sectors); + + /* Increment the number of error corrections we've done. */ + ++this->num_read_write_error_corrections; + } + } + + /* Update our tracking variables. */ + AMS_ABORT_UNLESS(remaining_sectors >= num_transferred_blocks); + remaining_sectors -= num_transferred_blocks; + cur_sector_index += num_transferred_blocks; + cur_buf += num_transferred_blocks * SectorSize; + } + + return ResultSuccess(); + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void BaseDeviceAccessor::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return this->host_controller->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void BaseDeviceAccessor::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return this->host_controller->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + #endif + + Result BaseDeviceAccessor::Activate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is awake. */ + R_UNLESS(this->base_device->IsAwake(), sdmmc::ResultNotAwakened()); + + /* If the device is already active, we don't need to do anything. */ + R_SUCCEED_IF(this->base_device->IsActive()); + + /* Activate the base device. */ + auto activate_guard = SCOPE_GUARD { ++this->num_activation_failures; }; + R_TRY(this->OnActivate()); + + /* We successfully activated the device. */ + activate_guard.Cancel(); + this->base_device->SetActive(); + + return ResultSuccess(); + } + + void BaseDeviceAccessor::Deactivate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Deactivate the base device. */ + if (this->base_device->IsActive()) { + this->host_controller->Shutdown(); + this->base_device->Deactivate(); + } + } + + Result BaseDeviceAccessor::ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Perform the read/write. */ + auto rw_guard = SCOPE_GUARD { ++this->num_read_write_failures; }; + R_TRY(this->OnReadWrite(sector_index, num_sectors, buffer, buffer_size, is_read)); + + /* We successfully performed the read/write. */ + rw_guard.Cancel(); + return ResultSuccess(); + } + + Result BaseDeviceAccessor::CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the current speed mode/bus width. */ + *out_speed_mode = this->host_controller->GetSpeedMode(); + *out_bus_width = this->host_controller->GetBusWidth(); + + /* Verify that we can get the status. */ + R_TRY(this->host_controller->GetInternalStatus()); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetMemoryCapacity(u32 *out_sectors) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the capacity. */ + AMS_ABORT_UNLESS(out_sectors != nullptr); + *out_sectors = this->base_device->GetMemoryCapacity(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetDeviceStatus(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the status. */ + R_TRY(this->IssueCommandSendStatus(out, 0)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetOcr(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the ocr. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = this->base_device->GetOcr(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetRca(u16 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the rca. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = this->base_device->GetRca(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetCid(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the cid. */ + this->base_device->GetCid(out, size); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetCsd(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the csd. */ + this->base_device->GetCsd(out, size); + + return ResultSuccess(); + } + + void BaseDeviceAccessor::GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Set the output error info. */ + AMS_ABORT_UNLESS(out_error_info != nullptr); + out_error_info->num_activation_failures = this->num_activation_failures; + out_error_info->num_activation_error_corrections = this->num_activation_error_corrections; + out_error_info->num_read_write_failures = this->num_read_write_failures; + out_error_info->num_read_write_error_corrections = this->num_read_write_error_corrections; + this->ClearErrorInfo(); + + /* Check if we should write logs. */ + if (out_log_size == nullptr) { + return; + } + + /* Check if we can write logs. */ + if (out_log_buffer == nullptr || log_buffer_size == 0) { + *out_log_size = 0; + return; + } + + /* Get and clear our logs. */ + #if defined(AMS_SDMMC_USE_LOGGER) + { + if (this->error_logger.HasLog()) { + this->PushErrorTimeStamp(); + + *out_log_size = this->error_logger.GetAndClearLogs(out_log_buffer, log_buffer_size); + } else { + *out_log_size = 0; + } + } + #else + { + *out_log_size = 0; + } + #endif + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp new file mode 100644 index 0000000..8cb0490 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_USE_LOGGER) + class Logger { + private: + static constexpr size_t LogLengthMax = 0x20; + static constexpr size_t LogCountMax = 0x10; + private: + char logs[LogCountMax][LogLengthMax]; + int log_index; + private: + void Clear() { + for (size_t i = 0; i < LogCountMax; ++i) { + this->logs[i][0] = '\0'; + } + this->log_index = 0; + } + + size_t Pop(char *dst, size_t dst_size) { + /* Decrease log index. */ + if ((--this->log_index) < 0) { + this->log_index = LogCountMax - 1; + } + + /* Check if we have a log. */ + if (this->logs[this->log_index][0] == '\0') { + return 0; + } + + /* Copy log to output. */ + const int len = ::ams::util::Strlcpy(dst, this->logs[this->log_index], dst_size); + + /* Clear the log we copied. */ + this->logs[this->log_index][0] = '\0'; + + return static_cast(len); + } + + public: + Logger() { + this->Clear(); + } + + void Push(const char *fmt, std::va_list vl) { + /* Format the log into the current buffer. */ + ::ams::util::TVSNPrintf(this->logs[this->log_index], LogLengthMax, fmt, vl); + + /* Update our log index. */ + if ((++this->log_index) >= static_cast(LogCountMax)) { + this->log_index = 0; + } + } + + void Push(const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + this->Push(fmt, vl); + va_end(vl); + } + + bool HasLog() const { + const int index = this->log_index > 0 ? this->log_index - 1 : static_cast(LogCountMax - 1); + return this->logs[index][0] != '\0'; + } + + size_t GetAndClearLogs(char *dst, size_t dst_size) { + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size > 0); + + /* Pop logs until we run out of them. */ + size_t total_len = 0; + while (true) { + /* Pop the current log. */ + const size_t cur_len = this->Pop(dst + total_len, dst_size - total_len); + if (cur_len == 0) { + break; + } + + /* Check if the log exceeded the buffer size. */ + if (total_len + cur_len + 1 >= dst_size) { + break; + } + + /* Advance the total length. */ + total_len += cur_len; + + /* Check if there's space for our separator. */ + if (total_len + 3 >= dst_size) { + break; + } + + dst[total_len + 0] = ','; + dst[total_len + 1] = ' '; + total_len += 2; + } + + /* Ensure that the complete log fits in the buffer. */ + if (total_len >= dst_size) { + total_len = dst_size - 1; + } + + /* Ensure null termination. */ + dst[total_len] = '\0'; + + /* Clear any remaining logs. */ + this->Clear(); + + /* Return the length of the logs, including null terminator. */ + return total_len + 1; + } + }; + #endif + + enum DeviceType { + DeviceType_Mmc = 0, + DeviceType_SdCard = 1, + DeviceType_GcAsic = 2, + }; + + enum DeviceState { + DeviceState_Idle = 0, + DeviceState_Ready = 1, + DeviceState_Ident = 2, + DeviceState_Stby = 3, + DeviceState_Tran = 4, + DeviceState_Data = 5, + DeviceState_Rcv = 6, + DeviceState_Prg = 7, + DeviceState_Dis = 8, + DeviceState_Rsvd0 = 9, + DeviceState_Rsvd1 = 10, + DeviceState_Rsvd2 = 11, + DeviceState_Rsvd3 = 12, + DeviceState_Rsvd4 = 13, + DeviceState_Rsvd5 = 14, + DeviceState_RsvdIoMode = 15, + DeviceState_Unknown = 16, + }; + + enum DeviceStatus : u32 { + DeviceStatus_AkeSeqError = (1u << 3), + DeviceStatus_AppCmd = (1u << 5), + DeviceStatus_SwitchError = (1u << 7), + DeviceStatus_EraseReset = (1u << 13), + DeviceStatus_WpEraseSkip = (1u << 15), + DeviceStatus_CidCsdOverwrite = (1u << 16), + DeviceStatus_Error = (1u << 19), + DeviceStatus_CcError = (1u << 20), + DeviceStatus_DeviceEccFailed = (1u << 21), + DeviceStatus_IllegalCommand = (1u << 22), + DeviceStatus_ComCrcError = (1u << 23), + DeviceStatus_LockUnlockFailed = (1u << 24), + DeviceStatus_WpViolation = (1u << 26), + DeviceStatus_EraseParam = (1u << 27), + DeviceStatus_EraseSeqError = (1u << 28), + DeviceStatus_BlockLenError = (1u << 29), + DeviceStatus_AddressMisaligned = (1u << 30), + DeviceStatus_AddressOutOfRange = (1u << 31), + + DeviceStatus_CurrentStateShift = 9, + DeviceStatus_CurrentStateMask = (0b1111u << DeviceStatus_CurrentStateShift), + + DeviceStatus_ErrorMask = (DeviceStatus_SwitchError | + DeviceStatus_EraseReset | + DeviceStatus_WpEraseSkip | + DeviceStatus_CidCsdOverwrite | + DeviceStatus_Error | + DeviceStatus_CcError | + DeviceStatus_DeviceEccFailed | + DeviceStatus_IllegalCommand | + DeviceStatus_ComCrcError | + DeviceStatus_LockUnlockFailed | + DeviceStatus_WpViolation | + DeviceStatus_EraseParam | + DeviceStatus_EraseSeqError | + DeviceStatus_BlockLenError | + DeviceStatus_AddressMisaligned | + DeviceStatus_AddressOutOfRange), + }; + + class BaseDevice { + private: + u32 ocr; + u8 cid[DeviceCidSize]; + u16 csd[DeviceCsdSize / sizeof(u16)]; + u32 memory_capacity; + bool is_high_capacity; + bool is_valid_ocr; + bool is_valid_cid; + bool is_valid_csd; + bool is_valid_high_capacity; + bool is_valid_memory_capacity; + bool is_active; + bool is_awake; + public: + #if defined(AMS_SDMMC_THREAD_SAFE) + mutable os::Mutex device_mutex; + public: + BaseDevice() : device_mutex(true) + #else + BaseDevice() + #endif + { + this->is_awake = true; + this->ocr = 0; + this->memory_capacity = 0; + this->is_high_capacity = false; + this->OnDeactivate(); + } + + void OnDeactivate() { + this->is_active = false; + this->is_valid_ocr = false; + this->is_valid_cid = false; + this->is_valid_csd = false; + this->is_valid_high_capacity = false; + this->is_valid_memory_capacity = false; + } + + bool IsAwake() const { + return this->is_awake; + } + + void Awaken() { + this->is_awake = true; + } + + void PutToSleep() { + this->is_awake = false; + } + + bool IsActive() const { + return this->is_active; + } + + void SetActive() { + this->is_active = true; + } + + virtual void Deactivate() { + this->OnDeactivate(); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const = 0; + #endif + + virtual DeviceType GetDeviceType() const = 0; + virtual u16 GetRca() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void InitializeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::InitializeEvent(removed_event, false, os::EventClearMode_ManualClear); + } + } + + void FinalizeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::FinalizeEvent(removed_event); + } + } + + void SignalRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::SignalEvent(removed_event); + } + } + + void ClearRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::ClearEvent(removed_event); + } + } + + bool IsRemoved() const { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + return os::TryWaitEvent(removed_event); + } + return false; + } + #endif + + Result CheckRemoved() const { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + return ResultSuccess(); + } + + Result CheckAccessible() const { + /* Check awake. */ + R_UNLESS(this->IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check active. */ + R_UNLESS(this->IsActive(), sdmmc::ResultNotActivated()); + + /* Check removed. */ + R_TRY(this->CheckRemoved()); + + return ResultSuccess(); + } + + void SetHighCapacity(bool en) { + this->is_high_capacity = en; + this->is_valid_high_capacity = true; + } + + bool IsHighCapacity() const { + AMS_ABORT_UNLESS(this->is_valid_high_capacity); + return this->is_high_capacity; + } + + void SetOcr(u32 o) { + this->ocr = o; + this->is_valid_ocr = true; + } + + u32 GetOcr() const { + AMS_ABORT_UNLESS(this->is_valid_ocr); + return this->ocr; + } + + void SetCid(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCidSize); + std::memcpy(this->cid, src, DeviceCidSize); + this->is_valid_cid = true; + } + + void GetCid(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(this->is_valid_cid); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + std::memcpy(dst, this->cid, DeviceCidSize); + } + + void SetCsd(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCsdSize); + std::memcpy(this->csd, src, DeviceCsdSize); + this->is_valid_csd = true; + } + + void GetCsd(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(this->is_valid_csd); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + std::memcpy(dst, this->csd, DeviceCsdSize); + } + + void SetMemoryCapacity(u32 num_sectors) { + this->memory_capacity = num_sectors; + this->is_valid_memory_capacity = true; + } + + u32 GetMemoryCapacity() const { + AMS_ABORT_UNLESS(this->is_valid_memory_capacity); + return this->memory_capacity; + } + + void GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const; + Result SetLegacyMemoryCapacity(); + + Result CheckDeviceStatus(u32 r1_resp) const; + DeviceState GetDeviceState(u32 r1_resp) const; + }; + + class BaseDeviceAccessor : public IDeviceAccessor { + private: + IHostController *host_controller; + BaseDevice *base_device; + u32 num_activation_failures; + u32 num_activation_error_corrections; + u32 num_read_write_failures; + u32 num_read_write_error_corrections; + #if defined(AMS_SDMMC_USE_LOGGER) + Logger error_logger; + #endif + private: + void ClearErrorInfo() { + this->num_activation_failures = 0; + this->num_activation_error_corrections = 0; + this->num_read_write_failures = 0; + this->num_read_write_error_corrections = 0; + } + protected: + explicit BaseDeviceAccessor(IHostController *hc) : host_controller(hc), base_device(nullptr) { + this->ClearErrorInfo(); + } + + IHostController *GetHostController() const { + return this->host_controller; + } + + void SetDevice(BaseDevice *bd) { + this->base_device = bd; + } + + Result CheckRemoved() const { + return this->base_device->CheckRemoved(); + } + + Result IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const; + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + u32 dummy; + return this->IssueCommandAndCheckR1(std::addressof(dummy), command_index, command_arg, is_busy, expected_state, status_ignore_mask); + } + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state) const { + return this->IssueCommandAndCheckR1(command_index, command_arg, is_busy, expected_state, 0); + } + + Result IssueCommandGoIdleState() const; + Result IssueCommandAllSendCid(void *dst, size_t dst_size) const; + Result IssueCommandSelectCard() const; + Result IssueCommandSendCsd(void *dst, size_t dst_size) const; + Result IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const; + + Result IssueCommandSendStatus(u32 status_ignore_mask) const { + u32 dummy; + return this->IssueCommandSendStatus(std::addressof(dummy), status_ignore_mask); + } + + Result IssueCommandSendStatus() const { + return this->IssueCommandSendStatus(0); + } + + Result IssueCommandSetBlockLenToSectorSize() const; + Result IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read); + + void IncrementNumActivationErrorCorrections() { + ++this->num_activation_error_corrections; + } + + void PushErrorTimeStamp() { + #if defined(AMS_SDMMC_USE_LOGGER) + { + this->error_logger.Push("%u", static_cast(os::ConvertToTimeSpan(os::GetSystemTick()).GetSeconds())); + } + #endif + } + + void PushErrorLog(bool with_timestamp, const char *fmt, ...) { + #if defined(AMS_SDMMC_USE_LOGGER) + { + std::va_list vl; + va_start(vl, fmt); + this->error_logger.Push(fmt, vl); + va_end(vl); + + if (with_timestamp) { + this->PushErrorTimeStamp(); + } + } + #else + { + AMS_UNUSED(with_timestamp, fmt); + } + #endif + } + + virtual Result OnActivate() = 0; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) = 0; + virtual Result ReStartup() = 0; + public: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual Result Activate() override; + virtual void Deactivate() override; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) override; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) override; + + virtual Result GetMemoryCapacity(u32 *out_sectors) const override; + virtual Result GetDeviceStatus(u32 *out) const override; + virtual Result GetOcr(u32 *out) const override; + virtual Result GetRca(u16 *out) const override; + virtual Result GetCid(void *out, size_t size) const override; + virtual Result GetCsd(void *out, size_t size) const override; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) override; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp new file mode 100644 index 0000000..1790380 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.hpp" +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController { + + namespace { + + constinit bool g_is_module_initialized[Module_Count] = {}; + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_module_mutex; + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() std::scoped_lock lk(g_module_mutex) + + #else + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + constinit bool g_is_pcv_control = false; + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if (g_is_pcv_control) + + #else + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if constexpr (false) + + #endif + + } + + void Initialize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Mark the module as initialized. */ + g_is_module_initialized[module] = true; + + /* Initialize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Initialize(module); + } else { + ClockResetController::reg::Initialize(module); + } + } + + void Finalize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Finalize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Finalize(module); + } else { + ClockResetController::reg::Finalize(module); + } + + /* Mark the module as finalized. */ + g_is_module_initialized[module] = false; + } + + bool IsAvailable(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::IsAvailable(module); + } else { + return ClockResetController::reg::IsAvailable(module); + } + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl() { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* If we're already using pcv control, we don't need to do anything. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return; + } + + /* Finalize all modules. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::reg::Finalize(static_cast(i)); + } + } + + /* Mark that we've switched to pcv control. */ + + /* Initialize modules using pcv control. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::pcv::Initialize(static_cast(i)); + } + } + } + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } else { + return ClockResetController::reg::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } + } + + void AssertReset(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::AssertReset(module); + } else { + return ClockResetController::reg::AssertReset(module); + } + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::ReleaseReset(module, target_frequency_khz); + } else { + return ClockResetController::reg::ReleaseReset(module, target_frequency_khz); + } + } + + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp new file mode 100644 index 0000000..29ac276 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::sdmmc::impl::ClockResetController { + + enum Module { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + Module_Sdmmc1 = 0, + Module_Sdmmc2 = 1, + Module_Sdmmc3 = 2, + Module_Sdmmc4 = 3, + #endif + + Module_Count, + }; + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl(); + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp new file mode 100644 index 0000000..8d8c39d --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not implemented"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not implemented"); + } + + #else + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not supported"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not supported"); + } + + #endif + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp new file mode 100644 index 0000000..85144bf --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp new file mode 100644 index 0000000..3a49428 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_init_mutex; + + #define AMS_SDMMC_LOCK_INIT_MUTEX() std::scoped_lock lk(g_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_INIT_MUTEX() + + #endif + + constinit bool g_is_initialized = false; + + struct ModuleInfo { + u32 target_frequency_khz; + u32 actual_frequency_khz; + }; + + constinit ModuleInfo g_module_infos[Module_Count] = {}; + + constexpr inline dd::PhysicalAddress ClockResetControllerRegistersPhysicalAddress = UINT64_C(0x60006000); + constexpr inline size_t ClockResetControllerRegistersSize = 4_KB; + + constinit uintptr_t g_clkrst_registers_address = 0; + + [[maybe_unused]] void InitializePllc4() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Check if PLLC4_BASE has the expected value; if it does, we have nothing to do. */ + constexpr u32 ExpectedPllc4Base = 0x58006804; + if (ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE) == ExpectedPllc4Base) { + return; + } + + /* Disable PLLC4_ENABLE, if it's currently set. */ + if (ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE))) { + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, DISABLE)); + } + + /* Operate on the register with PLLC4_ENABLE cleared. */ + { + /* Clear IDDQ, read to be sure it takes, wait 5 us. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_IDDQ, OFF)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + WaitMicroSeconds(5); + + /* Write the expected value sans IDDQ/PLLC4_ENABLE. */ + constexpr u32 ExpectedPllc4BaseMask = ~ams::reg::EncodeMask(CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_ENABLE), + CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_IDDQ)); + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, ExpectedPllc4Base & ExpectedPllc4BaseMask); + } + + /* Write PLLC4_ENABLE, and read to be sure our configuration takes. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + + /* Wait up to 1s for changes to take. */ + { + ManualTimer timer(1000); + while (true) { + /* Check if we're done. */ + if (!ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_LOCK, NOT_LOCK))) { + break; + } + + /* Check that we haven't timed out. */ + AMS_ABORT_UNLESS(timer.Update()); + } + } + } + + void InitializeLegacyTmClk() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Configure the legacy tm clock as 12MHz. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_LEGACY_TM_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 66)); + + /* Enable clock to the legacy tm. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_LEGACY_TM, ENABLE)); + } + + bool IsResetReleased(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the reset bit from RST_DEVICES_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC1_RST, DISABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC2_RST, DISABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_REG_BITS_ENUM(RST_DEVICES_U_SWR_SDMMC3_RST, DISABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC4_RST, DISABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsClockEnabled(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the enable bit from CLK_OUT_ENB_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockSourceSdmmc(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_actual_frequency_khz != nullptr); + + /* Determine frequency/divisor. */ + u32 clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLP_OUT0)); + u8 n; + switch (target_frequency_khz) { + case 25'000: + *out_actual_frequency_khz = 24'728; + n = 31; + break; + case 26'000: + *out_actual_frequency_khz = 25'500; + n = 30; + break; + case 40'800: + *out_actual_frequency_khz = 40'800; + n = 18; + break; + case 50'000: + *out_actual_frequency_khz = 48'000; + n = 15; + break; + case 52'000: + *out_actual_frequency_khz = 51'000; + n = 14; + break; + case 100'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 99'840; + n = 2; + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + #else + *out_actual_frequency_khz = 90'667; + n = 7; + #endif + break; + case 200'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 199'680; + n = 0; + if (module == Module_Sdmmc2 || module == Module_Sdmmc4) { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, PLLC4_OUT2_LJ)); + } else { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + } + #else + *out_actual_frequency_khz = 163'200; + n = 3; + #endif + break; + case 208'000: + *out_actual_frequency_khz = 204'000; + n = 2; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set frequencies in module info. */ + g_module_infos[module].target_frequency_khz = target_frequency_khz; + g_module_infos[module].actual_frequency_khz = *out_actual_frequency_khz; + + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Update the clock source. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1, clk_m | static_cast(n)); break; + case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast(n)); break; + case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast(n)); break; + case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast(n)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EnsureControl(Module module) { + /* Read from RST_DEVICES_* to be sure previous configuration takes. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc2: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc3: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U); break; + case Module_Sdmmc4: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void Initialize(Module module) { + /* Initialization isn't module specific. */ + AMS_UNUSED(module); + + /* Acquire exclusive access to the initialization lock. */ + AMS_SDMMC_LOCK_INIT_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (g_is_initialized) { + return; + } + + /* Clear module infos. */ + std::memset(g_module_infos, 0, sizeof(g_module_infos)); + + /* Get the registers address. */ + g_clkrst_registers_address = dd::QueryIoMapping(ClockResetControllerRegistersPhysicalAddress, ClockResetControllerRegistersSize); + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Perform register initialization. */ + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + InitializePllc4(); + #endif + InitializeLegacyTmClk(); + + /* Mark that we've initialized. */ + g_is_initialized = true; + } + + void Finalize(Module module) { + /* Nothing is needed for finalization. */ + AMS_UNUSED(module); + } + + bool IsAvailable(Module module) { + return IsResetReleased(module) && IsClockEnabled(module); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* If we're not changing the clock frequency, we don't need to do anything. */ + if (target_frequency_khz == g_module_infos[module].target_frequency_khz) { + *out_actual_frequency_khz = g_module_infos[module].actual_frequency_khz; + return; + } + + /* Temporarily disable clock. */ + const bool clock_enabled = IsClockEnabled(module); + if (clock_enabled) { + ClearClockEnable(module); + } + + /* Set the clock source. */ + SetClockSourceSdmmc(out_actual_frequency_khz, module, target_frequency_khz); + + /* Re-enable clock, if we should. */ + if (clock_enabled) { + SetClockEnable(module); + } + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void AssertReset(Module module) { + /* Set reset and disable clock. */ + SetReset(module); + ClearClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + /* Disable clock if it's enabled. */ + if (IsClockEnabled(module)) { + ClearClockEnable(module); + } + + /* Set reset. */ + SetReset(module); + + /* Set the clock source. */ + u32 actual_source_frequency_khz; + SetClockSourceSdmmc(std::addressof(actual_source_frequency_khz), module, target_frequency_khz); + + /* Enable clock. */ + SetClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + + /* Wait 100 clocks. */ + WaitClocks(100, actual_source_frequency_khz); + + /* Clear reset. */ + ClearReset(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp new file mode 100644 index 0000000..4043037 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp new file mode 100644 index 0000000..3529b85 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" + +namespace ams::sdmmc::impl { + + bool DeviceDetector::IsCurrentInserted() { + return gpio::GetValue(std::addressof(this->gpio_pad_session)) == this->inserted_gpio_value; + } + + void DeviceDetector::HandleDeviceStatus(bool prev_inserted, bool cur_inserted) { + if (!prev_inserted && !cur_inserted) { + /* Not inserted -> Not inserted, nothing to do. */ + } else if (!prev_inserted && cur_inserted) { + /* Card was inserted. */ + if (this->callback_info.inserted_callback != nullptr) { + this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); + } + } else if (prev_inserted && !cur_inserted) { + /* Card was removed. */ + if (this->callback_info.removed_callback != nullptr) { + this->callback_info.removed_callback(this->callback_info.removed_callback_arg); + } + } else /* if (prev_inserted && cur_inserted) */ { + /* Card was removed, and then inserted. */ + if (this->callback_info.removed_callback != nullptr) { + this->callback_info.removed_callback(this->callback_info.removed_callback_arg); + } + + if (this->callback_info.inserted_callback != nullptr) { + this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); + } + } + } + + void DeviceDetector::DetectorThread() { + /* Initialize the gpio session. */ + sm::DoWithSession([] { gpio::Initialize(); }); + gpio::OpenSession(std::addressof(this->gpio_pad_session), this->gpio_device_code); + gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Input); + gpio::SetDebounceTime(std::addressof(this->gpio_pad_session), this->gpio_debounce_ms); + gpio::SetDebounceEnabled(std::addressof(this->gpio_pad_session), true); + gpio::SetInterruptMode(std::addressof(this->gpio_pad_session), gpio::InterruptMode_AnyEdge); + + /* Get the gpio session's interrupt event. */ + os::SystemEventType gpio_event; + R_ABORT_UNLESS(gpio::BindInterrupt(std::addressof(gpio_event), std::addressof(this->gpio_pad_session))); + + /* Initialize and link waitable holders. */ + os::WaitableManagerType wait_manager; + os::WaitableHolderType detector_thread_end_holder; + os::WaitableHolderType request_sleep_wake_event_holder; + os::WaitableHolderType gpio_event_holder; + os::InitializeWaitableManager(std::addressof(wait_manager)); + os::InitializeWaitableHolder(std::addressof(detector_thread_end_holder), std::addressof(this->detector_thread_end_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(detector_thread_end_holder)); + os::InitializeWaitableHolder(std::addressof(request_sleep_wake_event_holder), std::addressof(this->request_sleep_wake_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(request_sleep_wake_event_holder)); + os::InitializeWaitableHolder(std::addressof(gpio_event_holder), std::addressof(gpio_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); + + /* Wait before detecting the initial state of the card. */ + os::SleepThread(TimeSpan::FromMilliSeconds(this->gpio_debounce_ms)); + bool cur_inserted = this->IsCurrentInserted(); + this->is_prev_inserted = cur_inserted; + + /* Set state as awake. */ + this->state = State_Awake; + os::SignalEvent(std::addressof(this->ready_device_status_event)); + + /* Enable interrupts to be informed of device status. */ + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); + + /* Wait, servicing our events. */ + while (true) { + /* Get the signaled holder. */ + os::WaitableHolderType *signaled_holder = os::WaitAny(std::addressof(wait_manager)); + + /* Process the holder. */ + bool insert_change = false; + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(this->detector_thread_end_event)); + this->state = State_Finalized; + break; + } else if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) { + /* A request for us to sleep/wake has come in, so we'll acknowledge it. */ + os::ClearEvent(std::addressof(this->request_sleep_wake_event)); + this->state = State_Sleep; + os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); + + /* Temporarily unlink our interrupt event. */ + os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); + + /* Wait to be signaled. */ + signaled_holder = os::WaitAny(std::addressof(wait_manager)); + + /* Link our interrupt event back in. */ + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); + + /* We're awake again. Either because we should exit, or because we were asked to wake up. */ + os::ClearEvent(std::addressof(this->request_sleep_wake_event)); + this->state = State_Awake; + os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); + + /* If we were asked to exit, do so. */ + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(this->detector_thread_end_event)); + this->state = State_Finalized; + break; + } else /* if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) */ { + if ((this->force_detection) || + (({ bool active; R_SUCCEEDED(gpio::IsWakeEventActive(std::addressof(active), this->gpio_device_code)) && active; })) || + (os::TryWaitSystemEvent(std::addressof(gpio_event))) || + (this->is_prev_inserted != this->IsCurrentInserted())) + { + insert_change = true; + } + } + } else /* if (signaled_holder == std::addressof(gpio_event_holder)) */ { + /* An event was detected. */ + insert_change = true; + } + + /* Handle an insert change, if one occurred. */ + if (insert_change) { + /* Call the relevant callback, if we have one. */ + if (this->device_detection_event_callback != nullptr) { + this->device_detection_event_callback(this->device_detection_event_callback_arg); + } + + /* Clear the interrupt event. */ + os::ClearSystemEvent(std::addressof(gpio_event)); + gpio::ClearInterruptStatus(std::addressof(this->gpio_pad_session)); + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); + + /* Update insertion status. */ + cur_inserted = this->IsCurrentInserted(); + this->HandleDeviceStatus(this->is_prev_inserted, cur_inserted); + this->is_prev_inserted = cur_inserted; + } + } + + /* Disable interrupts to our gpio event. */ + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), false); + + /* Finalize and unlink waitable holders. */ + os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); + os::FinalizeWaitableHolder(std::addressof(gpio_event_holder)); + os::UnlinkWaitableHolder(std::addressof(request_sleep_wake_event_holder)); + os::FinalizeWaitableHolder(std::addressof(request_sleep_wake_event_holder)); + os::UnlinkWaitableHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeWaitableHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeWaitableManager(std::addressof(wait_manager)); + + /* Finalize the gpio session. */ + gpio::UnbindInterrupt(std::addressof(this->gpio_pad_session)); + gpio::CloseSession(std::addressof(this->gpio_pad_session)); + gpio::Finalize(); + } + + void DeviceDetector::Initialize(CallbackInfo *ci) { + /* Transition our state from finalized to initializing. */ + AMS_ABORT_UNLESS(this->state == State_Finalized); + this->state = State_Initializing; + + /* Set our callback infos. */ + this->callback_info = *ci; + + /* Initialize our events. */ + os::InitializeEvent(std::addressof(this->ready_device_status_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->request_sleep_wake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->acknowledge_sleep_awake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->detector_thread_end_event), false, os::EventClearMode_ManualClear); + + /* Create and start the detector thread. */ + os::CreateThread(std::addressof(this->detector_thread), DetectorThreadEntry, this, this->detector_thread_stack, sizeof(this->detector_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(sdmmc, DeviceDetector)); + os::SetThreadNamePointer(std::addressof(this->detector_thread), AMS_GET_SYSTEM_THREAD_NAME(sdmmc, DeviceDetector)); + os::StartThread(std::addressof(this->detector_thread)); + } + + void DeviceDetector::Finalize() { + /* Ensure we're not already finalized. */ + AMS_ABORT_UNLESS(this->state != State_Finalized); + + /* Signal event to end the detector thread. */ + os::SignalEvent(std::addressof(this->detector_thread_end_event)); + os::WaitThread(std::addressof(this->detector_thread)); + + /* Finalize thread and events. */ + os::DestroyThread(std::addressof(this->detector_thread)); + os::FinalizeEvent(std::addressof(this->ready_device_status_event)); + os::FinalizeEvent(std::addressof(this->request_sleep_wake_event)); + os::FinalizeEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::FinalizeEvent(std::addressof(this->detector_thread_end_event)); + } + + void DeviceDetector::PutToSleep() { + /* Signal request, wait for acknowledgement. */ + os::SignalEvent(std::addressof(this->request_sleep_wake_event)); + os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); + } + + void DeviceDetector::Awaken(bool force_det) { + /* Signal request, wait for acknowledgement. */ + this->force_detection = force_det; + os::SignalEvent(std::addressof(this->request_sleep_wake_event)); + os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); + } + + bool DeviceDetector::IsInserted() { + bool inserted = false; + + switch (this->state) { + case State_Initializing: + /* Wait for us to know whether the device is inserted. */ + os::WaitEvent(std::addressof(this->ready_device_status_event)); + [[fallthrough]]; + case State_Awake: + /* Get whether the device is currently inserted. */ + inserted = this->IsCurrentInserted(); + break; + case State_Sleep: + case State_Finalized: + /* Get whether the device was inserted when we last knew. */ + inserted = this->is_prev_inserted; + break; + } + + return inserted; + } + + void DeviceDetector::RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + this->device_detection_event_callback_arg = arg; + this->device_detection_event_callback = cb; + } + + void DeviceDetector::UnregisterDetectionEventCallback() { + this->device_detection_event_callback = nullptr; + } + +} + +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp new file mode 100644 index 0000000..a61f050 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2020 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 +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include + +namespace ams::sdmmc::impl { + + using InsertedCallback = void (*)(void *); + using RemovedCallback = void (*)(void *); + + struct CallbackInfo { + InsertedCallback inserted_callback; + void *inserted_callback_arg; + RemovedCallback removed_callback; + void *removed_callback_arg; + }; + + class DeviceDetector { + private: + enum State { + State_Initializing = 0, + State_Awake = 1, + State_Sleep = 2, + State_Finalized = 3, + }; + private: + alignas(os::ThreadStackAlignment) u8 detector_thread_stack[8_KB]; + State state; + bool is_prev_inserted; + bool force_detection; + os::ThreadType detector_thread; + os::EventType detector_thread_end_event; + os::EventType request_sleep_wake_event; + os::EventType acknowledge_sleep_awake_event; + os::EventType ready_device_status_event; + + DeviceCode gpio_device_code; + gpio::GpioValue inserted_gpio_value; + u32 gpio_debounce_ms; + gpio::GpioPadSession gpio_pad_session; + + CallbackInfo callback_info; + + DeviceDetectionEventCallback device_detection_event_callback; + void *device_detection_event_callback_arg; + private: + static void DetectorThreadEntry(void *arg) { + reinterpret_cast(arg)->DetectorThread(); + } + + void DetectorThread(); + bool IsCurrentInserted(); + void HandleDeviceStatus(bool prev_inserted, bool cur_inserted); + public: + explicit DeviceDetector(DeviceCode dc, gpio::GpioValue igv, u32 gd) + : gpio_device_code(dc), inserted_gpio_value(igv), gpio_debounce_ms(gd) + { + this->state = State_Finalized; + this->is_prev_inserted = false; + this->force_detection = false; + this->callback_info = {}; + this->device_detection_event_callback = nullptr; + this->device_detection_event_callback_arg = nullptr; + } + + void Initialize(CallbackInfo *ci); + void Finalize(); + + void PutToSleep(); + void Awaken(bool force_det); + + u32 GetDebounceMilliSeconds() const { + return this->gpio_debounce_ms; + } + + bool IsInserted(); + + void RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg); + void UnregisterDetectionEventCallback(); + }; + +} + +#endif \ No newline at end of file diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp new file mode 100644 index 0000000..a1ebe02 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_gc_asic_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() std::scoped_lock lk(this->gc_asic_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() R_UNLESS(!this->gc_asic_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() + + #endif + + Result GcAsicDeviceAccessor::IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const { + /* Validate the operation buffer. */ + AMS_ABORT_UNLESS(op_buf != nullptr); + AMS_ABORT_UNLESS(op_buf_size >= GcAsicOperationSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_GcAsicWriteOperation, 0, CommandResponseType, false); + TransferData xfer_data(const_cast(op_buf), GcAsicOperationSize, 1, TransferDirection_WriteToDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + Result result = hc->IssueCommand(std::addressof(command), std::addressof(xfer_data)); + if (R_FAILED(result)) { + /* We failed to write operation. Check if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Determine what result we should return. */ + Result return_result = result; + { + /* Issue a stop transmission command. */ + u32 resp = 0; + result = hc->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + /* If we successfully stopped transmission but have an error status, we prefer to return that. */ + result = this->gc_asic_device.CheckDeviceStatus(resp); + if (R_FAILED(result)) { + return_result = result; + } + } + + /* Check again if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Request device status. */ + u32 device_status; + result = BaseDeviceAccessor::IssueCommandSendStatus(std::addressof(device_status), 0); + + /* If we got a device status error here and we didn't previously, we prefer to return that. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(return_result) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + return_result = result; + } + } + return return_result; + } + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandFinishOperation() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicFinishOperation, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandSleep() { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicSleep, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandUpdateKey() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicUpdateKey, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::StartupGcAsicDevice() { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_1_8V, BusWidth_8Bit, SpeedMode_GcAsicSpeed, false)); + + /* Wait 10 clocks for configuration to take. */ + WaitClocks(10, hc->GetDeviceClockFrequencyKHz()); + + /* Perform tuning with command index 21. */ + AMS_ABORT_UNLESS(hc->IsSupportedTuning()); + R_TRY(hc->Tuning(SpeedMode_GcAsicSpeed, 21)); + + /* Set the device as low capacity/no memory. */ + this->gc_asic_device.SetHighCapacity(false); + this->gc_asic_device.SetMemoryCapacity(0); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::OnActivate() { + /* If we fail to start up the device, ensure the host controller is shut down. */ + auto power_guard = SCOPE_GUARD { BaseDeviceAccessor::GetHostController()->Shutdown(); }; + + /* Try to start up the device. */ + R_TRY(this->StartupGcAsicDevice()); + + /* We started up, so we don't need to power down. */ + power_guard.Cancel(); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Check that we're not performing zero-byte rw. */ + AMS_ABORT_UNLESS(num_sectors > 0); + + /* Check that the buffer is big enough for the rw. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Perform the read/write. */ + u32 num_transferred_blocks; + R_TRY(BaseDeviceAccessor::ReadWriteSingle(std::addressof(num_transferred_blocks), sector_index, num_sectors, buf, is_read)); + + /* Require that we read/wrote as many sectors as we expected. */ + AMS_ABORT_UNLESS(num_transferred_blocks == num_sectors); + + return ResultSuccess(); + } + + void GcAsicDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our gc asic device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->gc_asic_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->gc_asic_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(this->gc_asic_device.GetRemovedEvent()); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + this->is_initialized = true; + } + + void GcAsicDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the removed event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->gc_asic_device.FinalizeRemovedEvent(); + } + #endif + } + + Result GcAsicDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + *out_speed_mode = SpeedMode_GcAsicSpeed; + return ResultSuccess(); + } + + void GcAsicDeviceAccessor::PutGcAsicToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->gc_asic_device.IsAwake()) { + return; + } + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) + #else + if (this->gc_asic_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + + /* Put the gc asic device to sleep. */ + this->gc_asic_device.PutToSleep(); + } + + Result GcAsicDeviceAccessor::AwakenGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + R_SUCCEED_IF(this->gc_asic_device.IsAwake()); + + /* Wake the device. */ + this->gc_asic_device.Awaken(); + + /* Wake the host controller, if we need to.*/ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) + #else + if (this->gc_asic_device.IsActive()) + #endif + { + R_TRY(BaseDeviceAccessor::GetHostController()->Awaken()); + } + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::WriteGcAsicOperation(const void *op_buf, size_t op_buf_size) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandWriteOperation(op_buf, op_buf_size)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::FinishGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandFinishOperation()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::AbortGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue stop transmission command. */ + u32 resp = 0; + R_TRY(BaseDeviceAccessor::GetHostController()->IssueStopTransmissionCommand(std::addressof(resp))); + R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::SleepGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandSleep()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::UpdateGcAsicKey() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandUpdateKey()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp new file mode 100644 index 0000000..c6c24ec --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class GcAsicDevice : public BaseDevice { + private: + static constexpr u16 Rca = 0; + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + mutable os::EventType removed_event; + #endif + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(this->removed_event); + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_GcAsic; + } + + virtual u16 GetRca() const override { + return Rca; + } + }; + + class GcAsicDeviceAccessor : public BaseDeviceAccessor { + private: + GcAsicDevice gc_asic_device; + bool is_initialized; + private: + Result IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const; + Result IssueCommandFinishOperation() const; + Result IssueCommandSleep(); + Result IssueCommandUpdateKey() const; + Result StartupGcAsicDevice(); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + + virtual Result ReStartup() override { + AMS_ABORT("Can't ReStartup GcAsic\n"); + } + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit GcAsicDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc), is_initialized(false) { + /* ... */ + } + + void PutGcAsicToSleep(); + Result AwakenGcAsic(); + Result WriteGcAsicOperation(const void *op_buf, size_t op_buf_size); + Result FinishGcAsicOperation(); + Result AbortGcAsicOperation(); + Result SleepGcAsic(); + Result UpdateGcAsicKey(); + + void SignalGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->gc_asic_device.SignalRemovedEvent(); + #else + AMS_ABORT("SignalGcRemovedEvent called without event support\n"); + #endif + } + + void ClearGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->gc_asic_device.ClearRemovedEvent(); + #else + AMS_ABORT("ClearGcRemovedEvent called without event support\n"); + #endif + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp new file mode 100644 index 0000000..94e85bd --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" + +namespace ams::sdmmc::impl { + + class IDeviceAccessor { + public: + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual Result Activate() = 0; + virtual void Deactivate() = 0; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) = 0; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) = 0; + + virtual Result GetSpeedMode(SpeedMode *out) const = 0; + virtual Result GetMemoryCapacity(u32 *out_sectors) const = 0; + virtual Result GetDeviceStatus(u32 *out) const = 0; + virtual Result GetOcr(u32 *out) const = 0; + virtual Result GetRca(u16 *out) const = 0; + virtual Result GetCid(void *out, size_t size) const = 0; + virtual Result GetCsd(void *out, size_t size) const = 0; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) = 0; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp new file mode 100644 index 0000000..f4439bb --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018-2020 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 + +#if defined(AMS_SDMMC_USE_OS_EVENTS) +#include +#endif + +namespace ams::sdmmc::impl { + + enum ResponseType { + ResponseType_R0 = 0, + ResponseType_R1 = 1, + ResponseType_R2 = 2, + ResponseType_R3 = 3, + ResponseType_R6 = 4, + ResponseType_R7 = 5, + }; + + enum TransferDirection { + TransferDirection_ReadFromDevice = 0, + TransferDirection_WriteToDevice = 1, + }; + + enum CommandIndex { + /* Generic commands. */ + CommandIndex_GoIdleState = 0, + CommandIndex_SendOpCond = 1, + CommandIndex_AllSendCid = 2, + CommandIndex_SendRelativeAddr = 3, + CommandIndex_SetRelativeAddr = 3, + CommandIndex_SetDsr = 4, + + CommandIndex_Switch = 6, + CommandIndex_SelectCard = 7, + CommandIndex_DeselectCard = 7, + CommandIndex_SendIfCond = 8, + CommandIndex_SendExtCsd = 8, + CommandIndex_SendCsd = 9, + CommandIndex_SendCid = 10, + CommandIndex_VoltageSwitch = 11, + CommandIndex_StopTransmission = 12, + CommandIndex_SendStatus = 13, + CommandIndex_SendTaskStatus = 13, + + CommandIndex_GoInactiveState = 15, + CommandIndex_SetBlockLen = 16, + CommandIndex_ReadSingleBlock = 17, + CommandIndex_ReadMultipleBlock = 18, + CommandIndex_SendTuningBlock = 19, + CommandIndex_SpeedClassControl = 20, + + CommandIndex_AddressExtension = 22, + CommandIndex_SetBlockCount = 23, + CommandIndex_WriteBlock = 24, + CommandIndex_WriteMultipleBlock = 25, + + CommandIndex_ProgramCsd = 27, + CommandIndex_SetWriteProt = 28, + CommandIndex_ClearWriteProt = 29, + CommandIndex_SendWriteProt = 30, + + CommandIndex_EraseWriteBlockStart = 32, + CommandIndex_EraseWriteBlockEnd = 33, + + CommandIndex_EraseGroupStart = 35, + CommandIndex_EraseGroupEnd = 36, + + CommandIndex_Erase = 38, + + CommandIndex_LockUnlock = 42, + + CommandIndex_AppCmd = 55, + CommandIndex_GenCmd = 56, + + /* Nintendo specific vendor commands for lotus3. */ + CommandIndex_GcAsicWriteOperation = 60, + CommandIndex_GcAsicFinishOperation = 61, + CommandIndex_GcAsicSleep = 62, + CommandIndex_GcAsicUpdateKey = 63, + }; + + struct Command { + u32 command_index; + u32 command_argument; + ResponseType response_type; + bool is_busy; + + constexpr Command(u32 ci, u32 ca, ResponseType r, bool b) : command_index(ci), command_argument(ca), response_type(r), is_busy(b) { /* ... */ } + }; + + struct TransferData { + void *buffer; + size_t block_size; + u32 num_blocks; + TransferDirection transfer_direction; + bool is_multi_block_transfer; + bool is_stop_transmission_command_enabled; + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd, bool mb, bool st) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(mb), is_stop_transmission_command_enabled(st) + { + if (this->num_blocks > 1) { + AMS_ABORT_UNLESS(this->is_multi_block_transfer); + } + } + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(false), is_stop_transmission_command_enabled(false) + { + AMS_ABORT_UNLESS(this->num_blocks == 1); + } + }; + + class IHostController { + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(ams::os::EventType *event) = 0; + #endif + + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) = 0; + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) = 0; + virtual void Shutdown() = 0; + virtual void PutToSleep() = 0; + virtual Result Awaken() = 0; + + virtual Result SwitchToSdr12(); + + virtual bool IsSupportedBusPower(BusPower bus_power) const = 0; + virtual BusPower GetBusPower() const = 0; + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const = 0; + virtual void SetBusWidth(BusWidth bus_width) = 0; + virtual BusWidth GetBusWidth() const = 0; + + virtual Result SetSpeedMode(SpeedMode speed_mode) = 0; + virtual SpeedMode GetSpeedMode() const = 0; + + virtual u32 GetDeviceClockFrequencyKHz() const = 0; + + virtual void SetPowerSaving(bool en) = 0; + virtual bool IsPowerSavingEnable() const = 0; + + virtual void EnableDeviceClock() = 0; + virtual void DisableDeviceClock() = 0; + virtual bool IsDeviceClockEnable() const = 0; + + virtual u32 GetMaxTransferNumBlocks() const = 0; + + virtual void ChangeCheckTransferInterval(u32 ms) = 0; + virtual void SetDefaultCheckTransferInterval() = 0; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) = 0; + virtual Result IssueStopTransmissionCommand(u32 *out_response) = 0; + + ALWAYS_INLINE Result IssueCommand(const Command *command, TransferData *xfer_data) { + return this->IssueCommand(command, xfer_data, nullptr); + } + + ALWAYS_INLINE Result IssueCommand(const Command *command) { + return this->IssueCommand(command, nullptr, nullptr); + } + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const = 0; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const = 0; + + virtual bool IsSupportedTuning() const = 0; + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) = 0; + virtual void SaveTuningStatusForHs400() = 0; + virtual Result GetInternalStatus() const = 0; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp new file mode 100644 index 0000000..bf36180 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp @@ -0,0 +1,998 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_timer.hpp" +#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" +#include "sdmmc_io_impl.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl { + + /* Lovingly taken from libexosphere. */ + namespace i2c_impl { + + enum Port { + Port_1 = 0, + /* TODO: Support other ports? */ + Port_5 = 4, + + Port_Count = 5, + }; + + constexpr inline const dd::PhysicalAddress I2c5RegistersAddress = UINT64_C(0x7000D000); + constexpr inline const size_t I2c5RegistersSize = 4_KB; + + namespace { + + constexpr inline size_t MaxTransferSize = sizeof(u32); + + constinit std::array g_register_addresses = [] { + std::array arr = {}; + return arr; + }(); + + void LoadConfig(uintptr_t address) { + /* Configure for TIMEOUT and MSTR config load. */ + /* NOTE: Nintendo writes value 1 to reserved bit 5 here. This bit is documented as having no meaning. */ + /* We will reproduce the write just in case it is undocumented. */ + reg::Write(address + I2C_CONFIG_LOAD, I2C_REG_BITS_VALUE(CONFIG_LOAD_RESERVED_BIT_5, 1), + I2C_REG_BITS_ENUM (CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, ENABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_SLV_CONFIG_LOAD, DISABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + /* Wait up to 20 microseconds for the master config to be loaded. */ + for (int i = 0; i < 20; ++i) { + if (reg::HasValue(address + I2C_CONFIG_LOAD, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, DISABLE))) { + return; + } + util::WaitMicroSeconds(1); + } + } + + void ClearBus(uintptr_t address) { + /* Configure the bus clear register. */ + reg::Write(address + I2C_BUS_CLEAR_CONFIG, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_STOP_COND, NO_STOP), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); + + /* Load the config. */ + LoadConfig(address); + + /* Wait up to 250us (in 25 us increments) until the bus clear is done. */ + for (int i = 0; i < 10; ++i) { + if (reg::HasValue(address + I2C_INTERRUPT_STATUS_REGISTER, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, SET))) { + break; + } + + util::WaitMicroSeconds(25); + } + + /* Read the bus clear status. */ + reg::Read(address + I2C_BUS_CLEAR_STATUS); + } + + void InitializePort(uintptr_t address) { + /* Calculate the divisor. */ + constexpr int Divisor = util::DivideUp(19200, 8 * 400); + + /* Set the divisor. */ + reg::Write(address + I2C_CLK_DIVISOR_REGISTER, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, Divisor - 1), + I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, 1)); + + /* Clear the bus. */ + ClearBus(address); + + /* Clear the status. */ + reg::Write(address + I2C_INTERRUPT_STATUS_REGISTER, reg::Read(address + I2C_INTERRUPT_STATUS_REGISTER)); + } + + bool Write(uintptr_t base_address, Port port, int address, const void *src, size_t src_size, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't write too much. */ + u32 data = 0; + if (src_size > MaxTransferSize) { + return false; + } + + /* Copy the data to a transfer word. */ + std::memcpy(std::addressof(data), src, src_size); + + + /* Configure the to write the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, WRITE)); + + /* Configure to write the data. */ + reg::Write(base_address + I2C_I2C_CMD_DATA1, data); + + /* Configure to write the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, WRITE), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, src_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check if the transfer was successful. */ + return reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL)); + } + + bool Read(uintptr_t base_address, Port port, void *dst, size_t dst_size, int address, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't read too much. */ + if (dst_size > MaxTransferSize) { + return false; + } + + /* Configure the to read the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, READ)); + + /* Configure to read the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, READ), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, dst_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check that the transfer was successful. */ + if (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL))) { + return false; + } + + /* Read and copy out the data. */ + u32 data = reg::Read(base_address + I2C_I2C_CMD_DATA1); + std::memcpy(dst, std::addressof(data), dst_size); + return true; + } + + } + + void SetRegisterAddress(Port port, uintptr_t address) { + g_register_addresses[port] = address; + } + + void Initialize(Port port) { + InitializePort(g_register_addresses[port]); + } + + bool Query(void *dst, size_t dst_size, Port port, int address, int r) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Select the register we want to read. */ + bool success = Write(base_address, port, address, std::addressof(r), 1, false); + if (success) { + /* If we successfully selected, read data from the register. */ + success = Read(base_address, port, dst, dst_size, address, true); + } + + return success; + } + + bool Send(Port port, int address, int r, const void *src, size_t src_size) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Create a transfer buffer, make sure we can use it. */ + u8 buffer[MaxTransferSize]; + if (src_size > sizeof(buffer) - 1) { + return false; + } + + /* Copy data into the buffer. */ + buffer[0] = static_cast(r); + std::memcpy(buffer + 1, src, src_size); + + return Write(base_address, port, address, buffer, src_size + 1, false); + } + + } + + namespace max7762x { + + /* The only regulator we care about for SD card power is ldo2. */ + constexpr inline const u8 Max77620PwrI2cAddr = 0x3C; + + constexpr inline const u32 Max77620Ldo2MvStep = 50'000; /* 50 mV (50K uV) steps. */ + constexpr inline const u32 Max77620Ldo2MvMin = 800'000; /* 0.8V min voltage. */ + constexpr inline const u32 Max77620Ldo2MvDefault = 1'800'000; /* 1.8V default voltage. */ + constexpr inline const u32 Max77620Ldo2MvMax = 3'300'000; /* 3.3V max voltage. */ + constexpr inline const u8 Max77620Ldo2VoltAddr = 0x27; + constexpr inline const u8 Max77620Ldo2CfgAddr = 0x28; + constexpr inline const u8 Max77620Ldo2VoltMask = 0x3F; + constexpr inline const u8 Max77620Ldo2EnableMask = 0xC0; + constexpr inline const u8 Max77620Ldo2EnableShift = 0x6; + constexpr inline const u8 Max77620Ldo2StatusMask = 0x00; + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + constinit os::SdkMutex g_i2c_init_mutex; + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() std::scoped_lock lk(g_i2c_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() + + #endif + + constinit bool g_initialized_i2c = false; + + void EnsureI2cInitialized() { + if (AMS_UNLIKELY(!g_initialized_i2c)) { + /* Ensure we have exclusive access to the i2c init status. */ + AMS_SDMMC_LOCK_I2C_INIT_MUTEX(); + + if (AMS_LIKELY(!g_initialized_i2c)) { + i2c_impl::SetRegisterAddress(i2c_impl::Port_5, dd::QueryIoMapping(i2c_impl::I2c5RegistersAddress, i2c_impl::I2c5RegistersSize)); + i2c_impl::Initialize(i2c_impl::Port_5); + g_initialized_i2c = true; + } + } + } + + } + + bool SetVoltageEnabled(bool en) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set or clear the enable mask. */ + val &= ~Max77620Ldo2EnableMask; + if (en) { + val |= ((3 << Max77620Ldo2EnableShift) & Max77620Ldo2EnableMask); + } + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now enabled/disabled. */ + return true; + } + + bool SetVoltageValue(u32 micro_volts) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Check that the value is within range. */ + if (micro_volts < Max77620Ldo2MvMin || Max77620Ldo2MvMax < micro_volts) { + return false; + } + + /* Determine the mult. */ + const u32 mult = util::DivideUp(micro_volts - Max77620Ldo2MvMin, Max77620Ldo2MvStep); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set the new voltage. */ + val &= ~Max77620Ldo2VoltMask; + val |= (mult & Max77620Ldo2VoltMask); + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now set. */ + return true; + } + + } + + Result SetSdCardVoltageEnabled(bool en) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageEnabled(en)); + + return ResultSuccess(); + } + + Result SetSdCardVoltageValue(u32 micro_volts) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageValue(micro_volts)); + + return ResultSuccess(); + } + + namespace gpio_impl { + + namespace { + + constexpr inline dd::PhysicalAddress GpioRegistersPhysicalAddress = 0x6000d000; + constexpr inline size_t GpioRegistersSize = 4_KB; + + enum GpioPadPort { + GpioPadPort_A = 0, + GpioPadPort_B = 1, + GpioPadPort_C = 2, + GpioPadPort_D = 3, + GpioPadPort_E = 4, + GpioPadPort_F = 5, + GpioPadPort_G = 6, + GpioPadPort_H = 7, + GpioPadPort_I = 8, + GpioPadPort_J = 9, + GpioPadPort_K = 10, + GpioPadPort_L = 11, + GpioPadPort_M = 12, + GpioPadPort_N = 13, + GpioPadPort_O = 14, + GpioPadPort_P = 15, + GpioPadPort_Q = 16, + GpioPadPort_R = 17, + GpioPadPort_S = 18, + GpioPadPort_T = 19, + GpioPadPort_U = 20, + GpioPadPort_V = 21, + GpioPadPort_W = 22, + GpioPadPort_X = 23, + GpioPadPort_Y = 24, + GpioPadPort_Z = 25, + GpioPadPort_AA = 26, + GpioPadPort_BB = 27, + GpioPadPort_CC = 28, + GpioPadPort_DD = 29, + GpioPadPort_EE = 30, + GpioPadPort_FF = 31, + }; + + consteval unsigned int GetInternalGpioPadNumber(GpioPadPort port, unsigned int which) { + AMS_ASSUME(which < 8); + + return (static_cast(port) * 8) + which; + } + + enum InternalGpioPadNumber { + InternalGpioPadNumber_E4 = GetInternalGpioPadNumber(GpioPadPort_E, 4), + InternalGpioPadNumber_M0 = GetInternalGpioPadNumber(GpioPadPort_M, 0), + }; + + constexpr int ConvertInternalGpioPadNumberToController(InternalGpioPadNumber number) { + return (number >> 5); + } + + constexpr int ConvertInternalGpioPadNumberToPort(InternalGpioPadNumber number) { + return (number >> 3); + } + + constexpr int ConvertInternalGpioPadNumberToBitIndex(InternalGpioPadNumber number) { + return (number & 7); + } + + constexpr int ConvertPortNumberToOffset(int port_number) { + return (port_number & 3); + } + + struct PadNameToInternalPadNumberEntry { + GpioPadName pad_name; + InternalGpioPadNumber internal_number; + }; + + constexpr inline const PadNameToInternalPadNumberEntry PadNameToInternalPadNumberTable[] = { + { GpioPadName_PowSdEn, InternalGpioPadNumber_E4 }, + }; + + constexpr InternalGpioPadNumber ConvertPadNameToInternalPadNumber(GpioPadName pad) { + const PadNameToInternalPadNumberEntry *target = nullptr; + for (const auto &entry : PadNameToInternalPadNumberTable) { + if (entry.pad_name == pad) { + target = std::addressof(entry); + break; + } + } + + AMS_ABORT_UNLESS(target != nullptr); + return target->internal_number; + } + + enum GpioRegisterType { + GpioRegisterType_GPIO_CNF = 0, + GpioRegisterType_GPIO_OE = 1, + GpioRegisterType_GPIO_OUT = 2, + GpioRegisterType_GPIO_IN = 3, + GpioRegisterType_GPIO_INT_STA = 4, + GpioRegisterType_GPIO_INT_ENB = 5, + GpioRegisterType_GPIO_INT_LVL = 6, + GpioRegisterType_GPIO_INT_CLR = 7, + GpioRegisterType_GPIO_DB_CTRL = 8, + GpioRegisterType_GPIO_DB_CNT = 9, + }; + + constexpr inline uintptr_t MaskedWriteAddressOffset = 0x80; + constexpr inline int MaskedWriteBitOffset = 8; + + constexpr uintptr_t GetGpioRegisterAddress(uintptr_t gpio_address, GpioRegisterType reg_type, InternalGpioPadNumber pad_number) { + const auto controller = ConvertInternalGpioPadNumberToController(pad_number); + const auto port = ConvertInternalGpioPadNumberToPort(pad_number); + const auto offset = ConvertPortNumberToOffset(port); + + switch (reg_type) { + default: + return gpio_address + (0x100 * controller) + (0x10 * reg_type) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CTRL: + return gpio_address + (0x100 * controller) + (0x10 * GpioRegisterType_GPIO_IN) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CNT: + return gpio_address + (0x100 * controller) + MaskedWriteAddressOffset + (0x10 * GpioRegisterType_GPIO_INT_CLR) + (0x4 * offset); + } + } + + void SetMaskedBit(uintptr_t pad_address, int index, int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (1u << (MaskedWriteBitOffset + index)) | (static_cast(value) << index)); + } + + void SetMaskedBits(uintptr_t pad_address, unsigned int mask, unsigned int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (mask << MaskedWriteBitOffset) | (value)); + } + + } + + void OpenSession(GpioPadName pad) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad as GPIO by setting the appropriate bit in CNF. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_CNF, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, 1); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void CloseSession(GpioPadName pad) { + /* Nothing needs to be done here, as the only thing official code does is unbind the interrupt event. */ + AMS_UNUSED(pad); + } + + void SetDirection(GpioPadName pad, Direction direction) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad direction modifying the appropriate bit in OE. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OE, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, direction); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void SetValue(GpioPadName pad, GpioValue value) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad value modifying the appropriate bit in OUT. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OUT, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, value); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + } + + namespace pinmux_impl { + + namespace { + + constexpr auto Sdmmc1ClkCmdDat03PadNumber = gpio_impl::InternalGpioPadNumber_M0; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadMask = 0x3F; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfGpio = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfSfio = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutHigh = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutLow = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeOutput = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeInput = 0x00; + + struct PinmuxDefinition { + u32 reg_offset; + u32 mask_val; + u32 pm_val; + }; + + /* NOTE: We only use the SDMMC1 pins, which are conveniently the first few... */ + constexpr const PinmuxDefinition PinmuxDefinitionMap[] = { + {0x00003000, 0x72FF, 0x01}, /* Sdmmc1Clk */ + {0x00003004, 0x72FF, 0x02}, /* Sdmmc1Cmd */ + {0x00003008, 0x72FF, 0x02}, /* Sdmmc1Dat3 */ + {0x0000300C, 0x72FF, 0x02}, /* Sdmmc1Dat2 */ + {0x00003010, 0x72FF, 0x02}, /* Sdmmc1Dat1 */ + {0x00003014, 0x72FF, 0x01}, /* Sdmmc1Dat0 */ + }; + + enum PinmuxPadIndex { + PinmuxPadIndex_Sdmmc1Clk = 0, + PinmuxPadIndex_Sdmmc1Cmd = 1, + PinmuxPadIndex_Sdmmc1Dat3 = 2, + PinmuxPadIndex_Sdmmc1Dat2 = 3, + PinmuxPadIndex_Sdmmc1Dat1 = 4, + PinmuxPadIndex_Sdmmc1Dat0 = 5, + + PinmuxPadIndex_Count, + }; + + static_assert(util::size(PinmuxDefinitionMap) == PinmuxPadIndex_Count); + + consteval const PinmuxDefinition GetDefinition(PinmuxPadIndex pad_index) { + AMS_ABORT_UNLESS(pad_index < PinmuxPadIndex_Count); + + return PinmuxDefinitionMap[pad_index]; + } + + template + ALWAYS_INLINE u32 UpdatePinmuxPad(uintptr_t pinmux_base_vaddr) { + constexpr const PinmuxDefinition Definition = GetDefinition(PadIndex); + + /* Fetch this PINMUX's register offset */ + constexpr u32 PinmuxRegOffset = Definition.reg_offset; + + /* Fetch this PINMUX's mask value */ + constexpr u32 PinmuxMaskVal = Definition.mask_val; + + /* Get current register ptr. */ + const uintptr_t pinmux_reg = pinmux_base_vaddr + PinmuxRegOffset; + + /* Read from the PINMUX register */ + u32 pinmux_val = reg::Read(pinmux_reg); + + /* This PINMUX register is locked */ + AMS_ABORT_UNLESS((pinmux_val & 0x80) == 0); + + constexpr u32 PmVal = (PinmuxConfigVal & 0x07); + + /* Adjust PM */ + if constexpr (PinmuxConfigMaskVal & 0x07) { + /* Apply additional changes first */ + if constexpr (PmVal == 0x05) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if ((pinmux_val & 0x0C) != 0x04) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= 0x04; + } + } + + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + } + + /* Translate PM value if necessary */ + constexpr u32 TranslatedPmVal = (PmVal == 0x04 || PmVal == 0x05 || PmVal >= 0x06) ? Definition.pm_val : PmVal; + + /* This pin supports PM change */ + if constexpr (PinmuxMaskVal & 0x03) { + /* Change PM */ + if ((pinmux_val & 0x03) != (TranslatedPmVal & 0x03)) { + pinmux_val &= 0xFFFFFFFC; + pinmux_val |= (TranslatedPmVal & 0x03); + } + } + } + + constexpr u32 PupdConfigVal = (PinmuxConfigVal & 0x18); + + /* Adjust PUPD */ + if constexpr (PinmuxConfigMaskVal & 0x18) { + if constexpr (PupdConfigVal < 0x11) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if (((pinmux_val >> 0x02) & 0x03) != (PupdConfigVal >> 0x03)) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= (PupdConfigVal >> 0x01); + } + } + } + } + + constexpr u32 EodConfigVal = (PinmuxConfigVal & 0x60); + + /* Adjust EOd field */ + if constexpr (PinmuxConfigMaskVal & 0x60) { + if constexpr (EodConfigVal == 0x20) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x40) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x60) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (!(pinmux_val & 0x800)) { + pinmux_val |= 0x800; + } + } + } else { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } + } + + constexpr u32 LockConfigVal = (PinmuxConfigVal & 0x80); + + /* Adjust Lock */ + if constexpr (PinmuxConfigMaskVal & 0x80) { + /* This pin supports Lock change */ + if constexpr (PinmuxMaskVal & 0x80) { + /* Change Lock */ + if ((pinmux_val ^ PinmuxConfigVal) & 0x80) { + pinmux_val &= 0xFFFFFF7F; + pinmux_val |= LockConfigVal; + } + } + } + + constexpr u32 IoResetConfigVal = (((PinmuxConfigVal >> 0x08) & 0x1) << 0x10); + + /* Adjust IoReset */ + if constexpr (PinmuxConfigMaskVal & 0x100) { + /* This pin supports IoReset change */ + if constexpr (PinmuxMaskVal & 0x10000) { + /* Change IoReset */ + if (((pinmux_val >> 0x10) ^ (PinmuxConfigVal >> 0x08)) & 0x01) { + pinmux_val &= 0xFFFEFFFF; + pinmux_val |= IoResetConfigVal; + } + } + } + + constexpr u32 ParkConfigVal = (((PinmuxConfigVal >> 0x0A) & 0x1) << 0x5); + + /* Adjust Park */ + if constexpr (PinmuxConfigMaskVal & 0x400) { + /* This pin supports Park change */ + if constexpr (PinmuxMaskVal & 0x20) { + /* Change Park */ + if (((pinmux_val >> 0x05) ^ (PinmuxConfigVal >> 0x0A)) & 0x01) { + pinmux_val &= 0xFFFFFFDF; + pinmux_val |= ParkConfigVal; + } + } + } + + constexpr u32 ElpdrConfigVal = (((PinmuxConfigVal >> 0x0B) & 0x1) << 0x08); + + /* Adjust ELpdr */ + if constexpr (PinmuxConfigMaskVal & 0x800) { + /* This pin supports ELpdr change */ + if constexpr (PinmuxMaskVal & 0x100) { + /* Change ELpdr */ + if (((pinmux_val >> 0x08) ^ (PinmuxConfigVal >> 0x0B)) & 0x01) { + pinmux_val &= 0xFFFFFEFF; + pinmux_val |= ElpdrConfigVal; + } + } + } + + constexpr u32 EhsmConfigVal = (((PinmuxConfigVal >> 0x0C) & 0x1) << 0x09); + + /* Adjust EHsm */ + if constexpr (PinmuxConfigMaskVal & 0x1000) { + /* This pin supports EHsm change */ + if constexpr (PinmuxMaskVal & 0x200) { + /* Change EHsm */ + if (((pinmux_val >> 0x09) ^ (PinmuxConfigVal >> 0x0C)) & 0x01) { + pinmux_val &= 0xFFFFFDFF; + pinmux_val |= EhsmConfigVal; + } + } + } + + constexpr u32 EIoHvConfigVal = (((PinmuxConfigVal >> 0x09) & 0x1) << 0x0A); + + /* Adjust EIoHv */ + if constexpr (PinmuxConfigMaskVal & 0x200) { + /* This pin supports EIoHv change */ + if constexpr (PinmuxMaskVal & 0x400) { + /* Change EIoHv */ + if (((pinmux_val >> 0x0A) ^ (PinmuxConfigVal >> 0x09)) & 0x01) { + pinmux_val &= 0xFFFFFBFF; + pinmux_val |= EIoHvConfigVal; + } + } + } + + constexpr u32 EschmtConfigVal = (((PinmuxConfigVal >> 0x0D) & 0x1) << 0x0C); + + /* Adjust ESchmt */ + if constexpr (PinmuxConfigMaskVal & 0x2000) { + /* This pin supports ESchmt change */ + if constexpr (PinmuxMaskVal & 0x1000) { + /* Change ESchmt */ + if (((pinmux_val >> 0x0C) ^ (PinmuxConfigVal >> 0x0D)) & 0x01) { + pinmux_val &= 0xFFFFEFFF; + pinmux_val |= EschmtConfigVal; + } + } + } + + constexpr u32 PreempConfigVal = (((PinmuxConfigVal >> 0x10) & 0x1) << 0xF); + + /* Adjust Preemp */ + if constexpr (PinmuxConfigMaskVal & 0x10000) { + /* This pin supports Preemp change */ + if constexpr (PinmuxMaskVal & 0x8000) { + /* Change Preemp */ + if (((pinmux_val >> 0x0F) ^ (PinmuxConfigVal >> 0x10)) & 0x01) { + pinmux_val &= 0xFFFF7FFF; + pinmux_val |= PreempConfigVal; + } + } + } + + constexpr u32 DrvTypeConfigVal = (((PinmuxConfigVal >> 0x0E) & 0x3) << 0xD); + + /* Adjust DrvType */ + if constexpr (PinmuxConfigMaskVal & 0xC000) { + /* This pin supports DrvType change */ + if constexpr (PinmuxMaskVal & 0x6000) { + /* Change DrvType */ + if (((pinmux_val >> 0x0D) ^ (PinmuxConfigVal >> 0x0E)) & 0x03) { + pinmux_val &= 0xFFFF9FFF; + pinmux_val |= DrvTypeConfigVal; + } + } + } + + /* Write to the appropriate PINMUX register */ + reg::Write(pinmux_reg, pinmux_val); + + /* Do a dummy read from the PINMUX register */ + pinmux_val = reg::Read(pinmux_reg); + + return pinmux_val; + } + + } + + void SetPinAssignment(PinAssignment assignment) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + AMS_UNUSED(apb_address); + + /* Set the pin assignment. */ + switch (assignment) { + case PinAssignment_Sdmmc1OutputHigh: + { + /* Clear Sdmmc1Clk pulldown. */ + UpdatePinmuxPad(apb_address); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as gpio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfGpio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as high. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutHigh); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as output. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeOutput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + } + break; + case PinAssignment_Sdmmc1ResetState: + { + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as sfio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfSfio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as low. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutLow); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as input. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeInput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + + /* Set Sdmmc1Clk pulldown. */ + UpdatePinmuxPad(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtEnable: + { + /* Set Schmitt enable for all pins in the group. */ + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtDisable: + { + /* Set Schmitt disable for all pins in the group. */ + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp new file mode 100644 index 0000000..89b6230 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::sdmmc::impl { + + Result SetSdCardVoltageEnabled(bool en); + Result SetSdCardVoltageValue(u32 micro_volts); + + namespace pinmux_impl { + + enum PinAssignment { + PinAssignment_Sdmmc1OutputHigh = 2, + PinAssignment_Sdmmc1ResetState = 3, + PinAssignment_Sdmmc1SchmtEnable = 4, + PinAssignment_Sdmmc1SchmtDisable = 5, + }; + + void SetPinAssignment(PinAssignment assignment); + + } + + namespace gpio_impl { + + enum GpioValue { + GpioValue_Low = 0, + GpioValue_High = 1 + }; + + enum Direction { + Direction_Input = 0, + Direction_Output = 1, + }; + + enum GpioPadName { + GpioPadName_PowSdEn = 2, + }; + + void OpenSession(GpioPadName pad); + void CloseSession(GpioPadName pad); + + void SetDirection(GpioPadName pad, Direction direction); + void SetValue(GpioPadName pad, GpioValue value); + + + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp new file mode 100644 index 0000000..7468aab --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_mmc_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() std::scoped_lock lk(this->mmc_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + + constexpr inline u32 OcrAccessMode_Mask = (3 << 29); + constexpr inline u32 OcrAccessMode_SectorMode = (2 << 29); + + constexpr inline u8 ManufacturerId_Toshiba = 0x11; + + enum DeviceType : u8 { + DeviceType_HighSpeed26MHz = (1u << 0), + DeviceType_HighSpeed52MHz = (1u << 1), + DeviceType_HighSpeedDdr52MHz1_8VOr3_0V = (1u << 2), + DeviceType_HighSpeedDdr52MHz1_2V = (1u << 3), + DeviceType_Hs200Sdr200MHz1_8V = (1u << 4), + DeviceType_Hs200Sdr200MHz1_2V = (1u << 5), + DeviceType_Hs400Sdr200MHz1_8V = (1u << 6), + DeviceType_Hs400Sdr200MHz1_2V = (1u << 7), + }; + + constexpr bool IsToshibaMmc(const u8 *cid) { + /* Check whether the CID's manufacturer id field is Toshiba. */ + AMS_ABORT_UNLESS(cid != nullptr); + return cid[14] == ManufacturerId_Toshiba; + } + + constexpr bool IsLessThanSpecification4(const u8 *csd) { + const u8 spec_vers = ((csd[14] >> 2) & 0xF); + return spec_vers < 4; + } + + constexpr bool IsBkopAutoEnable(const u8 *ext_csd) { + /* Check the AUTO_EN bit of BKOPS_EN. */ + return (ext_csd[163] & (1u << 1)) != 0; + } + + constexpr u8 GetDeviceType(const u8 *ext_csd) { + /* Get the DEVICE_TYPE register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[196]; + } + + constexpr bool IsSupportedHs400(u8 device_type) { + return (device_type & DeviceType_Hs400Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHs200(u8 device_type) { + return (device_type & DeviceType_Hs200Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHighSpeed(u8 device_type) { + return (device_type & DeviceType_HighSpeed52MHz) != 0; + } + + constexpr u32 GetMemoryCapacityFromExtCsd(const u32 *ext_csd) { + /* Get the SEC_COUNT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[212 / sizeof(u32)]; + } + + constexpr u32 GetBootPartitionMemoryCapacityFromExtCsd(const u8 *ext_csd) { + /* Get the BOOT_SIZE_MULT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[226] * (128_KB / SectorSize); + } + + constexpr Result GetCurrentSpeedModeFromExtCsd(SpeedMode *out, const u8 *ext_csd) { + /* Get the HS_TIMING register. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(ext_csd != nullptr); + + switch (ext_csd[185] & 0xF) { + case 0: *out = SpeedMode_MmcLegacySpeed; break; + case 1: *out = SpeedMode_MmcHighSpeed; break; + case 2: *out = SpeedMode_MmcHs200; break; + case 3: *out = SpeedMode_MmcHs400; break; + default: return sdmmc::ResultUnexpectedMmcExtendedCsdValue(); + } + + return ResultSuccess(); + } + + } + + void MmcDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrAccessMode_Mask) == OcrAccessMode_SectorMode); + } + + Result MmcDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const { + /* Get the command argument. */ + u32 arg = OcrAccessMode_SectorMode; + switch (bus_power) { + case BusPower_1_8V: arg |= 0x000080; break; + case BusPower_3_3V: arg |= 0x03F800; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(CommandIndex_SendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(*out_ocr), CommandResponseType); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSetRelativeAddr() const { + /* Get rca. */ + const u32 rca = this->mmc_device.GetRca(); + AMS_ABORT_UNLESS(rca > 0); + + /* Issue comamnd. */ + const u32 arg = rca << 16; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_SetRelativeAddr, arg, false, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSwitch(CommandSwitch cs) const { + /* Get the command argument. */ + const u32 arg = GetCommandSwitchArgument(cs); + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Switch, arg, true, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSendExtCsd(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= MmcExtendedCsdSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_SendExtCsd, 0, CommandResponseType, false); + TransferData xfer_data(dst, MmcExtendedCsdSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->mmc_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupStart(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupStart, arg, false, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupEnd(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupEnd, arg, false, DeviceState_Tran)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandErase() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Erase, 0, false, DeviceState_Tran)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::CancelToshibaMmcModel() { + /* Special erase sequence done by Nintendo on Toshiba MMCs. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsProductionStateAwarenessEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_ClearBitsAutoModeEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessNormal)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToReadyState(BusPower bus_power) { + /* Be prepared to wait up to 1.5 seconds to change state. */ + ManualTimer timer(1500); + while (true) { + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), bus_power)); + if ((ocr & OcrCardPowerUpStatus) != 0) { + this->mmc_device.SetOcrAndHighCapacity(ocr); + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result MmcDeviceAccessor::ExtendBusWidth(BusWidth max_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* Determine what bus width to switch to. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + BusWidth target_bw = BusWidth_1Bit; + CommandSwitch cs; + if (max_bw == BusWidth_8Bit && hc->IsSupportedBusWidth(BusWidth_8Bit)) { + target_bw = BusWidth_8Bit; + cs = CommandSwitch_WriteBusWidth8Bit; + } else if ((max_bw == BusWidth_8Bit || max_bw == BusWidth_4Bit) && hc->IsSupportedBusWidth(BusWidth_4Bit)) { + target_bw = BusWidth_4Bit; + cs = CommandSwitch_WriteBusWidth4Bit; + } else { + /* Target bus width is 1bit. */ + return ResultSuccess(); + } + + /* Set the bus width. */ + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + hc->SetBusWidth(target_bw); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::EnableBkopsAuto() { + /* Issue the command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsBkopsEnAutoEn)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHighSpeed(bool check_before) { + /* Issue high speed command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHighSpeed)); + + /* If we should check status before setting mode, do so. */ + if (check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + /* Set the host controller to high speed. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_MmcHighSpeed)); + + /* If we should check status after setting mode, do so. */ + if (!check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHs200() { + /* Issue Hs200 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs200)); + + /* Set the host controller to Hs200. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs200)); + + /* Perform tuning using command index 21. */ + R_TRY(hc->Tuning(SpeedMode_MmcHs200, 21)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHs400() { + /* Change first to Hs200. */ + R_TRY(this->ChangeToHs200()); + + /* Save tuning status. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + hc->SaveTuningStatusForHs400(); + + /* Change to high speed. */ + R_TRY(this->ChangeToHighSpeed(false)); + + /* Issue Hs400 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteBusWidth8BitDdr)); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs400)); + + /* Set the host controller to Hs400. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs400)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ExtendBusSpeed(u8 device_type, SpeedMode max_sm) { + /* We want to switch to the highest speed we can. */ + /* Check Hs400/Hs200 first. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + if (hc->IsSupportedTuning() && hc->GetBusPower() == BusPower_1_8V) { + if (hc->GetBusWidth() == BusWidth_8Bit && IsSupportedHs400(device_type) && max_sm == SpeedMode_MmcHs400) { + return this->ChangeToHs400(); + } else if ((hc->GetBusWidth() == BusWidth_8Bit || hc->GetBusWidth() == BusWidth_4Bit) && IsSupportedHs200(device_type) && (max_sm == SpeedMode_MmcHs400 || max_sm == SpeedMode_MmcHs200)) { + return this->ChangeToHs200(); + } + } + + /* Check if we can switch to high speed. */ + if (IsSupportedHighSpeed(device_type)) { + return this->ChangeToHighSpeed(true); + } + + /* We can't, so stay at normal speeds. */ + return ResultSuccess(); + } + + Result MmcDeviceAccessor::StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up at an appropriate bus power. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + const BusPower bp = hc->IsSupportedBusPower(BusPower_1_8V) ? BusPower_1_8V : BusPower_3_3V; + R_TRY(hc->Startup(bp, BusWidth_1Bit, SpeedMode_MmcIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + this->current_partition = MmcPartition_UserData; + + /* Go to ready state. */ + R_TRY(this->ChangeToReadyState(bp)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + this->mmc_device.SetCid(wb, wb_size); + const bool is_toshiba = IsToshibaMmc(static_cast(wb)); + + /* Issue set relative addr. */ + R_TRY(this->IssueCommandSetRelativeAddr()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + this->mmc_device.SetCsd(wb, wb_size); + const bool spec_under_4 = IsLessThanSpecification4(static_cast(wb)); + + /* Set the speed mode to legacy. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcLegacySpeed)); + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* If the device SPEC_VERS is less than 4, extended csd/switch aren't supported. */ + if (spec_under_4) { + R_TRY(this->mmc_device.SetLegacyMemoryCapacity()); + + this->mmc_device.SetActive(); + return ResultSuccess(); + } + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw)); + + /* Get the extended csd. */ + R_TRY(this->IssueCommandSendExtCsd(wb, wb_size)); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(wb), alignof(u32))); + this->mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast(wb))); + + /* If the mmc is manufactured by toshiba, try to enable bkops auto. */ + if (is_toshiba && !IsBkopAutoEnable(static_cast(wb))) { + /* NOTE: Nintendo does not check the result of this. */ + this->EnableBkopsAuto(); + } + + /* Extend the bus speed to as fast as we can. */ + const u8 device_type = GetDeviceType(static_cast(wb)); + R_TRY(this->ExtendBusSpeed(device_type, max_sm)); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_MMC_HS400) + { BusWidth_8Bit, SpeedMode_MmcHs400 }, + #else + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + #endif + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + { BusWidth_1Bit, SpeedMode_MmcHighSpeed }, + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + this->max_bus_width = params.bus_width; + this->max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + return ResultSuccess(); + } + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + BaseDeviceAccessor::PushErrorTimeStamp(); + + return result; + } + + Result MmcDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Get the sector index alignment. */ + u32 sector_index_alignment = 0; + if (!is_read) { + constexpr u32 MmcWriteSectorAlignment = 16_KB / SectorSize; + sector_index_alignment = MmcWriteSectorAlignment; + AMS_ABORT_UNLESS(util::IsAligned(sector_index, MmcWriteSectorAlignment)); + } + + /* Do the read/write. */ + return BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, sector_index_alignment, buf, buf_size, is_read); + } + + Result MmcDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + return result; + } + + return ResultSuccess(); + } + + void MmcDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our mmc device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->mmc_device)); + + /* Initialize. */ + BaseDeviceAccessor::GetHostController()->Initialize(); + this->is_initialized = true; + } + + void MmcDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + } + + Result MmcDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Get the current speed mode from the ext csd. */ + R_TRY(GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); + R_TRY(GetCurrentSpeedModeFromExtCsd(out_speed_mode, static_cast(this->work_buffer))); + + return ResultSuccess(); + } + + void MmcDeviceAccessor::PutMmcToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->mmc_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + this->mmc_device.PutToSleep(); + + /* If necessary, put the host controller to sleep. */ + if (this->mmc_device.IsActive()) { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void MmcDeviceAccessor::AwakenMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (this->mmc_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + if (this->mmc_device.IsActive()) { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + } + } + + /* Wake the device. */ + this->mmc_device.Awaken(); + } + + Result MmcDeviceAccessor::SelectMmcPartition(MmcPartition part) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Determine the appropriate SWITCH subcommand. */ + CommandSwitch cs; + switch (part) { + case MmcPartition_UserData: cs = CommandSwitch_WritePartitionAccessDefault; break; + case MmcPartition_BootPartition1: cs = CommandSwitch_WritePartitionAccessRwBootPartition1; break; + case MmcPartition_BootPartition2: cs = CommandSwitch_WritePartitionAccessRwBootPartition2; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Change partition. */ + this->current_partition = MmcPartition_Unknown; + { + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + this->current_partition = part; + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::EraseMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Get the partition capacity. */ + u32 part_capacity; + switch (this->current_partition) { + case MmcPartition_UserData: + part_capacity = this->mmc_device.GetMemoryCapacity(); + break; + case MmcPartition_BootPartition1: + case MmcPartition_BootPartition2: + R_TRY(this->GetMmcBootPartitionCapacity(std::addressof(part_capacity))); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Begin the erase. */ + R_TRY(this->IssueCommandEraseGroupStart(0)); + R_TRY(this->IssueCommandEraseGroupEnd(part_capacity - 1)); + + /* Issue the erase command, allowing 30 seconds for it to complete. */ + ManualTimer timer(30000); + Result result = this->IssueCommandErase(); + R_TRY_CATCH(result) { + R_CATCH(sdmmc::ResultDataTimeoutError) { /* Data timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultCommandCompleteSoftwareTimeout) { /* Command complete software timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultBusySoftwareTimeout) { /* Busy software timeout error is acceptable. */ } + } R_END_TRY_CATCH; + + /* Wait for the erase to finish. */ + while (true) { + /* Check if we're done. */ + result = BaseDeviceAccessor::IssueCommandSendStatus(); + if (R_SUCCEEDED(result)) { + break; + } + + /* Otherwise, check if we should reject the error. */ + if (!sdmmc::ResultUnexpectedDeviceState::Includes(result)) { + return result; + } + + /* Check if timeout has been exceeded. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcEraseSoftwareTimeout()); + } + + /* If the partition is user data, check if we need to perform toshiba-specific erase. */ + if (this->current_partition == MmcPartition_UserData) { + u8 cid[DeviceCidSize]; + this->mmc_device.GetCid(cid, sizeof(cid)); + if (IsToshibaMmc(cid)) { + /* NOTE: Nintendo does not check the result of this operation. */ + this->CancelToshibaMmcModel(); + } + } + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::GetMmcBootPartitionCapacity(u32 *out_num_sectors) const { + /* Get the capacity from the extended csd. */ + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + R_TRY(this->GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); + + *out_num_sectors = GetBootPartitionMemoryCapacityFromExtCsd(static_cast(this->work_buffer)); + return ResultSuccess(); + } + + Result MmcDeviceAccessor::GetMmcExtendedCsd(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + this->mmc_device.GetCsd(csd, sizeof(csd)); + + /* Check that the card supports ext csd. */ + R_UNLESS(!IsLessThanSpecification4(csd), sdmmc::ResultMmcNotSupportExtendedCsd()); + + /* Get the ext csd. */ + R_TRY(this->IssueCommandSendExtCsd(dst, dst_size)); + + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp new file mode 100644 index 0000000..97ac864 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class MmcDevice : public BaseDevice { + private: + static constexpr u16 Rca = 2; + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_Mmc; + } + + virtual u16 GetRca() const override { + return Rca; + } + + void SetOcrAndHighCapacity(u32 ocr); + }; + + class MmcDeviceAccessor : public BaseDeviceAccessor { + private: + MmcDevice mmc_device; + void *work_buffer; + size_t work_buffer_size; + BusWidth max_bus_width; + SpeedMode max_speed_mode; + MmcPartition current_partition; + bool is_initialized; + private: + enum CommandSwitch { + CommandSwitch_SetBitsProductionStateAwarenessEnable = 0, + CommandSwitch_ClearBitsAutoModeEnable = 1, + CommandSwitch_WriteProductionStateAwarenessNormal = 2, + CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites = 3, + CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites = 4, + CommandSwitch_SetBitsBkopsEnAutoEn = 5, + CommandSwitch_WriteBusWidth1Bit = 6, + CommandSwitch_WriteBusWidth4Bit = 7, + CommandSwitch_WriteBusWidth8Bit = 8, + CommandSwitch_WriteBusWidth8BitDdr = 9, + CommandSwitch_WriteHsTimingLegacySpeed = 10, + CommandSwitch_WriteHsTimingHighSpeed = 11, + CommandSwitch_WriteHsTimingHs200 = 12, + CommandSwitch_WriteHsTimingHs400 = 13, + CommandSwitch_WritePartitionAccessDefault = 14, + CommandSwitch_WritePartitionAccessRwBootPartition1 = 15, + CommandSwitch_WritePartitionAccessRwBootPartition2 = 16, + }; + + static constexpr ALWAYS_INLINE u32 GetCommandSwitchArgument(CommandSwitch cs) { + switch (cs) { + case CommandSwitch_SetBitsProductionStateAwarenessEnable: return 0x01111000; + case CommandSwitch_ClearBitsAutoModeEnable: return 0x02112000; + case CommandSwitch_WriteProductionStateAwarenessNormal: return 0x03850000; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites: return 0x03850100; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites: return 0x03850200; + case CommandSwitch_SetBitsBkopsEnAutoEn: return 0x01A30200; + case CommandSwitch_WriteBusWidth1Bit: return 0x03B70000; + case CommandSwitch_WriteBusWidth4Bit: return 0x03B70100; + case CommandSwitch_WriteBusWidth8Bit: return 0x03B70200; + case CommandSwitch_WriteBusWidth8BitDdr: return 0x03B70600; + case CommandSwitch_WriteHsTimingLegacySpeed: return 0x03B90000; + case CommandSwitch_WriteHsTimingHighSpeed: return 0x03B90100; + case CommandSwitch_WriteHsTimingHs200: return 0x03B90200; + case CommandSwitch_WriteHsTimingHs400: return 0x03B90300; + case CommandSwitch_WritePartitionAccessDefault: return 0x03B30000; + case CommandSwitch_WritePartitionAccessRwBootPartition1: return 0x03B30100; + case CommandSwitch_WritePartitionAccessRwBootPartition2: return 0x03B30200; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + private: + Result IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const; + Result IssueCommandSetRelativeAddr() const; + Result IssueCommandSwitch(CommandSwitch cs) const; + Result IssueCommandSendExtCsd(void *dst, size_t dst_size) const; + Result IssueCommandEraseGroupStart(u32 sector_index) const; + Result IssueCommandEraseGroupEnd(u32 sector_index) const; + Result IssueCommandErase() const; + Result CancelToshibaMmcModel(); + Result ChangeToReadyState(BusPower bus_power); + Result ExtendBusWidth(BusWidth max_bus_width); + Result EnableBkopsAuto(); + Result ChangeToHighSpeed(bool check_before); + Result ChangeToHs200(); + Result ChangeToHs400(); + Result ExtendBusSpeed(u8 device_type, SpeedMode max_sm); + Result StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit MmcDeviceAccessor(IHostController *hc) + : BaseDeviceAccessor(hc), work_buffer(nullptr), work_buffer_size(0), + max_bus_width(BusWidth_8Bit), max_speed_mode(SpeedMode_MmcHs400), current_partition(MmcPartition_Unknown), + is_initialized(false) + { + /* ... */ + } + + void SetMmcWorkBuffer(void *wb, size_t wb_size) { + this->work_buffer = wb; + this->work_buffer_size = wb_size; + } + + void PutMmcToSleep(); + void AwakenMmc(); + Result SelectMmcPartition(MmcPartition part); + Result EraseMmc(); + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors) const; + Result GetMmcExtendedCsd(void *dst, size_t dst_size) const; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp new file mode 100644 index 0000000..763869d --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_gc_asic0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortGcAsic0 g_gc_asic0_host_controller; + GcAsicDeviceAccessor g_gc_asic0_device_accessor(std::addressof(g_gc_asic0_host_controller)); + + } + + IHostController *GetHostControllerOfPortGcAsic0() { + return std::addressof(g_gc_asic0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp new file mode 100644 index 0000000..8dc7880 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_gc_asic_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortGcAsic0(); + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0(); + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp new file mode 100644 index 0000000..30cfc86 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_mmc0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" +#include "sdmmc_base_device_accessor.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortMmc0 g_mmc0_host_controller; + MmcDeviceAccessor g_mmc0_device_accessor(std::addressof(g_mmc0_host_controller)); + + } + + IHostController *GetHostControllerOfPortMmc0() { + return std::addressof(g_mmc0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp new file mode 100644 index 0000000..b675bd4 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_mmc_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortMmc0(); + IDeviceAccessor *GetDeviceAccessorOfPortMmc0(); + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp new file mode 100644 index 0000000..5288a5e --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_sd_card0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortSdCard0 g_sd_card0_host_controller; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + constexpr inline u32 SdCard0DebounceMilliSeconds = 128; + DeviceDetector g_sd_card0_detector(gpio::DeviceCode_SdCd, gpio::GpioValue_Low, SdCard0DebounceMilliSeconds); + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller), std::addressof(g_sd_card0_detector)); + + #else + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller)); + + #endif + + + } + + IHostController *GetHostControllerOfPortSdCard0() { + return std::addressof(g_sd_card0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp new file mode 100644 index 0000000..690dbb6 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_sd_card_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortSdCard0(); + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0(); + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp new file mode 100644 index 0000000..9bfff1a --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp @@ -0,0 +1,1054 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_sd_card_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() std::scoped_lock lk(this->sd_card_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() R_UNLESS(!this->sd_card_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + constexpr inline u32 OcrCardCapacityStatus = (1 << 30); + constexpr inline u32 OcrSwitchingTo1_8VAccepted = (1 << 24); + + constexpr bool IsLessThanSpecification1_1(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + + const u8 sd_spec = scr[0] & 0xF; + return sd_spec < 1; + } + + constexpr u32 GetSendOpCmdArgument(bool spec_under_2, bool uhs_i_supported) { + const u32 hcs = !spec_under_2 ? (1u << 30) : (0u << 30); + const u32 xpc = !spec_under_2 ? (1u << 28) : (0u << 28); + const u32 s18r = (!spec_under_2 && uhs_i_supported) ? (1u << 24) : (0u << 24); + return hcs | xpc | s18r | 0x00100000u; + } + + constexpr bool IsLessThanCsdVersion2(const u8 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Check whether CSD_STRUCTURE is 0. */ + return ((csd[14] & 0xC0) >> 6) == 0; + } + + constexpr u32 GetMemoryCapacityFromCsd(const u16 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Get CSIZE, convert appropriately. */ + const u32 csize = (static_cast(csd[3] & 0x3FFF) << 8) | (static_cast(csd[2] & 0xFF00) >> 8); + return (1 + csize) << 10; + } + + constexpr u8 GetSdBusWidths(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + return scr[1] & 0xF; + } + + constexpr bool IsSupportedBusWidth4Bit(u8 sd_bw) { + return (sd_bw & 0x4) != 0; + } + + constexpr bool IsSupportedAccessMode(const u8 *status, SwitchFunctionAccessMode access_mode) { + AMS_ABORT_UNLESS(status != nullptr); + + return (status[13] & (1u << access_mode)) != 0; + } + + constexpr u8 GetAccessModeFromFunctionSelection(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + return (status[16] & 0xF); + } + + constexpr bool IsAccessModeInFunctionSelection(const u8 *status, SwitchFunctionAccessMode mode) { + return GetAccessModeFromFunctionSelection(status) == static_cast(mode); + } + + constexpr u16 GetMaximumCurrentConsumption(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + + return (static_cast(status[0]) << 8) | + (static_cast(status[1]) << 0); + } + + constexpr u32 GetSizeOfProtectedArea(const u8 *sd_status) { + return (static_cast(sd_status[4]) << 24) | + (static_cast(sd_status[5]) << 16) | + (static_cast(sd_status[6]) << 8) | + (static_cast(sd_status[7]) << 0); + } + + Result GetCurrentSpeedMode(SpeedMode *out_sm, const u8 *status, bool is_uhs_i) { + AMS_ABORT_UNLESS(out_sm != nullptr); + + /* Get the access mode. */ + switch (static_cast(GetAccessModeFromFunctionSelection(status))) { + case SwitchFunctionAccessMode_Default: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr12; + } else { + *out_sm = SpeedMode_SdCardDefaultSpeed; + } + break; + case SwitchFunctionAccessMode_HighSpeed: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr25; + } else { + *out_sm = SpeedMode_SdCardHighSpeed; + } + break; + case SwitchFunctionAccessMode_Sdr50: + *out_sm = SpeedMode_SdCardSdr50; + break; + case SwitchFunctionAccessMode_Sdr104: + *out_sm = SpeedMode_SdCardSdr104; + break; + case SwitchFunctionAccessMode_Ddr50: + *out_sm = SpeedMode_SdCardDdr50; + break; + default: + return sdmmc::ResultUnexpectedSdCardSwitchFunctionStatus(); + } + + return ResultSuccess(); + } + + } + + void SdCardDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrCardCapacityStatus) != 0); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void SdCardDeviceAccessor::RemovedCallback() { + /* Signal that the device was removed. */ + this->sd_card_device.SignalRemovedEvent(); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Shut down. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + #endif + + Result SdCardDeviceAccessor::IssueCommandSendRelativeAddr(u16 *out_rca) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R6; + Command command(CommandIndex_SendRelativeAddr, 0, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Set the output rca. */ + AMS_ABORT_UNLESS(out_rca != nullptr); + *out_rca = static_cast(resp >> 16); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendIfCond() const { + /* Get the argument. */ + constexpr u32 SendIfCommandArgument = 0x01AAu; + constexpr u32 SendIfCommandArgumentMask = 0x0FFFu; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R7; + Command command(CommandIndex_SendIfCond, SendIfCommandArgument, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Verify that our argument was returned to us. */ + R_UNLESS((resp & SendIfCommandArgumentMask) == (SendIfCommandArgument & SendIfCommandArgumentMask), sdmmc::ResultSdCardValidationError()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + constexpr u32 CheckSupportedFunctionArgument = 0x00FFFFFF; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, CheckSupportedFunctionArgument, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + const u32 arg = (set_function ? (1u << 31) : (0u << 31)) | 0x00FFFFF0 | static_cast(access_mode); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, arg, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandVoltageSwitch() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_VoltageSwitch, 0, false, DeviceState_Ready)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask) const { + /* Get arg. */ + const u32 arg = static_cast(this->sd_card_device.GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_AppCmd, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (ignore_mask != 0) { + resp &= ~ignore_mask; + } + + /* Check the device status. */ + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + /* Check the app command bit. */ + R_UNLESS((resp & DeviceStatus_AppCmd) != 0, sdmmc::ResultUnexpectedSdCardAcmdDisabled()); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(this->sd_card_device.GetDeviceState(resp) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSetBusWidth4Bit() const { + /* Issue the application command. */ + constexpr u32 Arg = 0x2; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetBusWidth, Arg, false, DeviceState_Tran)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSdStatus(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSdStatusSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SdStatus, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSdStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const { + /* Get the argument. */ + const u32 arg = GetSendOpCmdArgument(spec_under_2, uhs_i_supported); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(SdApplicationCommandIndex_SdSendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(u32), CommandResponseType); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandClearCardDetect() const { + /* Issue the application command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetClearCardDetect, 0, false, DeviceState_Tran)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendScr(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardScrSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SendScr, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardScrSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::EnterUhsIMode() { + /* Send voltage switch command. */ + R_TRY(this->IssueCommandVoltageSwitch()); + + /* Switch to sdr12. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SwitchToSdr12()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ChangeToReadyState(bool spec_under_2, bool uhs_i_supported) { + /* Decide on an ignore mask. */ + u32 ignore_mask = spec_under_2 ? static_cast(DeviceStatus_IllegalCommand) : 0u; + + /* Be prepared to wait up to 3.0 seconds to change state. */ + ManualTimer timer(3000); + while (true) { + /* We want to get ocr, which requires our sending an application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Unknown, ignore_mask)); + ignore_mask = 0; + + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), spec_under_2, uhs_i_supported)); + + if ((ocr & OcrCardPowerUpStatus) != 0) { + this->sd_card_device.SetOcrAndHighCapacity(ocr); + + /* Handle uhs i mode. */ + this->sd_card_device.SetUhsIMode(false); + if (uhs_i_supported && ((ocr & OcrSwitchingTo1_8VAccepted) != 0)) { + R_TRY(this->EnterUhsIMode()); + this->sd_card_device.SetUhsIMode(true); + } + + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result SdCardDeviceAccessor::ChangeToStbyStateAndGetRca() { + /* Be prepared to wait up to 1.0 seconds to change state. */ + ManualTimer timer(1000); + while (true) { + /* Get rca. */ + u16 rca; + R_TRY(this->IssueCommandSendRelativeAddr(std::addressof(rca))); + if (rca != 0) { + this->sd_card_device.SetRca(rca); + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardGetValidRcaSoftwareTimeout()); + } + } + + Result SdCardDeviceAccessor::SetMemoryCapacity(const void *csd) { + if (IsLessThanCsdVersion2(static_cast(csd))) { + R_TRY(this->sd_card_device.SetLegacyMemoryCapacity()); + } else { + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(csd), alignof(u16))); + this->sd_card_device.SetMemoryCapacity(GetMemoryCapacityFromCsd(static_cast(csd))); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetScr(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSendScr(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusWidth(BusWidth max_bw, u8 sd_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* If 4bit mode isn't supported, we can't extend. */ + R_SUCCEED_IF(!IsSupportedBusWidth4Bit(sd_bw)); + + /* If the host controller doesn't support 4bit mode, we can't extend. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_SUCCEED_IF(!hc->IsSupportedBusWidth(BusWidth_4Bit)); + + /* Issue the application command to change to 4bit mode. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSetBusWidth4Bit()); + + /* Set the host controller's bus width. */ + hc->SetBusWidth(BusWidth_4Bit); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size) { + /* Issue command to check if we can switch access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, false, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardCannotSwitchAccessMode()); + + /* Check if we can accept the resulting current consumption. */ + constexpr u16 AcceptableCurrentLimit = 800; /* mA */ + R_UNLESS(GetMaximumCurrentConsumption(static_cast(wb)) < AcceptableCurrentLimit, sdmmc::ResultSdCardUnacceptableCurrentConsumption()); + + /* Switch the access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, true, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardFailedSwitchAccessMode()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size) { + /* Check that we're in 4bit bus mode. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_UNLESS(hc->GetBusWidth() == BusWidth_4Bit, sdmmc::ResultSdCardNot4BitBusWidthAtUhsIMode()); + + /* Determine what speed mode/access mode we should switch to. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + SwitchFunctionAccessMode target_am; + SpeedMode target_sm; + if (max_sm == SpeedMode_SdCardSdr104 && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr104)) { + target_am = SwitchFunctionAccessMode_Sdr104; + target_sm = SpeedMode_SdCardSdr104; + } else if ((max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50) && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr50)) { + target_am = SwitchFunctionAccessMode_Sdr50; + target_sm = SpeedMode_SdCardSdr50; + } else { + return sdmmc::ResultSdCardNotSupportSdr104AndSdr50(); + } + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(target_am, wb, wb_size)); + + /* Set the host controller speed mode and perform tuning using command index 19. */ + R_TRY(hc->SetSpeedMode(target_sm)); + R_TRY(hc->Tuning(target_sm, 19)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size) { + /* If the maximum speed is default speed, we have nothing to do. */ + R_SUCCEED_IF(max_sm == SpeedMode_SdCardDefaultSpeed); + + /* Otherwise, if the spec is under 1.1, we have nothing to do. */ + R_SUCCEED_IF(spec_under_1_1); + + /* Otherwise, Check if high speed is supported. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + R_SUCCEED_IF(!IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_HighSpeed)); + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(SwitchFunctionAccessMode_HighSpeed, wb, wb_size)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + /* Set the host controller speed mode. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_SdCardHighSpeed)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdStatus(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSdStatus(dst, dst_size)); + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::TryDisconnectDat3PullUpResistor() const { + /* Issue the application command to clear card detect. */ + /* NOTE: Nintendo accepts a failure. */ + if (R_SUCCEEDED(this->IssueCommandAppCmd(DeviceState_Tran))) { + /* NOTE: Nintendo does not check the result of this. */ + this->IssueCommandClearCardDetect(); + } + + /* NOTE: Nintendo does not check the result of this. */ + BaseDeviceAccessor::IssueCommandSendStatus(); + } + + Result SdCardDeviceAccessor::StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_3_3V, BusWidth_1Bit, SpeedMode_SdCardIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + + /* Check whether the spec is under 2.0. */ + bool spec_under_2 = false; + R_TRY_CATCH(this->IssueCommandSendIfCond()) { + R_CATCH(sdmmc::ResultResponseTimeoutError) { spec_under_2 = true; } + } R_END_TRY_CATCH; + + /* Set the rca to 0. */ + this->sd_card_device.SetRca(0); + + /* Go to ready state. */ + const bool can_use_uhs_i_mode = (max_bw != BusWidth_1Bit) && (max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50); + const bool uhs_i_supported = hc->IsSupportedTuning() && hc->IsSupportedBusPower(BusPower_1_8V); + R_TRY(this->ChangeToReadyState(spec_under_2, can_use_uhs_i_mode && uhs_i_supported)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + this->sd_card_device.SetCid(wb, wb_size); + + /* Go to stby state and get the RCA. */ + R_TRY(this->ChangeToStbyStateAndGetRca()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + this->sd_card_device.SetCsd(wb, wb_size); + R_TRY(this->SetMemoryCapacity(wb)); + + /* Set the host controller speed mode to default if we're not in uhs i mode. */ + if (!this->sd_card_device.IsUhsIMode()) { + R_TRY(hc->SetSpeedMode(SpeedMode_SdCardDefaultSpeed)); + } + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* Try to disconnect dat3 pullup resistor. */ + TryDisconnectDat3PullUpResistor(); + + /* Get the SCR. */ + R_TRY(this->GetScr(wb, wb_size)); + const u8 sd_bw = GetSdBusWidths(static_cast(wb)); + const bool spec_under_1_1 = IsLessThanSpecification1_1(static_cast(wb)); + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw, sd_bw)); + + /* Extend the bus speed to as fast as we can. */ + if (this->sd_card_device.IsUhsIMode()) { + R_TRY(this->ExtendBusSpeedAtUhsIMode(max_sm, wb, wb_size)); + } else { + R_TRY(this->ExtendBusSpeedAtNonUhsIMode(max_sm, spec_under_1_1, wb, wb_size)); + } + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_SD_UHS_I) + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #else + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #endif + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + this->max_bus_width = params.bus_width; + this->max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + return ResultSuccess(); + } + + /* Check if we were removed. */ + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + /* Check the csd for errors. */ + if (sdmmc::ResultUnexpectedDeviceCsdValue::Includes(result)) { + u32 csd[DeviceCsdSize / sizeof(u32)]; + this->sd_card_device.GetCsd(csd, sizeof(csd)); + BaseDeviceAccessor::PushErrorLog(false, "%06X%08X%08X%08X", csd[3] & 0x00FFFFFF, csd[2], csd[1], csd[0]); + } + BaseDeviceAccessor::PushErrorTimeStamp(); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + return result; + } + + Result SdCardDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Do the read/write. */ + const Result result = BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, 0, buf, buf_size, is_read); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + return result; + } + + Result SdCardDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_FAILED(result)) { + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + return result; + } + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our sd card device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->sd_card_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* TODO: We probably want this (and other sd card detection stuff) to be conditional pcv control active. */ + /* This will be a requirement to support sd card access with detector in stratosphere before PCV is alive. */ + this->sd_card_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(this->sd_card_device.GetRemovedEvent()); + CallbackInfo ci = { + .inserted_callback = nullptr, + .inserted_callback_arg = this, + .removed_callback = RemovedCallbackEntry, + .removed_callback_arg = this, + }; + this->sd_card_detector->Initialize(std::addressof(ci)); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + this->is_initialized = true; + } + + void SdCardDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + this->sd_card_detector->Finalize(); + this->sd_card_device.FinalizeRemovedEvent(); + } + #endif + } + + Result SdCardDeviceAccessor::Activate() { + /* Activate the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're awake. */ + R_UNLESS(this->sd_card_device.IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check that we're not already active. */ + R_SUCCEED_IF(this->sd_card_device.IsActive()); + + /* Clear the removed event. */ + this->sd_card_device.ClearRemovedEvent(); + + /* Check that the SD card is inserted. */ + R_UNLESS(this->sd_card_detector->IsInserted(), sdmmc::ResultNoDevice()); + } + #endif + + /* Activate the base device. */ + return BaseDeviceAccessor::Activate(); + } + + Result SdCardDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus default speed). */ + R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); + if (IsLessThanSpecification1_1(static_cast(this->work_buffer))) { + *out_speed_mode = SpeedMode_SdCardDefaultSpeed; + return ResultSuccess(); + } + + /* Get the current speed mode. */ + R_TRY(this->IssueCommandCheckSupportedFunction(this->work_buffer, this->work_buffer_size)); + R_TRY(GetCurrentSpeedMode(out_speed_mode, static_cast(this->work_buffer), this->sd_card_device.IsUhsIMode())); + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::PutSdCardToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->sd_card_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + this->sd_card_device.PutToSleep(); + + /* Put the detector to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + this->sd_card_detector->PutToSleep(); + #endif + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) + #else + if (this->sd_card_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void SdCardDeviceAccessor::AwakenSdCard() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (this->sd_card_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + bool force_det = false; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) + #else + if (this->sd_card_device.IsActive()) + #endif + { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_SUCCEEDED(result)) { + force_det = R_FAILED(BaseDeviceAccessor::IssueCommandSendStatus()); + } else { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + force_det = true; + } + } + + /* Wake the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + this->sd_card_detector->Awaken(force_det); + #else + AMS_UNUSED(force_det); + #endif + + /* Wake the device. */ + this->sd_card_device.Awaken(); + } + + Result SdCardDeviceAccessor::GetSdCardScr(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Get the SCR. */ + R_TRY(this->GetScr(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(dst, dst_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast(dst)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Get the status. */ + if (switch_function == SdCardSwitchFunction_CheckSupportedFunction) { + R_TRY(this->IssueCommandCheckSupportedFunction(dst, dst_size)); + } else { + SwitchFunctionAccessMode am; + switch (switch_function) { + case SdCardSwitchFunction_CheckDefault: am = SwitchFunctionAccessMode_Default; break; + case SdCardSwitchFunction_CheckHighSpeed: am = SwitchFunctionAccessMode_HighSpeed; break; + case SdCardSwitchFunction_CheckSdr50: am = SwitchFunctionAccessMode_Sdr50; break; + case SdCardSwitchFunction_CheckSdr104: am = SwitchFunctionAccessMode_Sdr104; break; + case SdCardSwitchFunction_CheckDdr50: am = SwitchFunctionAccessMode_Ddr50; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_TRY(this->IssueCommandSwitchAccessMode(dst, dst_size, false, am)); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast(this->work_buffer)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Determine the access mode. */ + SwitchFunctionAccessMode am; + switch (speed_mode) { + case SpeedMode_SdCardSdr12: + case SpeedMode_SdCardDefaultSpeed: + am = SwitchFunctionAccessMode_Default; + break; + case SpeedMode_SdCardSdr25: + case SpeedMode_SdCardHighSpeed: + am = SwitchFunctionAccessMode_HighSpeed; + break; + case SpeedMode_SdCardSdr50: + am = SwitchFunctionAccessMode_Sdr50; + break; + case SpeedMode_SdCardSdr104: + am = SwitchFunctionAccessMode_Sdr104; + break; + case SpeedMode_SdCardDdr50: + am = SwitchFunctionAccessMode_Ddr50; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Check that the mode is supported. */ + R_TRY(this->IssueCommandSwitchAccessMode(this->work_buffer, this->work_buffer_size, false, am)); + R_UNLESS(IsSupportedAccessMode(static_cast(this->work_buffer), am), sdmmc::ResultSdCardNotSupportAccessMode()); + + /* Get the current consumption. */ + AMS_ABORT_UNLESS(out_current_consumption != nullptr); + *out_current_consumption = GetMaximumCurrentConsumption(static_cast(this->work_buffer)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardSdStatus(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Get the status. */ + R_TRY(this->GetSdStatus(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const { + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + + /* Get the sd status. */ + R_TRY(this->GetSdCardSdStatus(this->work_buffer, this->work_buffer_size)); + const u32 size_of_protected_area = GetSizeOfProtectedArea(static_cast(this->work_buffer)); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + this->sd_card_device.GetCsd(csd, sizeof(csd)); + + /* Handle based on csd version. */ + if (IsLessThanCsdVersion2(csd)) { + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + this->sd_card_device.GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Calculate capacity. */ + *out_num_sectors = size_of_protected_area << ((read_bl_len + c_size_mult + 2) - 9); + } else { + /* SIZE_OF_PROTECTED_AREA is in bytes. */ + *out_num_sectors = size_of_protected_area / SectorSize; + } + + return ResultSuccess(); + } + + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp new file mode 100644 index 0000000..4f37b0c --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_base_device_accessor.hpp" + +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" +#endif + +namespace ams::sdmmc::impl { + + class SdCardDevice : public BaseDevice { + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + mutable os::EventType removed_event; + #endif + u16 rca; + bool is_valid_rca; + bool is_uhs_i_mode; + public: + SdCardDevice() : rca(0) { + this->OnDeactivate(); + } + + virtual void Deactivate() override { + this->OnDeactivate(); + BaseDevice::Deactivate(); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(this->removed_event); + } + #elif defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_SdCard; + } + + virtual u16 GetRca() const override { + AMS_ABORT_UNLESS(this->is_valid_rca); + return this->rca; + } + + void OnDeactivate() { + this->is_valid_rca = false; + this->is_uhs_i_mode = false; + } + + void SetRca(u16 v) { + this->rca = v; + this->is_valid_rca = true; + } + + void SetOcrAndHighCapacity(u32 ocr); + + void SetUhsIMode(bool en) { + this->is_uhs_i_mode = en; + } + + bool IsUhsIMode() const { + return this->is_uhs_i_mode; + } + }; + + enum SdCardApplicationCommandIndex : std::underlying_type::type { + SdApplicationCommandIndex_SetBusWidth = 6, + + SdApplicationCommandIndex_SdStatus = 13, + + SdApplicationCommandIndex_SendNumWriteBlocks = 22, + SdApplicationCommandIndex_SetWriteBlockEraseCount = 23, + + SdApplicationCommandIndex_SdSendOpCond = 41, + SdApplicationCommandIndex_SetClearCardDetect = 42, + + SdApplicationCommandIndex_SendScr = 51, + }; + + enum SwitchFunctionAccessMode { + SwitchFunctionAccessMode_Default = 0, + SwitchFunctionAccessMode_HighSpeed = 1, + SwitchFunctionAccessMode_Sdr50 = 2, + SwitchFunctionAccessMode_Sdr104 = 3, + SwitchFunctionAccessMode_Ddr50 = 4, + }; + + class SdCardDeviceAccessor : public BaseDeviceAccessor { + private: + SdCardDevice sd_card_device; + void *work_buffer; + size_t work_buffer_size; + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + DeviceDetector *sd_card_detector; + #endif + BusWidth max_bus_width; + SpeedMode max_speed_mode; + bool is_initialized; + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void RemovedCallback(); + + static void RemovedCallbackEntry(void *arg) { + static_cast(arg)->RemovedCallback(); + } + #endif + + Result IssueCommandSendRelativeAddr(u16 *out_rca) const; + Result IssueCommandSendIfCond() const; + Result IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const; + Result IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const; + Result IssueCommandVoltageSwitch() const; + Result IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask = 0) const; + Result IssueCommandSetBusWidth4Bit() const; + Result IssueCommandSdStatus(void *dst, size_t dst_size) const; + Result IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const; + Result IssueCommandClearCardDetect() const; + Result IssueCommandSendScr(void *dst, size_t dst_size) const; + + Result EnterUhsIMode(); + Result ChangeToReadyState(bool spec_under_2, bool uhs_i_supported); + Result ChangeToStbyStateAndGetRca(); + Result SetMemoryCapacity(const void *csd); + Result GetScr(void *dst, size_t dst_size) const; + Result ExtendBusWidth(BusWidth max_bw, u8 sd_bw); + Result SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size); + Result ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size); + Result ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size); + Result GetSdStatus(void *dst, size_t dst_size) const; + Result StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + + void TryDisconnectDat3PullUpResistor() const; + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result Activate() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + explicit SdCardDeviceAccessor(IHostController *hc, DeviceDetector *dd) : BaseDeviceAccessor(hc), sd_card_detector(dd) + #else + explicit SdCardDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc) + #endif + { + this->work_buffer = nullptr; + this->work_buffer_size = 0; + this->max_bus_width = BusWidth_4Bit; + this->max_speed_mode = SpeedMode_SdCardSdr104; + this->is_initialized = false; + } + + void SetSdCardWorkBuffer(void *wb, size_t wb_size) { + this->work_buffer = wb; + this->work_buffer_size = wb_size; + } + + void PutSdCardToSleep(); + void AwakenSdCard(); + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const; + Result GetSdCardScr(void *dst, size_t dst_size) const; + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const; + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const; + Result GetSdCardSdStatus(void *dst, size_t dst_size) const; + + bool IsSdCardInserted() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->IsInserted(); + #else + AMS_ABORT("IsSdCardInserted without SdCardDetector"); + #endif + } + + bool IsSdCardRemoved() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_device.IsRemoved(); + #else + AMS_ABORT("IsSdCardRemoved without SdCardDetector"); + #endif + } + + void RegisterSdCardDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->RegisterDetectionEventCallback(cb, arg); + #else + AMS_UNUSED(cb, arg); + AMS_ABORT("RegisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + + void UnregisterSdCardDetectionEventCallback() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->UnregisterDetectionEventCallback(); + #else + AMS_ABORT("UnregisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp new file mode 100644 index 0000000..f03d153 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp @@ -0,0 +1,1039 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_timer.hpp" + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + #include +#endif + + +namespace ams::sdmmc::impl { + + namespace { + + constexpr inline u32 ControllerReactionTimeoutMilliSeconds = 2000; + constexpr inline u32 CommandTimeoutMilliSeconds = 2000; + constexpr inline u32 DefaultCheckTransferIntervalMilliSeconds = 1500; + constexpr inline u32 BusyTimeoutMilliSeconds = 2000; + + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::ResetBufferInfos() { + for (auto &info : this->buffer_infos) { + info.buffer_address = 0; + info.buffer_size = 0; + } + } + + dd::DeviceVirtualAddress SdHostStandardController::GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size) { + /* Try to find the buffer in our registered regions. */ + dd::DeviceVirtualAddress device_addr = 0; + for (const auto &info : this->buffer_infos) { + if (info.buffer_address <= buffer && (buffer + buffer_size) <= (info.buffer_address + info.buffer_size)) { + device_addr = info.buffer_device_virtual_address + (buffer - info.buffer_address); + break; + } + } + + /* Ensure that we found the buffer. */ + AMS_ABORT_UNLESS(device_addr != 0); + return device_addr; + } + #endif + + void SdHostStandardController::EnsureControl() { + /* Perform a read of clock control to be sure previous configuration takes. */ + reg::Read(this->registers->clock_control); + } + + Result SdHostStandardController::EnableInternalClock() { + /* Enable internal clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, OSCILLATE)); + this->EnsureControl(); + + /* Wait for the internal clock to become stable. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the clock is steady. */ + if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, READY))) { + break; + } + + /* If not, check for timeout. */ + R_UNLESS(timer.Update(), sdmmc::ResultInternalClockStableSoftwareTimeout()); + } + } + + /* Configure to use host controlled divided clock. */ + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER)); + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK)); + + /* Set host version 4.0.0 enable. */ + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, VERSION_4)); + + /* Set host 64 bit addressing enable. */ + AMS_ABORT_UNLESS(reg::HasValue(this->registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED))); + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING)); + + /* Select SDMA mode. */ + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DMA_SELECT, SDMA)); + + /* Configure timeout control to use the maximum timeout value (TMCLK * 2^27) */ + reg::ReadWrite(this->registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110)); + + return ResultSuccess(); + } + + void SdHostStandardController::SetBusPower(BusPower bus_power) { + /* Check that we support the bus power. */ + AMS_ABORT_UNLESS(this->IsSupportedBusPower(bus_power)); + + /* Set the appropriate power. */ + switch (bus_power) { + case BusPower_Off: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF)); + break; + case BusPower_1_8V: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V)); + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + case BusPower_3_3V: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V)); + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::EnableInterruptStatus() { + /* Set the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + + /* Read/write the interrupt enables to be sure they take. */ + reg::Write(this->registers->normal_int_enable, reg::Read(this->registers->normal_int_enable)); + reg::Write(this->registers->error_int_enable, reg::Read(this->registers->error_int_enable)); + + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Clear interrupts. */ + this->ClearInterrupt(); + + /* Enable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + } + #endif + } + + void SdHostStandardController::DisableInterruptStatus() { + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Disable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + #endif + + /* Mask the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result SdHostStandardController::WaitInterrupt(u32 timeout_ms) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait for the interrupt to be signaled. */ + os::WaitableHolderType *signaled_holder = os::TimedWaitAny(std::addressof(this->waitable_manager), TimeSpan::FromMilliSeconds(timeout_ms)); + if (signaled_holder == std::addressof(this->interrupt_event_holder)) { + /* We received the interrupt. */ + return ResultSuccess(); + } else if (signaled_holder == std::addressof(this->removed_event_holder)) { + /* The device was removed. */ + return sdmmc::ResultDeviceRemoved(); + } else { + /* Timeout occurred. */ + return sdmmc::ResultWaitInterruptSoftwareTimeout(); + } + } + + void SdHostStandardController::ClearInterrupt() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Clear the interrupt event. */ + os::ClearInterruptEvent(this->interrupt_event); + } + #endif + + void SdHostStandardController::SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data) { + /* Ensure the transfer data is valid. */ + AMS_ABORT_UNLESS(xfer_data->block_size != 0); + AMS_ABORT_UNLESS(xfer_data->num_blocks != 0); + + /* Determine the number of blocks. */ + const u16 num_blocks = std::min(xfer_data->num_blocks, SdHostStandardRegisters::BlockCountMax); + + /* Determine the address/how many blocks to transfer. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + const u64 address = this->GetDeviceVirtualAddress(reinterpret_cast(xfer_data->buffer), xfer_data->block_size * num_blocks); + const u16 num_xfer_blocks = num_blocks; + #else + const u64 address = reinterpret_cast(xfer_data->buffer); + const u16 num_xfer_blocks = num_blocks; + #endif + + /* Verify the address is usable. */ + AMS_ABORT_UNLESS(util::IsAligned(address, BufferDeviceVirtualAddressAlignment)); + + /* Configure for sdma. */ + reg::Write(this->registers->adma_address, static_cast(address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(address >> BITSIZEOF(u32))); + + /* Set our next sdma address. */ + this->next_sdma_address = util::AlignDown(address + SdmaBufferBoundary, SdmaBufferBoundary); + + /* Configure block size. */ + AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 512_KB), + SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast(xfer_data->block_size))); + + /* Configure transfer blocks. */ + reg::Write(this->registers->block_count, num_xfer_blocks); + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_xfer_blocks; + } + + /* Configure transfer mode. */ + reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM (TRANSFER_MODE_DMA_ENABLE, ENABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_BLOCK_COUNT_ENABLE, (xfer_data->is_multi_block_transfer), ENABLE, DISABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_MULTI_BLOCK_SELECT, (xfer_data->is_multi_block_transfer), MULTI_BLOCK, SINGLE_BLOCK), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, (xfer_data->transfer_direction == TransferDirection_ReadFromDevice), READ, WRITE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE)); + } + + void SdHostStandardController::SetTransferForTuning() { + /* Get the tuning block size. */ + u16 tuning_block_size; + switch (this->GetBusWidth()) { + case BusWidth_4Bit: + tuning_block_size = 64; + break; + case BusWidth_8Bit: + tuning_block_size = 128; + break; + case BusWidth_1Bit: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure block size. */ + AMS_ABORT_UNLESS(tuning_block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(this->registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size)); + + /* Configure transfer blocks. */ + reg::Write(this->registers->block_count, 1); + + /* Configure transfer mode. */ + reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, READ)); + } + + void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) { + /* Encode the command value. */ + u16 command_val = 0; + + /* Encode the response type. */ + switch (command->response_type) { + case ResponseType_R0: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, NO_RESPONSE), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R1: + case ResponseType_R6: + case ResponseType_R7: + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_RESPONSE_TYPE, command->is_busy, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM (COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM (COMMAND_INDEX_CHECK, ENABLE)); + break; + case ResponseType_R2: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_136), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R3: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Encode the data present select. */ + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_DATA_PRESENT, has_xfer_data, DATA_PRESENT, NO_DATA_PRESENT)); + + /* Encode the command index. */ + AMS_ABORT_UNLESS(command->command_index <= SdHostStandardCommandIndexMax); + command_val |= reg::Encode(SD_REG_BITS_VALUE(COMMAND_COMMAND_INDEX, command->command_index)); + + /* Write the command and argument. */ + reg::Write(this->registers->argument, command->command_argument); + reg::Write(this->registers->command, command_val); + } + + void SdHostStandardController::SetCommandForTuning(u32 command_index) { + Command command(command_index, 0, ResponseType_R1, false); + return this->SetCommand(std::addressof(command), true); + } + + Result SdHostStandardController::ResetCmdDatLine() { + /* Set the software reset cmd/dat bits. */ + reg::ReadWrite(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, RESET)); + + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait until reset is done. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, WORK), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, WORK))) + { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + return sdmmc::ResultAbortTransactionSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::AbortTransaction() { + R_TRY(this->ResetCmdDatLine()); + return ResultSuccess(); + } + + Result SdHostStandardController::WaitWhileCommandInhibit(bool has_dat) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command inhibit cmd is set. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitCmdSoftwareTimeout(); + } + } + } + + /* Wait while command inhibit dat is set. */ + if (has_dat) { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitDatSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) { + /* Read the statuses. */ + volatile u16 normal_int_status = reg::Read(this->registers->normal_int_status); + volatile u16 error_int_status = reg::Read(this->registers->error_int_status); + volatile u16 auto_cmd_err_status = reg::Read(this->registers->acmd12_err); + + /* Set the output status, if necessary. */ + if (out_normal_int_status != nullptr) { + *out_normal_int_status = normal_int_status; + } + + /* If we don't have an error interrupt, just use the normal status. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, NO_ERROR))) { + /* If the wait mask has any bits set, we're done waiting for the interrupt. */ + const u16 masked_status = (normal_int_status & wait_mask); + R_UNLESS(masked_status != 0, sdmmc::ResultNoWaitedInterrupt()); + + /* Write the masked value to the status register to ensure consistent state. */ + reg::Write(this->registers->normal_int_status, masked_status); + return ResultSuccess(); + } + + /* We have an error interrupt. Write the status to the register to ensure consistent state. */ + reg::Write(this->registers->error_int_status, error_int_status); + + /* Check the error interrupt status bits, and return appropriate errors. */ + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, NO_ERROR)), sdmmc::ResultResponseIndexError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, NO_ERROR)), sdmmc::ResultResponseEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, NO_ERROR)), sdmmc::ResultResponseCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, NO_ERROR)), sdmmc::ResultResponseTimeoutError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, NO_ERROR)), sdmmc::ResultDataEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, NO_ERROR)), sdmmc::ResultDataCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, NO_ERROR)), sdmmc::ResultDataTimeoutError()); + + /* Check for auto cmd errors. */ + if (reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, ERROR))) { + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, NO_ERROR)), sdmmc::ResultAutoCommandResponseIndexError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, NO_ERROR)), sdmmc::ResultAutoCommandResponseEndBitError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, NO_ERROR)), sdmmc::ResultAutoCommandResponseCrcError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, NO_ERROR)), sdmmc::ResultAutoCommandResponseTimeoutError()); + + /* An known auto cmd error occurred. */ + return sdmmc::ResultSdHostStandardUnknownAutoCmdError(); + } else { + /* Unknown error occurred. */ + return sdmmc::ResultSdHostStandardUnknownError(); + } + } + + Result SdHostStandardController::WaitCommandComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(CommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + this->ClearInterrupt(); + + if (R_FAILED(result)) { + this->AbortTransaction(); + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* If the device wasn't removed, cancel our transaction. */ + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } + #else + { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command is not complete. */ + { + ManualTimer timer(CommandTimeoutMilliSeconds); + while (true) { + /* Check and clear the interrupt status. */ + const auto result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + + /* If we succeeded, we're done. */ + if (R_SUCCEEDED(result)) { + return ResultSuccess(); + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitTransferComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(this->check_transfer_interval_ms); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + volatile u16 normal_int_status; + result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + this->ClearInterrupt(); + + /* If the interrupt succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else { + /* Abort the transaction. */ + this->AbortTransaction(); + return result; + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* Otherwise, timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + } + } + } + #else + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait until transfer times out. */ + { + ManualTimer timer(this->check_transfer_interval_ms); + while (true) { + /* Check/clear our interrupt status. */ + volatile u16 normal_int_status; + const auto result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + /* If the check succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + /* Only timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + break; + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitWhileBusy() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while busy. */ + { + ManualTimer timer(BusyTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* If the DAT0 line signal is level high, we're done. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) { + return ResultSuccess(); + } + + /* Otherwise, check if we're timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultBusySoftwareTimeout(); + } + } + } + } + + void SdHostStandardController::GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can write the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = reg::Read(this->registers->response[0]); + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = reg::Read(this->registers->response[0]); + out_response[1] = reg::Read(this->registers->response[1]); + out_response[2] = reg::Read(this->registers->response[2]); + out_response[3] = reg::Read(this->registers->response[3]); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result SdHostStandardController::IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit((xfer_data != nullptr) || command->is_busy)); + + /* Configure for the transfer. */ + u32 num_transferred_blocks = 0; + if (xfer_data != nullptr) { + /* Setup the transfer, and get the number of blocks. */ + this->SetTransfer(std::addressof(num_transferred_blocks), xfer_data); + + /* Ensure the device sees consistent data with the cpu. */ + dd::FlushDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Issue the command with interrupt status enabled. */ + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + this->SetCommand(command, xfer_data != nullptr); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + + /* Process any response. */ + if (command->response_type != ResponseType_R0) { + this->last_response_type = command->response_type; + this->GetResponse(this->last_response, sizeof(this->last_response), this->last_response_type); + } + + /* Wait for data to be transferred. */ + if (xfer_data != nullptr) { + R_TRY(this->WaitTransferComplete()); + } + } + + /* If data was transferred, ensure we're in a consistent state. */ + if (xfer_data != nullptr) { + /* Ensure the cpu sees consistent data with the device. */ + if (xfer_data->transfer_direction == TransferDirection_ReadFromDevice) { + dd::InvalidateDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Set the number of transferred blocks. */ + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_transferred_blocks; + } + + /* Process stop transition command. */ + this->last_stop_transmission_response = this->registers->response[3]; + } + + /* Wait until we're no longer busy. */ + if (command->is_busy || (xfer_data != nullptr)) { + R_TRY(this->WaitWhileBusy()); + } + + return ResultSuccess(); + } + + Result SdHostStandardController::IssueStopTransmissionCommandWithDeviceClock(u32 *out_response) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit(false)); + + /* Issue the command with interrupt status enabled. */ + constexpr ResponseType StopTransmissionCommandResponseType = ResponseType_R1; + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + Command command(CommandIndex_StopTransmission, 0, StopTransmissionCommandResponseType, true); + this->SetCommand(std::addressof(command), false); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + } + + /* Process response. */ + this->GetResponse(out_response, sizeof(u32), StopTransmissionCommandResponseType); + + /* Wait until we're done. */ + R_TRY(this->WaitWhileBusy()); + + return ResultSuccess(); + } + + SdHostStandardController::SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size) { + /* Translate the physical address to a address. */ + const uintptr_t registers_addr = dd::QueryIoMapping(registers_phys_addr, registers_size); + + /* Set registers. */ + AMS_ABORT_UNLESS(registers_addr != 0); + this->registers = reinterpret_cast(registers_addr); + + /* Reset DMA buffers, if we have any. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + this->ResetBufferInfos(); + #endif + + /* Clear removed event, if we have one. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->removed_event = nullptr; + #endif + + /* Clear dma address. */ + this->next_sdma_address = 0; + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + + /* Clear clock/power trackers. */ + this->device_clock_frequency_khz = 0; + this->is_power_saving_enable = false; + this->is_device_clock_enable = false; + + /* Clear last response. */ + this->last_response_type = ResponseType_R0; + std::memset(this->last_response, 0, sizeof(this->last_response)); + this->last_stop_transmission_response = 0; + } + + void SdHostStandardController::Initialize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InitializeWaitableManager(std::addressof(this->waitable_manager)); + + AMS_ABORT_UNLESS(this->interrupt_event != nullptr); + os::InitializeWaitableHolder(std::addressof(this->interrupt_event_holder), this->interrupt_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->interrupt_event_holder)); + + if (this->removed_event != nullptr) { + os::InitializeWaitableHolder(std::addressof(this->removed_event_holder), this->removed_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->removed_event_holder)); + } + } + #endif + } + + void SdHostStandardController::Finalize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + if (this->removed_event != nullptr) { + os::UnlinkWaitableHolder(std::addressof(this->removed_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->removed_event_holder)); + } + + os::UnlinkWaitableHolder(std::addressof(this->interrupt_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->interrupt_event_holder)); + + os::FinalizeWaitableManager(std::addressof(this->waitable_manager)); + } + #endif + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and set a free info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + info = { + .buffer_address = buffer, + .buffer_size = buffer_size, + .buffer_device_virtual_address = buffer_device_virtual_address, + }; + return; + } + } + + AMS_ABORT("Out of BufferInfos\n"); + } + + void SdHostStandardController::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and clear the buffer info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + AMS_ABORT_UNLESS(info.buffer_size == buffer_size); + AMS_ABORT_UNLESS(info.buffer_device_virtual_address == buffer_device_virtual_address); + + info.buffer_address = 0; + info.buffer_size = 0; + return; + } + } + + AMS_ABORT("BufferInfo not found\n"); + } + #endif + + void SdHostStandardController::SetWorkBuffer(void *wb, size_t wb_size) { + AMS_UNUSED(wb, wb_size); + AMS_ABORT("WorkBuffer is not needed\n"); + } + + BusPower SdHostStandardController::GetBusPower() const { + /* Check if the bus has power. */ + if (reg::HasValue(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON))) { + /* If it does, return the corresponding power. */ + switch (reg::GetValue(this->registers->power_control, SD_REG_BITS_MASK(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1))) { + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_1_8V: + return BusPower_1_8V; + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_3_3V: + return BusPower_3_3V; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* It doesn't, so it's off. */ + return BusPower_Off; + } + } + + void SdHostStandardController::SetBusWidth(BusWidth bus_width) { + /* Check that we support the bus width. */ + AMS_ABORT_UNLESS(this->IsSupportedBusWidth(bus_width)); + + /* Set the appropriate data transfer width. */ + switch (bus_width) { + case BusWidth_1Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_4Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_8Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + BusWidth SdHostStandardController::GetBusWidth() const { + /* Check if the bus is using eight-bit extended data transfer. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT))) { + return BusWidth_8Bit; + } else { + /* Bus is configured as USE_DATA_TRANSFER_WIDTH, so check if it's four bit. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT))) { + return BusWidth_4Bit; + } else { + return BusWidth_1Bit; + } + } + } + + void SdHostStandardController::SetPowerSaving(bool en) { + /* Set whether we're power saving enable. */ + this->is_power_saving_enable = en; + + /* Configure accordingly. */ + if (this->is_power_saving_enable) { + /* We want to disable SD clock if it's enabled. */ + if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + } else { + /* We want to enable SD clock if it's disabled and we're supposed to enable device clock. */ + if (this->is_device_clock_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + } + } + + void SdHostStandardController::EnableDeviceClock() { + /* If we're not in power-saving mode and the device clock is disabled, enable it. */ + if (!this->is_power_saving_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + this->is_device_clock_enable = true; + } + + void SdHostStandardController::DisableDeviceClock() { + /* Unconditionally disable the device clock. */ + this->is_device_clock_enable = false; + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) { + this->check_transfer_interval_ms = ms; + } + + void SdHostStandardController::SetDefaultCheckTransferInterval() { + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + } + + Result SdHostStandardController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueCommandWithDeviceClock(command, xfer_data, out_num_transferred_blocks); + } + } + + Result SdHostStandardController::IssueStopTransmissionCommand(u32 *out_response) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueStopTransmissionCommandWithDeviceClock(out_response); + } + } + + void SdHostStandardController::GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_type == this->last_response_type); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = this->last_response[0]; + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = this->last_response[0]; + out_response[1] = this->last_response[1]; + out_response[2] = this->last_response[2]; + out_response[3] = this->last_response[3]; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_size >= sizeof(u32)); + + /* Get the response. */ + out_response[0] = this->last_stop_transmission_response; + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp new file mode 100644 index 0000000..7bdf0a3 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_i_host_controller.hpp" +#include "sdmmc_sd_host_standard_registers.hpp" + +namespace ams::sdmmc::impl { + + class SdHostStandardController : public IHostController { + protected: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + struct BufferInfo { + uintptr_t buffer_address; + size_t buffer_size; + dd::DeviceVirtualAddress buffer_device_virtual_address; + }; + + static constexpr inline auto NumBufferInfos = 3; + #endif + protected: + SdHostStandardRegisters *registers; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + BufferInfo buffer_infos[NumBufferInfos]; + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + os::WaitableManagerType waitable_manager; + os::InterruptEventType *interrupt_event; + os::WaitableHolderType interrupt_event_holder; + os::EventType *removed_event; + os::WaitableHolderType removed_event_holder; + #endif + + u64 next_sdma_address; + u32 check_transfer_interval_ms; + + u32 device_clock_frequency_khz; + bool is_power_saving_enable; + bool is_device_clock_enable; + + ResponseType last_response_type; + u32 last_response[4]; + u32 last_stop_transmission_response; + protected: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void PreSetInterruptEvent(os::InterruptEventType *ie) { + this->interrupt_event = ie; + } + + bool IsRemoved() const { + return this->removed_event != nullptr && os::TryWaitEvent(this->removed_event); + } + #endif + + void SetDeviceClockFrequencyKHz(u32 khz) { + this->device_clock_frequency_khz = khz; + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void ResetBufferInfos(); + dd::DeviceVirtualAddress GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size); + #endif + + void EnsureControl(); + Result EnableInternalClock(); + void SetBusPower(BusPower bus_power); + + void EnableInterruptStatus(); + void DisableInterruptStatus(); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result WaitInterrupt(u32 timeout_ms); + void ClearInterrupt(); + #endif + + void SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data); + void SetTransferForTuning(); + + void SetCommand(const Command *command, bool has_xfer_data); + void SetCommandForTuning(u32 command_index); + + Result ResetCmdDatLine(); + Result AbortTransaction(); + Result WaitWhileCommandInhibit(bool has_dat); + Result CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask); + Result WaitCommandComplete(); + Result WaitTransferComplete(); + Result WaitWhileBusy(); + + void GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const; + + Result IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks); + Result IssueStopTransmissionCommandWithDeviceClock(u32 *out_response); + + ALWAYS_INLINE Result CheckRemoved() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + return ResultSuccess(); + } + public: + SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(os::EventType *e) override { + this->removed_event = e; + } + #endif + + virtual void Initialize() override; + virtual void Finalize() override; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) override; + + virtual Result SwitchToSdr12() override { + AMS_ABORT("SwitchToSdr12 not supported\n"); + } + + virtual BusPower GetBusPower() const override; + + virtual void SetBusWidth(BusWidth bus_width) override; + virtual BusWidth GetBusWidth() const override; + + virtual u32 GetDeviceClockFrequencyKHz() const override { + return this->device_clock_frequency_khz; + } + + virtual void SetPowerSaving(bool en) override; + virtual bool IsPowerSavingEnable() const override { + return this->is_power_saving_enable; + } + + virtual void EnableDeviceClock() override; + virtual void DisableDeviceClock() override; + virtual bool IsDeviceClockEnable() const override { + return this->is_device_clock_enable; + } + + virtual u32 GetMaxTransferNumBlocks() const override { + return SdHostStandardRegisters::BlockCountMax; + } + + virtual void ChangeCheckTransferInterval(u32 ms) override; + virtual void SetDefaultCheckTransferInterval() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const override; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const override; + + virtual bool IsSupportedTuning() const override { + return false; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) { + AMS_UNUSED(speed_mode, command_index); + AMS_ABORT("Tuning not supported\n"); + } + + virtual void SaveTuningStatusForHs400() { + AMS_ABORT("SaveTuningStatusForHs400 not supported\n"); + } + + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp new file mode 100644 index 0000000..e104374 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::sdmmc::impl { + + struct SdHostStandardRegisters { + volatile uint32_t dma_address; + volatile uint16_t block_size; + volatile uint16_t block_count; + volatile uint32_t argument; + volatile uint16_t transfer_mode; + volatile uint16_t command; + volatile uint32_t response[0x4]; + volatile uint32_t buffer; + volatile uint32_t present_state; + volatile uint8_t host_control; + volatile uint8_t power_control; + volatile uint8_t block_gap_control; + volatile uint8_t wake_up_control; + volatile uint16_t clock_control; + volatile uint8_t timeout_control; + volatile uint8_t software_reset; + volatile uint16_t normal_int_status; + volatile uint16_t error_int_status; + volatile uint16_t normal_int_enable; + volatile uint16_t error_int_enable; + volatile uint16_t normal_signal_enable; + volatile uint16_t error_signal_enable; + volatile uint16_t acmd12_err; + volatile uint16_t host_control2; + volatile uint32_t capabilities; + volatile uint32_t capabilities_1; + volatile uint32_t max_current; + volatile uint32_t _0x4c; + volatile uint16_t set_acmd12_error; + volatile uint16_t set_int_error; + volatile uint8_t adma_error; + volatile uint8_t _0x56[0x3]; + volatile uint32_t adma_address; + volatile uint32_t upper_adma_address; + volatile uint16_t preset_for_init; + volatile uint16_t preset_for_default; + volatile uint16_t preset_for_high; + volatile uint16_t preset_for_sdr12; + volatile uint16_t preset_for_sdr25; + volatile uint16_t preset_for_sdr50; + volatile uint16_t preset_for_sdr104; + volatile uint16_t preset_for_ddr50; + volatile uint32_t _0x70[0x23]; + volatile uint16_t slot_int_status; + volatile uint16_t host_version; + + static constexpr inline u16 BlockCountMax = 0xFFFF; + }; + static_assert(sizeof(SdHostStandardRegisters) == 0x100); + + constexpr inline size_t SdmaBufferBoundary = 512_KB; + + #define SD_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SD_HOST_STANDARD, NAME) + #define SD_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SD_HOST_STANDARD, NAME, VALUE) + #define SD_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SD_HOST_STANDARD, NAME, ENUM) + #define SD_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SD_HOST_STANDARD, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_SD_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SD_HOST_STANDARD, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_SD_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_SD_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_SD_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_SD_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_SD_REG(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, 0, 12); + DEFINE_SD_REG_THREE_BIT_ENUM(BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 12, 4_KB, 8_KB, 16_KB, 32_KB, 64_KB, 128_KB, 256_KB, 512_KB); + constexpr inline size_t SdHostStandardBlockSizeTransferBlockSizeMax = 0xFFF; + + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DMA_ENABLE, 0, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_BLOCK_COUNT_ENABLE, 1, DISABLE, ENABLE); + DEFINE_SD_REG_TWO_BIT_ENUM(TRANSFER_MODE_AUTO_CMD_ENABLE, 2, DISABLE, CMD12_ENABLE, CMD23_ENABLE, AUTO_SELECT); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, 4, WRITE, READ); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_MULTI_BLOCK_SELECT, 5, SINGLE_BLOCK, MULTI_BLOCK); + + DEFINE_SD_REG_TWO_BIT_ENUM(COMMAND_RESPONSE_TYPE, 0, NO_RESPONSE, RESPONSE_LENGTH_136, RESPONSE_LENGTH_48, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_CRC_CHECK, 3, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_INDEX_CHECK, 4, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_DATA_PRESENT, 5, NO_DATA_PRESENT, DATA_PRESENT); + DEFINE_SD_REG(COMMAND_COMMAND_INDEX, 8, 6); + constexpr inline size_t SdHostStandardCommandIndexMax = 0x3F; + + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, 0, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, 1, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, 20, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT1_LINE_SIGNAL_LEVEL, 21, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT2_LINE_SIGNAL_LEVEL, 22, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT3_LINE_SIGNAL_LEVEL, 23, LOW, HIGH); + DEFINE_SD_REG(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 20, 4); + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, 1, ONE_BIT, FOUR_BIT); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, 2, NORMAL_SPEED, HIGH_SPEED); + DEFINE_SD_REG_TWO_BIT_ENUM(HOST_CONTROL_DMA_SELECT, 3, SDMA, RESERVED1, ADMA2, ADMA2_OR_ADMA3); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, 5, USE_DATA_TRANSFER_WIDTH, EIGHT_BIT); + + DEFINE_SD_REG_BIT_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, 0, OFF, ON); + DEFINE_SD_REG_THREE_BIT_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1, RESERVED0, RESERVED1, RESERVED2, RESERVED3, RESERVED4, 1_8V, 3_0V, 3_3V); + + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, 0, STOP, OSCILLATE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, 1, NOT_READY, READY); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, 2, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, 5, DIVIDED_CLOCK, PROGRAMMABLE_CLOCK); + DEFINE_SD_REG(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, 6, 2); + DEFINE_SD_REG(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, 8, 8); + + DEFINE_SD_REG(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0, 4); + + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_ALL, 0, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_CMD, 1, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_DAT, 2, WORK, RESET); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, 0, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, 1, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, 3, NOT_GENERATED, GENERATED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, 15, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, 0, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, 4, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, 5, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, 6, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, 8, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, 4, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, 5, MASKED, ENABLED); + + #define SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, __ENUM__) + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, 5, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, 6, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, 8, MASKED, ENABLED); + + #define SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, __ENUM__) + + + DEFINE_SD_REG_THREE_BIT_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, 0, SDR12, SDR25, SDR50, SDR104, DDR50, HS400, RSVD6, UHS_II); + + constexpr inline auto SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_HS200 = SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_SDR104; + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3, 3_3V_SIGNALING, 1_8V_SIGNALING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_EXECUTE_TUNING, 6, TUNING_COMPLETED, EXECUTE_TUNING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, 7, USING_FIXED_CLOCK, USING_TUNED_CLOCK); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, 12, VERSION_300_COMPATIBLE, VERSION_4); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 13, 32_BIT_ADDRESSING, 64_BIT_ADDRESSING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, 15, HOST_DRIVER, AUTOMATIC_SELECTION); + + DEFINE_SD_REG_BIT_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, 28, NOT_SUPPORTED, SUPPORTED); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp new file mode 100644 index 0000000..3b16008 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp @@ -0,0 +1,1311 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" +#include "sdmmc_io_impl.board.nintendo_nx.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + /* FOR REFERENCE: board-specific sdmmc registers. */ + //struct SdmmcRegisters { + // /* Standard registers. */ + // volatile SdHostStandardRegisters sd_host_standard_registers; + // + // /* Vendor specific registers */ + // volatile uint32_t vendor_clock_cntrl; + // volatile uint32_t vendor_sys_sw_cntrl; + // volatile uint32_t vendor_err_intr_status; + // volatile uint32_t vendor_cap_overrides; + // volatile uint32_t vendor_boot_cntrl; + // volatile uint32_t vendor_boot_ack_timeout; + // volatile uint32_t vendor_boot_dat_timeout; + // volatile uint32_t vendor_debounce_count; + // volatile uint32_t vendor_misc_cntrl; + // volatile uint32_t max_current_override; + // volatile uint32_t max_current_override_hi; + // volatile uint32_t _0x12c[0x20]; + // volatile uint32_t vendor_io_trim_cntrl; + // + // /* Start of sdmmc2/sdmmc4 only */ + // volatile uint32_t vendor_dllcal_cfg; + // volatile uint32_t vendor_dll_ctrl0; + // volatile uint32_t vendor_dll_ctrl1; + // volatile uint32_t vendor_dllcal_cfg_sta; + // /* End of sdmmc2/sdmmc4 only */ + // + // volatile uint32_t vendor_tuning_cntrl0; + // volatile uint32_t vendor_tuning_cntrl1; + // volatile uint32_t vendor_tuning_status0; + // volatile uint32_t vendor_tuning_status1; + // volatile uint32_t vendor_clk_gate_hysteresis_count; + // volatile uint32_t vendor_preset_val0; + // volatile uint32_t vendor_preset_val1; + // volatile uint32_t vendor_preset_val2; + // volatile uint32_t sdmemcomppadctrl; + // volatile uint32_t auto_cal_config; + // volatile uint32_t auto_cal_interval; + // volatile uint32_t auto_cal_status; + // volatile uint32_t io_spare; + // volatile uint32_t sdmmca_mccif_fifoctrl; + // volatile uint32_t timeout_wcoal_sdmmca; + // volatile uint32_t _0x1fc; + //}; + + DEFINE_SD_REG_BIT_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, 2, NORMAL, OVERRIDE); + DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TAP_VAL, 16, 8); + DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TRIM_VAL, 24, 5); + + DEFINE_SD_REG(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 8, 6); + + DEFINE_SD_REG(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 2, 1); + + DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, 31, DISABLE, ENABLE); + + DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, 31, DONE, RUNNING); + + DEFINE_SD_REG(VENDOR_TUNING_CNTRL0_MUL_M, 6, 7); + DEFINE_SD_REG_THREE_BIT_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, 13, TRIES_40, TRIES_64, TRIES_128, TRIES_192, TRIES_256, RESERVED5, RESERVED6, RESERVED7); + DEFINE_SD_REG_BIT_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, 17, NOT_UPDATED_BY_HW, UPDATED_BY_HW); + + DEFINE_SD_REG(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, 0, 4); + DEFINE_SD_REG(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 31, 1); + + DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, 0, 7); + DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, 8, 7); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, 29, DISABLED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, 31, DISABLED, ENABLED); + + DEFINE_SD_REG(AUTO_CAL_STATUS_AUTO_CAL_PULLUP, 0, 7); + DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, 31, INACTIVE, ACTIVE); + + DEFINE_SD_REG_BIT_ENUM(IO_SPARE_SPARE_OUT_3, 19, TWO_CYCLE_DELAY, ONE_CYCLE_DELAY); + + namespace { + + constexpr inline u32 TuningCommandTimeoutMilliSeconds = 5; + + constexpr void GetDividerSetting(u32 *out_target_clock_frequency_khz, u16 *out_x, SpeedMode speed_mode) { + switch (speed_mode) { + case SpeedMode_MmcIdentification: + *out_target_clock_frequency_khz = 26000; + *out_x = 66; + break; + case SpeedMode_MmcLegacySpeed: + *out_target_clock_frequency_khz = 26000; + *out_x = 1; + break; + case SpeedMode_MmcHighSpeed: + *out_target_clock_frequency_khz = 52000; + *out_x = 1; + break; + case SpeedMode_MmcHs200: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_MmcHs400: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_SdCardIdentification: + *out_target_clock_frequency_khz = 25000; + *out_x = 64; + break; + case SpeedMode_SdCardDefaultSpeed: + *out_target_clock_frequency_khz = 25000; + *out_x = 1; + break; + case SpeedMode_SdCardHighSpeed: + *out_target_clock_frequency_khz = 50000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr12: + *out_target_clock_frequency_khz = 25000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr50: + *out_target_clock_frequency_khz = 100000; + *out_x = 1; + break; + case SpeedMode_SdCardSdr104: + *out_target_clock_frequency_khz = 200000; + *out_x = 1; + break; + case SpeedMode_GcAsicFpgaSpeed: + *out_target_clock_frequency_khz = 40800; + *out_x = 1; + break; + case SpeedMode_GcAsicSpeed: + *out_target_clock_frequency_khz = 200000; + *out_x = 2; + break; + case SpeedMode_SdCardSdr25: + case SpeedMode_SdCardDdr50: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + constinit os::SdkMutex g_soc_mutex; + + #define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex) + + #else + + #define AMS_SDMMC_LOCK_SOC_MUTEX() + + #endif + + constinit bool g_determined_soc = false; + constinit bool g_is_soc_mariko = false; + + } + + bool IsSocMariko() { + if (!g_determined_soc) { + /* Ensure we have exclusive access to the soc variables. */ + AMS_SDMMC_LOCK_SOC_MUTEX(); + + /* Check the SocType. */ + #if defined(ATMOSPHERE_IS_EXOSPHERE) + { + g_is_soc_mariko = fuse::GetSocType() == fuse::SocType_Mariko; + } + #elif defined(ATMOSPHERE_IS_MESOSPHERE) + { + MESOSPHERE_TODO("Detect mariko via KSystemControl call?"); + } + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + { + /* Connect to spl for the duration of our check. */ + spl::Initialize(); + ON_SCOPE_EXIT { spl::Finalize(); }; + + g_is_soc_mariko = spl::GetSocType() == spl::SocType_Mariko; + } + #else + #error "Unknown execution context for ams::sdmmc::impl::IsSocMariko" + #endif + + /* Note that we determined the soc. */ + g_determined_soc = true; + } + + return g_is_soc_mariko; + } + + void SdmmcController::ReleaseReset(SpeedMode speed_mode) { + /* Get the clock reset module. */ + const auto module = this->GetClockResetModule(); + + /* If the module is available, disable clock. */ + if (ClockResetController::IsAvailable(module)) { + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::EnsureControl(); + } + + /* Get the correct divider setting for the speed mode. */ + u32 target_clock_frequency_khz; + u16 x; + GetDividerSetting(std::addressof(target_clock_frequency_khz), std::addressof(x), speed_mode); + + /* Release reset. */ + ClockResetController::ReleaseReset(module, target_clock_frequency_khz); + } + + void SdmmcController::AssertReset() { + return ClockResetController::AssertReset(this->GetClockResetModule()); + } + + Result SdmmcController::StartupCore(BusPower bus_power) { + /* Set schmitt trigger. */ + this->SetSchmittTrigger(bus_power); + + /* Select one-cycle delay version of cmd_oen. */ + reg::ReadWrite(this->sdmmc_registers->io_spare, SD_REG_BITS_ENUM(IO_SPARE_SPARE_OUT_3, ONE_CYCLE_DELAY)); + + /* Select regulated reference voltage for trimmer and DLL supply. */ + reg::ReadWrite(this->sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0)); + + /* Configure outbound tap value. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue())); + + /* Configure SPI_MODE_CLKEN_OVERRIDE. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, NORMAL)); + + /* Set slew codes. */ + this->SetSlewCodes(); + + /* Set vref sel. */ + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetVrefSelValue())); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(bus_power); + this->CalibrateDriveStrength(bus_power); + + /* Enable internal clock. */ + R_TRY(SdHostStandardController::EnableInternalClock()); + + return ResultSuccess(); + } + + Result SdmmcController::SetClockTrimmer(SpeedMode speed_mode, u8 tap_value) { + /* If speed mode is Hs400, set the dqs trim value. */ + if (speed_mode == SpeedMode_MmcHs400) { + reg::ReadWrite(this->sdmmc_registers->vendor_cap_overrides, SD_REG_BITS_VALUE(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 40)); + } + + /* Configure tap value as updated by software. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, NOT_UPDATED_BY_HW)); + + /* Set the inbound tap value. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TAP_VAL, tap_value)); + + /* Reset the cmd/dat line. */ + R_TRY(SdHostStandardController::ResetCmdDatLine()); + + return ResultSuccess(); + } + + u8 SdmmcController::GetCurrentTapValue() { + return static_cast(reg::GetValue(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_MASK(VENDOR_CLOCK_CNTRL_TAP_VAL))); + } + + Result SdmmcController::CalibrateDll() { + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Begin calibration. */ + reg::ReadWrite(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE)); + + /* Wait up to 5ms for calibration to begin. */ + { + ManualTimer timer(5); + while (true) { + /* If calibration is done, we're done. */ + if (!reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllCalibrationSoftwareTimeout()); + } + } + + /* Wait up to 10ms for calibration to complete. */ + { + ManualTimer timer(10); + while (true) { + /* If calibration is done, we're done. */ + if (reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg_sta, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, DONE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllApplicationSoftwareTimeout()); + } + } + + return ResultSuccess(); + } + + Result SdmmcController::SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value) { + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Set clock trimmer. */ + /* NOTE: Nintendo does not re-enable the clock if this fails... */ + R_TRY(this->SetClockTrimmer(speed_mode, tap_value)); + + /* Configure for the desired speed mode. */ + switch (speed_mode) { + case SpeedMode_MmcIdentification: + case SpeedMode_SdCardIdentification: + case SpeedMode_MmcLegacySpeed: + case SpeedMode_SdCardDefaultSpeed: + /* Set as normal speed, 3.3V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHighSpeed: + case SpeedMode_SdCardHighSpeed: + /* Set as high speed, 3.3V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHs200: + /* Set as HS200, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_MmcHs400: + /* Set as HS400, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr12: + /* Set as SDR12, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_SdCardSdr104: + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + /* Set as SDR104, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + SdHostStandardController::EnsureControl(); + + /* Get the divider setting. */ + u32 target_source_clock_frequency_khz; + u16 x; + GetDividerSetting(std::addressof(target_source_clock_frequency_khz), std::addressof(x), speed_mode); + + /* Set the clock frequency. */ + u32 actual_source_clock_frequency_khz; + ClockResetController::SetClockFrequencyKHz(std::addressof(actual_source_clock_frequency_khz), this->GetClockResetModule(), target_source_clock_frequency_khz); + + /* Set the device clock frequency. */ + const u32 actual_device_clock_frequency_khz = util::DivideUp(actual_source_clock_frequency_khz, x); + SdHostStandardController::SetDeviceClockFrequencyKHz(actual_device_clock_frequency_khz); + + /* Check that the divider is correct. */ + AMS_ABORT_UNLESS((x == 1) || util::IsAligned(x, 2)); + + /* Write the divider val to clock control. */ + const u16 n = x / 2; + const u16 upper_n = n >> 8; + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_VALUE(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, n), + SD_REG_BITS_VALUE(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, upper_n)); + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If speed mode is Hs400, calibrate dll. */ + if (speed_mode == SpeedMode_MmcHs400) { + R_TRY(this->CalibrateDll()); + } + + /* Set the current speed mode. */ + this->current_speed_mode = speed_mode; + + return ResultSuccess(); + } + + Result SdmmcController::IssueTuningCommand(u32 command_index) { + /* Check that we're not power saving enable. */ + AMS_ABORT_UNLESS(!SdHostStandardController::IsPowerSavingEnable()); + + /* Wait until command inhibit is done. */ + R_TRY(SdHostStandardController::WaitWhileCommandInhibit(true)); + + /* Set transfer for tuning. */ + SdHostStandardController::SetTransferForTuning(); + + /* If necessary, clear interrupt and enable buffer read ready signal. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->ClearInterrupt(); + + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + } + #endif + + /* Set the buffer read ready enable, and read status to ensure it takes. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, reg::Read(this->sdmmc_registers->sd_host_standard_registers.normal_int_status)); + + /* Issue command with clock disabled. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + { + SdHostStandardController::SetCommandForTuning(command_index); + + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + SdHostStandardController::AbortTransaction(); + } + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* When we're done waiting, ensure that we clean up appropriately. */ + ON_SCOPE_EXIT { + /* Clear the buffer read ready signal, if we should. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + #endif + + /* Clear the buffer read ready enable. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + + /* Wait 8 clocks to ensure configuration takes. */ + SdHostStandardController::EnsureControl(); + WaitClocks(8, SdHostStandardController::GetDeviceClockFrequencyKHz()); + }; + + /* Wait for the command to finish. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + const auto result = SdHostStandardController::WaitInterrupt(TuningCommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, clear the interrupt. */ + reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + this->ClearInterrupt(); + return ResultSuccess(); + } else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) { + SdHostStandardController::AbortTransaction(); + return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); + } else { + return result; + } + } + #else + { + SdHostStandardController::EnsureControl(); + ManualTimer timer(TuningCommandTimeoutMilliSeconds); + while (true) { + /* Check if we received the interrupt. */ + if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED))) { + /* If we did, acknowledge it. */ + reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + return ResultSuccess(); + } + + /* Otherwise, check if we timed out. */ + if (!timer.Update()) { + SdHostStandardController::AbortTransaction(); + return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); + } + } + } + #endif + } + + void SdmmcController::SetDriveCodeOffsets(BusPower bus_power) { + /* Get the offsets. */ + u8 pd, pu; + this->GetAutoCalOffsets(std::addressof(pd), std::addressof(pu), bus_power); + + /* Set the offsets. */ + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd), + SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu)); + + /* Wait for 1ms to ensure that our configuration takes. */ + /* NOTE/HACK: Nintendo does not (need) to do this, but they use SvcSleepThread for waits. */ + /* It's unclear why this wait is necessary, but not doing it causes drive strength calibration to fail if done immediately afterwards. */ + util::WaitMicroSeconds(1000); + } + + void SdmmcController::CalibrateDriveStrength(BusPower bus_power) { + /* Reset drive strength calibration status. */ + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Calibrate with the clock disabled. */ + { + /* Set SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + if (reg::HasValue(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) { + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 1)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + } + + /* Calibrate with SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD set. */ + { + /* Begin autocal. */ + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, ENABLED), + SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, ENABLED)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(2); + + /* Wait up to 10ms for auto cal to complete. */ + ManualTimer timer(10); + while (true) { + /* Check if auto cal is inactive. */ + if (reg::HasValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, INACTIVE))) { + /* Check the pullup status. */ + const u32 pullup = (reg::GetValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F; + if (pullup == 0x1F) { + this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd(); + } + if (pullup == 0) { + this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen(); + } + + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout(); + + this->SetDriveStrengthToDefaultValues(bus_power); + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, DISABLED)); + break; + } + } + } + + /* Clear SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0)); + } + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If calibration didn't receive a replacement error, set internal state to success. */ + if (sdmmc::ResultDriveStrengthCalibrationNotCompleted::Includes(this->drive_strength_calibration_status)) { + this->drive_strength_calibration_status = ResultSuccess(); + } + } + + Result SdmmcController::Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) { + /* Verify that we're awake. */ + AMS_ABORT_UNLESS(this->is_awake); + + /* Release the controller from reset. */ + this->ReleaseReset(speed_mode); + + /* Mark that we're not shutdown. */ + this->is_shutdown = false; + + /* Power on the controller. */ + R_TRY(this->PowerOn(bus_power)); + + /* Start up for the specific power. */ + R_TRY(this->StartupCore(bus_power)); + + /* Set our current power/width/speed. */ + SdHostStandardController::SetBusWidth(bus_width); + SdHostStandardController::SetBusPower(bus_power); + R_TRY(this->SetSpeedMode(speed_mode)); + this->SetPowerSaving(power_saving_enable); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + + /* Ensure that we can control the device. */ + SdHostStandardController::EnsureControl(); + + return ResultSuccess(); + } + + void SdmmcController::Shutdown() { + /* If we're already shut down, there's nothing to do. */ + if (this->is_shutdown) { + return; + } + + /* If we're currently awake, we need to disable clock/power. */ + if (this->is_awake) { + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + } + + /* Power off. */ + this->PowerOff(); + + /* If awake, assert reset. */ + if (this->is_awake) { + this->AssertReset(); + } + + /* Mark that we're shutdown. */ + this->is_shutdown = true; + } + + void SdmmcController::PutToSleep() { + /* If we're already shut down or asleep, there's nothing to do. */ + if (this->is_shutdown || !this->is_awake) { + return; + } + + /* Save values before sleep. */ + this->bus_power_before_sleep = SdHostStandardController::GetBusPower(); + this->bus_width_before_sleep = SdHostStandardController::GetBusWidth(); + this->speed_mode_before_sleep = this->current_speed_mode; + this->tap_value_before_sleep = this->GetCurrentTapValue(); + this->is_powersaving_enable_before_sleep = SdHostStandardController::IsPowerSavingEnable(); + + /* Disable clock/power to the device. */ + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + + /* Assert reset. */ + this->AssertReset(); + + /* Mark that we're asleep. */ + this->is_awake = false; + } + + Result SdmmcController::Awaken() { + /* If we're shut down, or if we're awake already, there's nothing to do. */ + R_SUCCEED_IF(this->is_shutdown); + R_SUCCEED_IF(this->is_awake); + + /* Mark that we're awake. */ + this->is_awake = true; + + /* Clear pad parked status. */ + this->ClearPadParked(); + + /* Release reset. */ + this->ReleaseReset(this->speed_mode_before_sleep); + + /* Start up for the correct power. */ + R_TRY(this->StartupCore(this->bus_power_before_sleep)); + + /* Configure values to what they were before sleep. */ + SdHostStandardController::SetBusWidth(this->bus_width_before_sleep); + SdHostStandardController::SetBusPower(this->bus_power_before_sleep); + R_TRY(this->SetSpeedModeWithTapValue(this->speed_mode_before_sleep, this->tap_value_before_sleep)); + this->SetPowerSaving(this->is_powersaving_enable_before_sleep); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + SdHostStandardController::EnsureControl(); + + return ResultSuccess(); + } + + Result SdmmcController::SwitchToSdr12() { + /* Disable clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Check that the dat lines are all low. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch()); + + /* Set Speed Mode. */ + R_TRY(this->SetSpeedMode(SpeedMode_SdCardSdr12)); + + /* Set voltage to 1.8V. */ + SdHostStandardController::EnsureControl(); + R_TRY(this->LowerBusPower()); + this->SetSchmittTrigger(BusPower_1_8V); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(BusPower_1_8V); + this->CalibrateDriveStrength(BusPower_1_8V); + + /* Set the bus power in standard controller. */ + SdHostStandardController::SetBusPower(BusPower_1_8V); + + /* Wait up to 5ms for the switch to take. */ + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(5000); + + /* Check that we switched to 1.8V. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)), sdmmc::ResultSdHostStandardFailSwitchTo1_8V()); + + /* Enable clock, and wait 1ms. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1000); + + /* Check that the dat lines are all high. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch()); + + return ResultSuccess(); + } + + Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) { + /* Get the tap value. */ + u8 tap_value; + if (speed_mode == SpeedMode_MmcHs400) { + AMS_ABORT_UNLESS(this->is_valid_tap_value_for_hs_400); + tap_value = this->tap_value_for_hs_400; + } else { + tap_value = this->GetDefaultInboundTapValue(); + } + + /* Set the speed mode. */ + R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value)); + + return ResultSuccess(); + } + + void SdmmcController::SetPowerSaving(bool en) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !en && SdHostStandardController::IsDeviceClockEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::SetPowerSaving(en); + } + + void SdmmcController::EnableDeviceClock() { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::EnableDeviceClock(); + } + + Result SdmmcController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::IssueCommand(command, xfer_data, out_num_transferred_blocks); + } + + Result SdmmcController::IssueStopTransmissionCommand(u32 *out_response) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::IssueStopTransmissionCommand(out_response); + } + + Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) { + /* Clear vendor tuning control 1. */ + reg::Write(this->sdmmc_registers->vendor_tuning_cntrl1, 0); + + /* Determine/configure the number of tries. */ + int num_tries; + switch (speed_mode) { + case SpeedMode_MmcHs200: + case SpeedMode_MmcHs400: + case SpeedMode_SdCardSdr104: + num_tries = 128; + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_128)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + num_tries = 256; + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_256)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure the multiplier. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_VALUE(VENDOR_TUNING_CNTRL0_MUL_M, 1)); + + /* Configure tap value to be updated by hardware. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, UPDATED_BY_HW)); + + /* Configure to execute tuning. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING)); + + /* Perform tuning num_tries times. */ + for (int i = 0; /* ... */; ++i) { + /* Check if we've been removed. */ + R_TRY(this->CheckRemoved()); + + /* Issue the command. */ + this->IssueTuningCommand(command_index); + + /* Check if tuning is done. */ + if (i >= num_tries) { + break; + } + + if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, TUNING_COMPLETED))) { + break; + } + } + + /* Check if we're using the tuned clock. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed()); + + return ResultSuccess(); + } + + void SdmmcController::SaveTuningStatusForHs400() { + /* Save the current tap value. */ + this->tap_value_for_hs_400 = GetCurrentTapValue(); + this->is_valid_tap_value_for_hs_400 = true; + } + + Result Sdmmc1Controller::PowerOnForRegisterControl(BusPower bus_power) { + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; }; + + /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ + R_TRY(this->power_controller->PowerOn(BusPower_3_3V)); + + return ResultSuccess(); + } + + void Sdmmc1Controller::PowerOffForRegisterControl() { + /* If we're already off, there's nothing to do. */ + if (this->current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + if (this->current_bus_power == BusPower_3_3V) { + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + this->power_controller->LowerBusPower(); + + /* Set our bus power. */ + this->current_bus_power = BusPower_1_8V; + } + + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh); + + + /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + this->power_controller->PowerOff(); + + /* Set our bus power. */ + this->current_bus_power = BusPower_Off; + + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1ResetState); + } + + Result Sdmmc1Controller::LowerBusPowerForRegisterControl() { + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; }; + + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + R_TRY(this->power_controller->LowerBusPower()); + + return ResultSuccess(); + } + + void Sdmmc1Controller::SetSchmittTriggerForRegisterControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); + } else { + switch (bus_power) { + case BusPower_1_8V: + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); + break; + case BusPower_3_3V: + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable); + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + Result Sdmmc1Controller::PowerOnForPcvControl(BusPower bus_power) { + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; }; + + /* TODO: return pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ + return ResultSuccess(); + } + + void Sdmmc1Controller::PowerOffForPcvControl() { + /* If we're already off, there's nothing to do. */ + if (this->current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + { + /* TODO: pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + this->current_bus_power = BusPower_1_8V; + } + + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + + /* TODO: pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + this->current_bus_power = BusPower_Off; + + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ + + } + + Result Sdmmc1Controller::LowerBusPowerForPcvControl() { + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; }; + + /* TODO: return pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + return ResultSuccess(); + } + + void Sdmmc1Controller::SetSchmittTriggerForPcvControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + } else { + switch (bus_power) { + case BusPower_1_8V: + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + break; + case BusPower_3_3V: + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */ + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + #endif + + Result Sdmmc1Controller::PowerOn(BusPower bus_power) { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->PowerOnForPcvControl(bus_power); + } else + #endif + { + return this->PowerOnForRegisterControl(bus_power); + } + } + + void Sdmmc1Controller::PowerOff() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->PowerOffForPcvControl(); + } else + #endif + { + return this->PowerOffForRegisterControl(); + } + } + + Result Sdmmc1Controller::LowerBusPower() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->LowerBusPowerForPcvControl(); + } else + #endif + { + return this->LowerBusPowerForRegisterControl(); + } + } + + void Sdmmc1Controller::SetSchmittTrigger(BusPower bus_power) { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->SetSchmittTriggerForPcvControl(bus_power); + } else + #endif + { + return this->SetSchmittTriggerForRegisterControl(bus_power); + } + } + + void Sdmmc1Controller::Initialize() { + return this->InitializeForRegisterControl(); + } + + void Sdmmc1Controller::Finalize() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->FinalizeForPcvControl(); + } else + #endif + { + return this->FinalizeForRegisterControl(); + } + } + + void Sdmmc1Controller::InitializeForRegisterControl() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* Mark ourselves as initialized by register control. */ + this->is_pcv_control = false; + #endif + + /* pinmux::Initialize(); */ + /* This just opens a session handle to pinmux service, no work to do. */ + + /* pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ + /* This just sets the session's internal value to the pin group name, so nothing to do here either. */ + + /* pcv::Initialize(); */ + /* This initializes a lot of globals in pcv, most of which we don't care about. */ + /* However, we do care about the Sdmmc1PowerController. */ + AMS_ABORT_UNLESS(this->power_controller == nullptr); + this->power_controller = new (GetPointer(this->power_controller_storage)) PowerController; + + /* Perform base initialization. */ + SdmmcController::Initialize(); + } + + void Sdmmc1Controller::FinalizeForRegisterControl() { + /* Perform base finalization. */ + SdmmcController::Finalize(); + + /* pcv::Finalize(); */ + /* As with initialize, we mostly don't care about the globals this touches. */ + /* However, we do want to finalize the Sdmmc1PowerController. */ + AMS_ABORT_UNLESS(this->power_controller != nullptr); + this->power_controller->~PowerController(); + this->power_controller = nullptr; + + /* pinmux::CloseSession(std::addressof(this->pinmux_session)); */ + /* This does nothing. */ + + /* pinmux::Finalize(); */ + /* This does nothing. */ + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* Mark ourselves as initialized by register control. */ + this->is_pcv_control = false; + #endif + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void Sdmmc1Controller::InitializeForPcvControl() { + /* Mark ourselves as initialized by pcv control. */ + this->is_pcv_control = true; + + /* TODO: pinmux::Initialize(); */ + /* TODO: pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */ + /* TODO: pcv::Initialize(); */ + + /* Perform base initialization. */ + SdmmcController::Initialize(); + } + + void Sdmmc1Controller::FinalizeForPcvControl() { + /* Perform base finalization. */ + SdmmcController::Finalize(); + + /* TODO: pcv::Finalize(); */ + /* TODO: pinmux::CloseSession(std::addressof(this->pinmux_session)); */ + /* TODO: pinmux::Finalize(); */ + + /* Mark ourselves as initialized by register control. */ + this->is_pcv_control = false; + } + #endif + + namespace { + + constexpr inline dd::PhysicalAddress PmcRegistersPhysicalAddress = 0x7000E400; + + } + + Result Sdmmc1Controller::PowerController::ControlVddioSdmmc1(BusPower bus_power) { + /* Configure appropriate voltage. */ + switch (bus_power) { + case BusPower_Off: + R_TRY(SetSdCardVoltageEnabled(false)); + break; + case BusPower_1_8V: + R_TRY(SetSdCardVoltageValue(1'800'000)); + R_TRY(SetSdCardVoltageEnabled(true)); + break; + case BusPower_3_3V: + R_TRY(SetSdCardVoltageValue(3'300'000)); + R_TRY(SetSdCardVoltageEnabled(true)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + return ResultSuccess(); + } + + void Sdmmc1Controller::PowerController::SetSdmmcIoMode(bool is_3_3V) { + /* Determine the address we're updating. */ + constexpr dd::PhysicalAddress ApbdevPmcPwrDetValAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_PWR_DET_VAL; + + /* Read the current value. */ + u32 value = dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); + + /* Mask out the existing bits. */ + value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(PWR_DET_VAL_SDMMC1))); + + /* ORR in the new bits. */ + value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(PWR_DET_VAL_SDMMC1, is_3_3V, ENABLE, DISABLE)); + + /* Write the new value. */ + dd::WriteIoRegister(ApbdevPmcPwrDetValAddress, value); + + /* Read the value back to be sure our write takes. */ + dd::ReadIoRegister(ApbdevPmcPwrDetValAddress); + } + + void Sdmmc1Controller::PowerController::ControlRailSdmmc1Io(bool is_power_on) { + /* Determine the address we're updating. */ + constexpr dd::PhysicalAddress ApbdevPmcNoIoPowerAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_NO_IOPOWER; + + /* Read the current value. */ + u32 value = dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); + + /* Mask out the existing bits. */ + value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(NO_IOPOWER_SDMMC1))); + + /* ORR in the new bits. */ + value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(NO_IOPOWER_SDMMC1, is_power_on, DISABLE, ENABLE)); + + /* Write the new value. */ + dd::WriteIoRegister(ApbdevPmcNoIoPowerAddress, value); + + /* Read the value back to be sure our write takes. */ + dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress); + } + + Sdmmc1Controller::PowerController::PowerController() : current_bus_power(BusPower_Off) { + /* gpio::Initialize(); */ + /* ... */ + + /* Open gpio session. */ + /* gpio::OpenSession(std::addressof(this->gpio_pad_session), gpio::GpioPadName_PowSdEn); */ + gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn); + + /* Configure the gpio as low/output. */ + /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); + + /* gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Output); */ + gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output); + } + + Sdmmc1Controller::PowerController::~PowerController() { + /* gpio::CloseSession(std::addressof(this->gpio_pad_session)); */ + gpio_impl::CloseSession(gpio_impl::GpioPadName_PowSdEn); + + /* gpio::Finalize(); */ + /* ... */ + } + + Result Sdmmc1Controller::PowerController::PowerOn(BusPower bus_power) { + /* Bus power should be off, and if it's not we don't need to do anything. */ + AMS_ASSERT(this->current_bus_power == BusPower_Off); + R_SUCCEED_IF(this->current_bus_power != BusPower_Off); + + /* Power on requires the target bus power be 3.3V. */ + AMS_ABORT_UNLESS(bus_power == BusPower_3_3V); + + /* Enable the rail. */ + this->ControlRailSdmmc1Io(true); + + /* Set the SD power GPIO to high. */ + /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_High); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_High); + + /* Wait 10ms for power change to take. */ + WaitMicroSeconds(10000); + + /* Configure Sdmmc1 IO as 3.3V. */ + this->SetSdmmcIoMode(true); + R_TRY(this->ControlVddioSdmmc1(BusPower_3_3V)); + + /* Wait 130 us for changes to take. */ + WaitMicroSeconds(130); + + /* Update our current bus power. */ + this->current_bus_power = bus_power; + + return ResultSuccess(); + } + + Result Sdmmc1Controller::PowerController::PowerOff() { + /* Bus power should be on, and if it's not we don't need to do anything. */ + AMS_ASSERT(this->current_bus_power != BusPower_Off); + R_SUCCEED_IF(this->current_bus_power == BusPower_Off); + + /* Bus power should be 1.8V. */ + /* NOTE: the result returned here is 0x8C0 (regulator::ResultIllegalRequest()) on newer firmwares. */ + AMS_ASSERT(this->current_bus_power == BusPower_1_8V); + R_UNLESS(this->current_bus_power == BusPower_1_8V, pcv::ResultIllegalRequest()); + + /* Disable vddio, and wait 4 ms. */ + this->ControlVddioSdmmc1(BusPower_Off); + WaitMicroSeconds(4000); + + /* Set the SD power GPIO to low. */ + /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */ + gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low); + + /* Wait 239ms for the gpio config to take. */ + WaitMicroSeconds(239000); + + /* Disable the rail. */ + this->ControlRailSdmmc1Io(false); + this->SetSdmmcIoMode(true); + + /* Update our current bus power. */ + this->current_bus_power = BusPower_Off; + + return ResultSuccess(); + } + + Result Sdmmc1Controller::PowerController::LowerBusPower() { + /* Bus power should be 3.3V, and if it's not we don't need to do anything. */ + AMS_ASSERT(this->current_bus_power == BusPower_3_3V); + R_SUCCEED_IF(this->current_bus_power != BusPower_3_3V); + + /* Configure as 1.8V, then wait 150us for it to take. */ + R_TRY(this->ControlVddioSdmmc1(BusPower_1_8V)); + WaitMicroSeconds(150); + this->SetSdmmcIoMode(false); + + /* Update our current bus power. */ + this->current_bus_power = BusPower_1_8V; + + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp new file mode 100644 index 0000000..d007d9d --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2018-2020 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 "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl { + + bool IsSocMariko(); + + constexpr inline size_t SdmmcRegistersSize = 0x200; + + constexpr inline dd::PhysicalAddress ApbMiscRegistersPhysicalAddress = UINT64_C(0x70000000); + constexpr inline size_t ApbMiscRegistersSize = 16_KB; + + class SdmmcController : public SdHostStandardController { + private: + struct SdmmcRegisters { + /* Standard registers. */ + volatile SdHostStandardRegisters sd_host_standard_registers; + + /* Vendor specific registers */ + volatile uint32_t vendor_clock_cntrl; + volatile uint32_t vendor_sys_sw_cntrl; + volatile uint32_t vendor_err_intr_status; + volatile uint32_t vendor_cap_overrides; + volatile uint32_t vendor_boot_cntrl; + volatile uint32_t vendor_boot_ack_timeout; + volatile uint32_t vendor_boot_dat_timeout; + volatile uint32_t vendor_debounce_count; + volatile uint32_t vendor_misc_cntrl; + volatile uint32_t max_current_override; + volatile uint32_t max_current_override_hi; + volatile uint32_t _0x12c[0x20]; + volatile uint32_t vendor_io_trim_cntrl; + + /* Start of sdmmc2/sdmmc4 only */ + volatile uint32_t vendor_dllcal_cfg; + volatile uint32_t vendor_dll_ctrl0; + volatile uint32_t vendor_dll_ctrl1; + volatile uint32_t vendor_dllcal_cfg_sta; + /* End of sdmmc2/sdmmc4 only */ + + volatile uint32_t vendor_tuning_cntrl0; + volatile uint32_t vendor_tuning_cntrl1; + volatile uint32_t vendor_tuning_status0; + volatile uint32_t vendor_tuning_status1; + volatile uint32_t vendor_clk_gate_hysteresis_count; + volatile uint32_t vendor_preset_val0; + volatile uint32_t vendor_preset_val1; + volatile uint32_t vendor_preset_val2; + volatile uint32_t sdmemcomppadctrl; + volatile uint32_t auto_cal_config; + volatile uint32_t auto_cal_interval; + volatile uint32_t auto_cal_status; + volatile uint32_t io_spare; + volatile uint32_t sdmmca_mccif_fifoctrl; + volatile uint32_t timeout_wcoal_sdmmca; + volatile uint32_t _0x1fc; + }; + static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize); + private: + SdmmcRegisters *sdmmc_registers; + bool is_shutdown; + bool is_awake; + SpeedMode current_speed_mode; + BusPower bus_power_before_sleep; + BusWidth bus_width_before_sleep; + SpeedMode speed_mode_before_sleep; + u8 tap_value_before_sleep; + bool is_powersaving_enable_before_sleep; + u8 tap_value_for_hs_400; + bool is_valid_tap_value_for_hs_400; + Result drive_strength_calibration_status; + private: + void ReleaseReset(SpeedMode speed_mode); + void AssertReset(); + Result StartupCore(BusPower bus_power); + Result SetClockTrimmer(SpeedMode speed_mode, u8 tap_value); + u8 GetCurrentTapValue(); + Result CalibrateDll(); + Result SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value); + Result IssueTuningCommand(u32 command_index); + protected: + void SetDriveCodeOffsets(BusPower bus_power); + void CalibrateDriveStrength(BusPower bus_power); + + virtual void SetPad() = 0; + + virtual ClockResetController::Module GetClockResetModule() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const = 0; + virtual os::InterruptEventType *GetInterruptEvent() const = 0; + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() = 0; + virtual void ClearPadParked() = 0; + virtual Result PowerOn(BusPower bus_power) = 0; + virtual void PowerOff() = 0; + virtual Result LowerBusPower() = 0; + virtual void SetSchmittTrigger(BusPower bus_power) = 0; + virtual u8 GetOutboundTapValue() const = 0; + virtual u8 GetDefaultInboundTapValue() const = 0; + virtual u8 GetVrefSelValue() const = 0; + virtual void SetSlewCodes() = 0; + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const = 0; + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) = 0; + public: + explicit SdmmcController(dd::PhysicalAddress registers_phys_addr) : SdHostStandardController(registers_phys_addr, SdmmcRegistersSize) { + /* Set sdmmc registers. */ + static_assert(offsetof(SdmmcRegisters, sd_host_standard_registers) == 0); + this->sdmmc_registers = reinterpret_cast(this->registers); + + this->is_shutdown = true; + this->is_awake = true; + this->is_valid_tap_value_for_hs_400 = false; + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + this->tap_value_for_hs_400 = 0; + this->current_speed_mode = SpeedMode_MmcIdentification; + this->bus_power_before_sleep = BusPower_Off; + this->bus_width_before_sleep = BusWidth_1Bit; + this->speed_mode_before_sleep = SpeedMode_MmcIdentification; + this->tap_value_before_sleep = 0; + this->is_powersaving_enable_before_sleep = false; + } + + virtual void Initialize() override { + /* Set pad. */ + this->SetPad(); + + /* Initialize our clock/reset module. */ + ClockResetController::Initialize(this->GetClockResetModule()); + + /* If necessary, initialize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InterruptEventType *interrupt_event = this->GetInterruptEvent(); + os::InitializeInterruptEvent(interrupt_event, this->GetInterruptNumber(), os::EventClearMode_ManualClear); + SdHostStandardController::PreSetInterruptEvent(interrupt_event); + } + #endif + + /* Perform base initialization. */ + SdHostStandardController::Initialize(); + } + + virtual void Finalize() override { + /* Perform base finalization. */ + SdHostStandardController::Finalize(); + + /* If necessary, finalize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::FinalizeInterruptEvent(this->GetInterruptEvent()); + } + #endif + + /* Finalize our clock/reset module. */ + ClockResetController::Finalize(this->GetClockResetModule()); + } + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) override; + virtual void Shutdown() override; + virtual void PutToSleep() override; + virtual Result Awaken() override; + virtual Result SwitchToSdr12() override; + virtual Result SetSpeedMode(SpeedMode speed_mode) override; + + virtual SpeedMode GetSpeedMode() const override { + return this->current_speed_mode; + } + + virtual void SetPowerSaving(bool en) override; + virtual void EnableDeviceClock() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual bool IsSupportedTuning() const override { + return true; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) override; + virtual void SaveTuningStatusForHs400() override; + + virtual Result GetInternalStatus() const override { + return this->drive_strength_calibration_status; + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc1RegistersPhysicalAddress = UINT64_C(0x700B0000); + + class Sdmmc1Controller : public SdmmcController { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + + /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */ + class PowerController { + NON_COPYABLE(PowerController); + NON_MOVEABLE(PowerController); + private: + BusPower current_bus_power; + private: + Result ControlVddioSdmmc1(BusPower bus_power); + void SetSdmmcIoMode(bool is_3_3V); + void ControlRailSdmmc1Io(bool is_power_on); + public: + PowerController(); + ~PowerController(); + + Result PowerOn(BusPower bus_power); + Result PowerOff(); + Result LowerBusPower(); + }; + private: + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* TODO: pinmux::PinmuxSession pinmux_session; */ + #endif + BusPower current_bus_power; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + bool is_pcv_control; + #endif + TYPED_STORAGE(PowerController) power_controller_storage; + PowerController *power_controller; + private: + Result PowerOnForRegisterControl(BusPower bus_power); + void PowerOffForRegisterControl(); + Result LowerBusPowerForRegisterControl(); + void SetSchmittTriggerForRegisterControl(BusPower bus_power); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + Result PowerOnForPcvControl(BusPower bus_power); + void PowerOffForPcvControl(); + Result LowerBusPowerForPcvControl(); + void SetSchmittTriggerForPcvControl(BusPower bus_power); + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc1; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 46; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return !IsSocMariko(); + } + + virtual void ClearPadParked() override { + /* Nothing is needed here. */ + } + + virtual Result PowerOn(BusPower bus_power) override; + virtual void PowerOff() override; + virtual Result LowerBusPower() override; + + virtual void SetSchmittTrigger(BusPower bus_power) override; + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xE; + } else { + return 0x2; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x4; + } + } + + virtual u8 GetVrefSelValue() const override { + if (IsSocMariko()) { + return 0x0; + } else { + return 0x7; + } + } + + virtual void SetSlewCodes() override { + if (IsSocMariko()) { + /* Do nothing. */ + } else { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Write the slew values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 0x1), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 0x1)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); + } + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Set the offsets. */ + if (IsSocMariko()) { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 6; + *out_auto_cal_pu_offset = 6; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 0x7B; + *out_auto_cal_pu_offset = 0x7B; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0x7D; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drive code values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0x8; + drvup = 0x8; + } else { + switch (bus_power) { + case BusPower_1_8V: + drvdn = 0xF; + drvup = 0xB; + break; + case BusPower_3_3V: + drvdn = 0xC; + drvup = 0xC; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Write the drv up/down values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, drvdn), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); + } + public: + Sdmmc1Controller() : SdmmcController(Sdmmc1RegistersPhysicalAddress) { + this->current_bus_power = BusPower_Off; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + this->is_pcv_control = false; + #endif + this->power_controller = nullptr; + } + + virtual void Initialize() override; + virtual void Finalize() override; + + void InitializeForRegisterControl(); + void FinalizeForRegisterControl(); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void InitializeForPcvControl(); + void FinalizeForPcvControl(); + #endif + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + class Sdmmc2And4Controller : public SdmmcController { + protected: + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return false; + } + + virtual Result PowerOn(BusPower bus_power) override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + AMS_UNUSED(bus_power); + return ResultSuccess(); + } + + virtual void PowerOff() override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + } + + virtual Result LowerBusPower() override { + AMS_ABORT("Sdmmc2And4Controller cannot lower bus power\n"); + } + + virtual void SetSchmittTrigger(BusPower bus_power) override { + /* Do nothing. */ + AMS_UNUSED(bus_power); + } + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xD; + } else { + return 0x8; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x0; + } + } + + virtual u8 GetVrefSelValue() const override { + return 0x7; + } + + virtual void SetSlewCodes() override { + /* Do nothing. */ + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Sdmmc2And4Controller only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Set the offsets. */ + *out_auto_cal_pd_offset = 5; + *out_auto_cal_pu_offset = 5; + } + public: + explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) { + /* ... */ + } + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc2RegistersPhysicalAddress = UINT64_C(0x700B0200); + + class Sdmmc2Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc2; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 47; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + if (IsSocMariko()) { + /* Nothing is needed here. */ + } else { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC2_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC2 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + if (IsSocMariko()) { + /* Write the drv up/down values to APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 0xA), + APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 0xA)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL); + } else { + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 0x10), + APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 0x10)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + public: + Sdmmc2Controller() : Sdmmc2And4Controller(Sdmmc2RegistersPhysicalAddress) { + /* ... */ + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600); + + class Sdmmc4Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + if (IsSocMariko()) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Enable Schmitt Trigger in emmc4 iobrick. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, ENABLE)); + + /* Clear CMD_PULLU, CLK_PULLD, DQS_PULLD. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 0)); + + /* Read again to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL); + } else { + /* On Erista, we can just leave the reset value intact. */ + } + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc4; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 63; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC4_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC4 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drv up/down values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0xA; + drvup = 0xA; + } else { + drvdn = 0x10; + drvup = 0x10; + } + + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, drvdn), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + public: + Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) { + /* ... */ + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp new file mode 100644 index 0000000..9d3b943 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 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 + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" + + namespace ams::sdmmc::impl { + + using SdmmcControllerForPortSdCard0 = Sdmmc1Controller; + using SdmmcControllerForPortGcAsic0 = Sdmmc2Controller; + using SdmmcControllerForPortMmc0 = Sdmmc4Controller; + + } + +#else + #error "Unknown board for ams::sdmmc::SdmmcController" +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp new file mode 100644 index 0000000..ae90332 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + namespace { + + #if defined(AMS_SDMMC_USE_OS_TIMER) + void SpinWaitMicroSeconds(u32 us) { + const os::Tick timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMicroSeconds(us)) + os::Tick(1); + while (true) { + if (os::GetSystemTick() > timeout_tick) { + break; + } + } + } + + ALWAYS_INLINE void DataSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("dsb" ::: "memory"); + #else + #error "Unknown architecture for DataSynchronizationBarrier" + #endif + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("isb" ::: "memory"); + #else + #error "Unknown architecture for InstructionSynchronizationBarrier" + #endif + } + #endif + + } + + void WaitMicroSeconds(u32 us) { + #if defined(AMS_SDMMC_USE_OS_TIMER) + /* Ensure that nothing is reordered before we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + + /* If the time is small, spinloop, otherwise pend ourselves. */ + if (us < 100) { + SpinWaitMicroSeconds(us); + } else { + os::SleepThread(TimeSpan::FromMicroSeconds(us)); + } + + /* Ensure that nothing is reordered after we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + util::WaitMicroSeconds(us); + #else + #error "Unknown context for ams::sdmmc::impl::WaitMicroSeconds" + #endif + } + + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz) { + AMS_ABORT_UNLESS(clock_frequency_khz > 0); + WaitMicroSeconds(util::DivideUp(1000 * num_clocks, clock_frequency_khz)); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp new file mode 100644 index 0000000..5d81495 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020 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 + +namespace ams::sdmmc::impl { + + void WaitMicroSeconds(u32 us); + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz); + + #if defined(AMS_SDMMC_USE_OS_TIMER) + class ManualTimer { + private: + os::Tick timeout_tick; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(ms)); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = os::GetSystemTick() > this->timeout_tick; + return true; + } + }; + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + class ManualTimer { + private: + u32 timeout_us; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_us = util::GetMicroSeconds() + (ms * 1000); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = util::GetMicroSeconds() > this->timeout_us; + return true; + } + }; + #else + #error "Unknown context for ams::sdmmc::ManualTimer" + #endif + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_common.cpp b/libraries/libvapours/source/sdmmc/sdmmc_common.cpp new file mode 100644 index 0000000..598e048 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_common.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_i_host_controller.hpp" +#include "impl/sdmmc_i_device_accessor.hpp" +#include "impl/sdmmc_clock_reset_controller.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::IHostController *GetHostController(Port port) { + /* Get the controller. */ + impl::IHostController *host_controller = nullptr; + switch (port) { + case Port_Mmc0: host_controller = impl::GetHostControllerOfPortMmc0(); break; + case Port_SdCard0: host_controller = impl::GetHostControllerOfPortSdCard0(); break; + case Port_GcAsic0: host_controller = impl::GetHostControllerOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(host_controller != nullptr); + return host_controller; + } + + impl::IDeviceAccessor *GetDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::IDeviceAccessor *device_accessor = nullptr; + switch (port) { + case Port_Mmc0: device_accessor = impl::GetDeviceAccessorOfPortMmc0(); break; + case Port_SdCard0: device_accessor = impl::GetDeviceAccessorOfPortSdCard0(); break; + case Port_GcAsic0: device_accessor = impl::GetDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(device_accessor != nullptr); + return device_accessor; + } + + } + + + + void Initialize(Port port) { + return GetDeviceAccessor(port)->Initialize(); + } + + void Finalize(Port port) { + return GetDeviceAccessor(port)->Finalize(); + } + +#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvClockResetControl() { + return impl::ClockResetController::SwitchToPcvControl(); + } +#endif + +#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } +#endif + + void ChangeCheckTransferInterval(Port port, u32 ms) { + return GetHostController(port)->ChangeCheckTransferInterval(ms); + } + void SetDefaultCheckTransferInterval(Port port) { + return GetHostController(port)->SetDefaultCheckTransferInterval(); + } + + Result Activate(Port port) { + return GetDeviceAccessor(port)->Activate(); + } + + void Deactivate(Port port) { + return GetDeviceAccessor(port)->Deactivate(); + } + + Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors) { + return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, dst, dst_size, true); + } + + Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size) { + return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, const_cast(src), src_size, false); + } + + Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + + Result GetDeviceSpeedMode(SpeedMode *out, Port port) { + return GetDeviceAccessor(port)->GetSpeedMode(out); + } + + Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port) { + return GetDeviceAccessor(port)->GetMemoryCapacity(out_num_sectors); + } + + Result GetDeviceStatus(u32 *out_device_status, Port port) { + return GetDeviceAccessor(port)->GetDeviceStatus(out_device_status); + } + + Result GetDeviceCid(void *out, size_t out_size, Port port) { + return GetDeviceAccessor(port)->GetCid(out, out_size); + } + + Result GetDeviceCsd(void *out, size_t out_size, Port port) { + return GetDeviceAccessor(port)->GetCsd(out, out_size); + } + + void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port) { + return GetDeviceAccessor(port)->GetAndClearErrorInfo(out_error_info, out_log_size, out_log_buffer, log_buffer_size); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp b/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp new file mode 100644 index 0000000..2071fd3 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_gc_asic_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::GcAsicDeviceAccessor *GetGcAsicDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::GcAsicDeviceAccessor *gc_asic_device_accessor = nullptr; + switch (port) { + case Port_GcAsic0: gc_asic_device_accessor = impl::GetGcAsicDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(gc_asic_device_accessor != nullptr); + return gc_asic_device_accessor; + } + + } + + void PutGcAsicToSleep(Port port) { + return GetGcAsicDeviceAccessor(port)->PutGcAsicToSleep(); + } + + Result AwakenGcAsic(Port port) { + return GetGcAsicDeviceAccessor(port)->AwakenGcAsic(); + } + + Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size) { + return GetGcAsicDeviceAccessor(port)->WriteGcAsicOperation(op_buf, op_buf_size); + } + + Result FinishGcAsicOperation(Port port) { + return GetGcAsicDeviceAccessor(port)->FinishGcAsicOperation(); + } + + Result AbortGcAsicOperation(Port port) { + return GetGcAsicDeviceAccessor(port)->AbortGcAsicOperation(); + } + + Result SleepGcAsic(Port port) { + return GetGcAsicDeviceAccessor(port)->SleepGcAsic(); + } + + Result UpdateGcAsicKey(Port port) { + return GetGcAsicDeviceAccessor(port)->UpdateGcAsicKey(); + } + + void SignalGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->SignalGcRemovedEvent(); + } + + void ClearGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->ClearGcRemovedEvent(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp b/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp new file mode 100644 index 0000000..76fc571 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_mmc_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::MmcDeviceAccessor *GetMmcDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::MmcDeviceAccessor *mmc_device_accessor = nullptr; + switch (port) { + case Port_Mmc0: mmc_device_accessor = impl::GetMmcDeviceAccessorOfPortMmc0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(mmc_device_accessor != nullptr); + return mmc_device_accessor; + } + + } + + void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetMmcDeviceAccessor(port)->SetMmcWorkBuffer(buffer, buffer_size); + } + + void PutMmcToSleep(Port port) { + return GetMmcDeviceAccessor(port)->PutMmcToSleep(); + } + + void AwakenMmc(Port port) { + return GetMmcDeviceAccessor(port)->AwakenMmc(); + } + + Result SelectMmcPartition(Port port, MmcPartition mmc_partition) { + return GetMmcDeviceAccessor(port)->SelectMmcPartition(mmc_partition); + } + + Result EraseMmc(Port port) { + return GetMmcDeviceAccessor(port)->EraseMmc(); + } + + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port) { + return GetMmcDeviceAccessor(port)->GetMmcBootPartitionCapacity(out_num_sectors); + } + + Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port) { + return GetMmcDeviceAccessor(port)->GetMmcExtendedCsd(out_buffer, buffer_size); + } + + Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetMmcDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp b/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp new file mode 100644 index 0000000..c10a38d --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2020 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_sd_card_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::SdCardDeviceAccessor *GetSdCardDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::SdCardDeviceAccessor *sd_card_device_accessor = nullptr; + switch (port) { + case Port_SdCard0: sd_card_device_accessor = impl::GetSdCardDeviceAccessorOfPortSdCard0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(sd_card_device_accessor != nullptr); + return sd_card_device_accessor; + } + + } + + void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetSdCardDeviceAccessor(port)->SetSdCardWorkBuffer(buffer, buffer_size); + } + + void PutSdCardToSleep(Port port) { + return GetSdCardDeviceAccessor(port)->PutSdCardToSleep(); + } + + void AwakenSdCard(Port port) { + return GetSdCardDeviceAccessor(port)->AwakenSdCard(); + } + + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardProtectedAreaCapacity(out_num_sectors); + } + + Result GetSdCardScr(void *dst, size_t dst_size, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardScr(dst, dst_size); + } + + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function) { + return GetSdCardDeviceAccessor(port)->GetSdCardSwitchFunctionStatus(dst, dst_size, switch_function); + } + + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode) { + return GetSdCardDeviceAccessor(port)->GetSdCardCurrentConsumption(out_current_consumption, speed_mode); + } + + Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardSdStatus(dst, dst_size); + } + + Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetSdCardDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + + bool IsSdCardInserted(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardInserted(); + } + + bool IsSdCardRemoved(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardRemoved(); + } + + + void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg) { + return GetSdCardDeviceAccessor(port)->RegisterSdCardDetectionEventCallback(callback, arg); + } + + void UnregisterSdCardDetectionEventCallback(Port port) { + return GetSdCardDeviceAccessor(port)->UnregisterSdCardDetectionEventCallback(); + } + +} +