mirror of
https://github.com/Atmosphere-NX/Atmosphere-libs.git
synced 2025-06-22 11:32:38 +02:00
1040 lines
48 KiB
C++
1040 lines
48 KiB
C++
/*
|
|
* Copyright (c) Atmosphère-NX
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
|
|
#include <stratosphere.hpp>
|
|
#elif defined(ATMOSPHERE_IS_MESOSPHERE)
|
|
#include <mesosphere.hpp>
|
|
#elif defined(ATMOSPHERE_IS_EXOSPHERE)
|
|
#include <exosphere.hpp>
|
|
#else
|
|
#include <vapours.hpp>
|
|
#endif
|
|
#include "sdmmc_sd_host_standard_controller.hpp"
|
|
#include "sdmmc_timer.hpp"
|
|
|
|
#if defined(ATMOSPHERE_IS_STRATOSPHERE)
|
|
#include <stratosphere/dd.hpp>
|
|
#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 : m_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 : m_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(m_registers->clock_control);
|
|
}
|
|
|
|
Result SdHostStandardController::EnableInternalClock() {
|
|
/* Enable internal clock. */
|
|
reg::ReadWrite(m_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(m_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(m_registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER));
|
|
reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK));
|
|
|
|
/* Set host version 4.0.0 enable. */
|
|
reg::ReadWrite(m_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(m_registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED)));
|
|
reg::ReadWrite(m_registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING));
|
|
|
|
/* Select SDMA mode. */
|
|
reg::ReadWrite(m_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(m_registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110));
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
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(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF));
|
|
break;
|
|
case BusPower_1_8V:
|
|
reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V));
|
|
reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON));
|
|
break;
|
|
case BusPower_3_3V:
|
|
reg::ReadWrite(m_registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V));
|
|
reg::ReadWrite(m_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(m_registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED));
|
|
reg::ReadWrite(m_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(m_registers->normal_int_enable, reg::Read(m_registers->normal_int_enable));
|
|
reg::Write(m_registers->error_int_enable, reg::Read(m_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(m_registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED));
|
|
reg::ReadWrite(m_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(m_registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED));
|
|
reg::ReadWrite(m_registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED));
|
|
}
|
|
#endif
|
|
|
|
/* Mask the status register interrupt enables. */
|
|
reg::ReadWrite(m_registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED));
|
|
reg::ReadWrite(m_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::MultiWaitHolderType *signaled_holder = os::TimedWaitAny(std::addressof(m_multi_wait), TimeSpan::FromMilliSeconds(timeout_ms));
|
|
if (signaled_holder == std::addressof(m_interrupt_event_holder)) {
|
|
/* We received the interrupt. */
|
|
R_SUCCEED();
|
|
} else if (signaled_holder == std::addressof(m_removed_event_holder)) {
|
|
/* The device was removed. */
|
|
R_THROW(sdmmc::ResultDeviceRemoved());
|
|
} else {
|
|
/* Timeout occurred. */
|
|
R_THROW(sdmmc::ResultWaitInterruptSoftwareTimeout());
|
|
}
|
|
}
|
|
|
|
void SdHostStandardController::ClearInterrupt() {
|
|
/* Ensure that we control the registers. */
|
|
this->EnsureControl();
|
|
|
|
/* Clear the interrupt event. */
|
|
os::ClearInterruptEvent(m_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<u16>(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<uintptr_t>(xfer_data->buffer), xfer_data->block_size * num_blocks);
|
|
const u16 num_xfer_blocks = num_blocks;
|
|
#else
|
|
const u64 address = reinterpret_cast<uintptr_t>(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(m_registers->adma_address, static_cast<u32>(address >> 0));
|
|
reg::Write(m_registers->upper_adma_address, static_cast<u32>(address >> BITSIZEOF(u32)));
|
|
|
|
/* Set our next sdma address. */
|
|
m_next_sdma_address = util::AlignDown<u64>(address + SdmaBufferBoundary, SdmaBufferBoundary);
|
|
|
|
/* Configure block size. */
|
|
AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax);
|
|
reg::Write(m_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<u16>(xfer_data->block_size)));
|
|
|
|
/* Configure transfer blocks. */
|
|
reg::Write(m_registers->block_count, num_xfer_blocks);
|
|
if (out_num_transferred_blocks != nullptr) {
|
|
*out_num_transferred_blocks = num_xfer_blocks;
|
|
}
|
|
|
|
/* Configure transfer mode. */
|
|
reg::Write(m_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(m_registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size));
|
|
|
|
/* Configure transfer blocks. */
|
|
reg::Write(m_registers->block_count, 1);
|
|
|
|
/* Configure transfer mode. */
|
|
reg::Write(m_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(m_registers->argument, command->command_argument);
|
|
reg::Write(m_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(m_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(m_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()) {
|
|
R_THROW(sdmmc::ResultAbortTransactionSoftwareTimeout());
|
|
}
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result SdHostStandardController::AbortTransaction() {
|
|
R_TRY(this->ResetCmdDatLine());
|
|
R_SUCCEED();
|
|
}
|
|
|
|
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(m_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();
|
|
R_THROW(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(m_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();
|
|
R_THROW(sdmmc::ResultCommandInhibitDatSoftwareTimeout());
|
|
}
|
|
}
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) {
|
|
/* Read the statuses. */
|
|
volatile u16 normal_int_status = reg::Read(m_registers->normal_int_status);
|
|
volatile u16 error_int_status = reg::Read(m_registers->error_int_status);
|
|
volatile u16 auto_cmd_err_status = reg::Read(m_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(m_registers->normal_int_status, masked_status);
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* We have an error interrupt. Write the status to the register to ensure consistent state. */
|
|
reg::Write(m_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. */
|
|
R_THROW(sdmmc::ResultSdHostStandardUnknownAutoCmdError());
|
|
} else {
|
|
/* Unknown error occurred. */
|
|
R_THROW(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();
|
|
}
|
|
|
|
R_RETURN(result);
|
|
} else if (sdmmc::ResultDeviceRemoved::Includes(result)) {
|
|
/* Otherwise, check if the device was removed. */
|
|
R_RETURN(result);
|
|
} else {
|
|
/* If the device wasn't removed, cancel our transaction. */
|
|
this->AbortTransaction();
|
|
R_THROW(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)) {
|
|
R_SUCCEED();
|
|
} 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();
|
|
R_THROW(sdmmc::ResultCommandCompleteSoftwareTimeout());
|
|
}
|
|
} else {
|
|
/* Otherwise, we have a generic failure. */
|
|
this->AbortTransaction();
|
|
R_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(m_registers->block_count);
|
|
|
|
/* Wait for interrupt. */
|
|
Result result = this->WaitInterrupt(m_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))) {
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* 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(m_registers->adma_address, static_cast<u32>(m_next_sdma_address >> 0));
|
|
reg::Write(m_registers->upper_adma_address, static_cast<u32>(m_next_sdma_address >> BITSIZEOF(u32)));
|
|
|
|
m_next_sdma_address += SdmaBufferBoundary;
|
|
}
|
|
} else {
|
|
/* Abort the transaction. */
|
|
this->AbortTransaction();
|
|
R_RETURN(result);
|
|
}
|
|
|
|
R_RETURN(result);
|
|
} else if (sdmmc::ResultDeviceRemoved::Includes(result)) {
|
|
/* Otherwise, check if the device was removed. */
|
|
R_RETURN(result);
|
|
} else {
|
|
/* Otherwise, timeout if the transfer hasn't advanced. */
|
|
if (last_block_count != reg::Read(m_registers->block_count)) {
|
|
this->AbortTransaction();
|
|
R_THROW(sdmmc::ResultTransferCompleteSoftwareTimeout());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
/* Wait while transfer is not complete. */
|
|
while (true) {
|
|
/* Get the last block count. */
|
|
const u16 last_block_count = reg::Read(m_registers->block_count);
|
|
|
|
/* Wait until transfer times out. */
|
|
{
|
|
ManualTimer timer(m_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))) {
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* 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(m_registers->adma_address, static_cast<u32>(m_next_sdma_address >> 0));
|
|
reg::Write(m_registers->upper_adma_address, static_cast<u32>(m_next_sdma_address >> BITSIZEOF(u32)));
|
|
|
|
m_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(m_registers->block_count)) {
|
|
this->AbortTransaction();
|
|
R_THROW(sdmmc::ResultTransferCompleteSoftwareTimeout());
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
/* Otherwise, we have a generic failure. */
|
|
this->AbortTransaction();
|
|
R_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(m_registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) {
|
|
R_SUCCEED();
|
|
}
|
|
|
|
/* Otherwise, check if we're timed out. */
|
|
if (!timer.Update()) {
|
|
this->AbortTransaction();
|
|
R_THROW(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(m_registers->response[0]);
|
|
break;
|
|
case ResponseType_R2:
|
|
/* 128-bit response. */
|
|
AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4);
|
|
out_response[0] = reg::Read(m_registers->response[0]);
|
|
out_response[1] = reg::Read(m_registers->response[1]);
|
|
out_response[2] = reg::Read(m_registers->response[2]);
|
|
out_response[3] = reg::Read(m_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) {
|
|
m_last_response_type = command->response_type;
|
|
this->GetResponse(m_last_response, sizeof(m_last_response), m_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. */
|
|
m_last_stop_transmission_response = m_registers->response[3];
|
|
}
|
|
|
|
/* Wait until we're no longer busy. */
|
|
if (command->is_busy || (xfer_data != nullptr)) {
|
|
R_TRY(this->WaitWhileBusy());
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
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());
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
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);
|
|
m_registers = reinterpret_cast<SdHostStandardRegisters *>(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)
|
|
m_removed_event = nullptr;
|
|
#endif
|
|
|
|
/* Clear dma address. */
|
|
m_next_sdma_address = 0;
|
|
m_check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds;
|
|
|
|
/* Clear clock/power trackers. */
|
|
m_device_clock_frequency_khz = 0;
|
|
m_is_power_saving_enable = false;
|
|
m_is_device_clock_enable = false;
|
|
|
|
/* Clear last response. */
|
|
m_last_response_type = ResponseType_R0;
|
|
std::memset(m_last_response, 0, sizeof(m_last_response));
|
|
m_last_stop_transmission_response = 0;
|
|
}
|
|
|
|
void SdHostStandardController::Initialize() {
|
|
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
|
{
|
|
os::InitializeMultiWait(std::addressof(m_multi_wait));
|
|
|
|
AMS_ABORT_UNLESS(m_interrupt_event != nullptr);
|
|
os::InitializeMultiWaitHolder(std::addressof(m_interrupt_event_holder), m_interrupt_event);
|
|
os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_interrupt_event_holder));
|
|
|
|
if (m_removed_event != nullptr) {
|
|
os::InitializeMultiWaitHolder(std::addressof(m_removed_event_holder), m_removed_event);
|
|
os::LinkMultiWaitHolder(std::addressof(m_multi_wait), std::addressof(m_removed_event_holder));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SdHostStandardController::Finalize() {
|
|
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
|
{
|
|
if (m_removed_event != nullptr) {
|
|
os::UnlinkMultiWaitHolder(std::addressof(m_removed_event_holder));
|
|
os::FinalizeMultiWaitHolder(std::addressof(m_removed_event_holder));
|
|
}
|
|
|
|
os::UnlinkMultiWaitHolder(std::addressof(m_interrupt_event_holder));
|
|
os::FinalizeMultiWaitHolder(std::addressof(m_interrupt_event_holder));
|
|
|
|
os::FinalizeMultiWait(std::addressof(m_multi_wait));
|
|
}
|
|
#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 : m_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 : m_buffer_infos) {
|
|
if (info.buffer_address == buffer) {
|
|
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(m_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(m_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(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT));
|
|
reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH));
|
|
break;
|
|
case BusWidth_4Bit:
|
|
reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT));
|
|
reg::ReadWrite(m_registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH));
|
|
break;
|
|
case BusWidth_8Bit:
|
|
reg::ReadWrite(m_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(m_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(m_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. */
|
|
m_is_power_saving_enable = en;
|
|
|
|
/* Configure accordingly. */
|
|
if (m_is_power_saving_enable) {
|
|
/* We want to disable SD clock if it's enabled. */
|
|
if (reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) {
|
|
reg::ReadWrite(m_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 (m_is_device_clock_enable && reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) {
|
|
reg::ReadWrite(m_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 (!m_is_power_saving_enable && reg::HasValue(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) {
|
|
reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
|
}
|
|
m_is_device_clock_enable = true;
|
|
}
|
|
|
|
void SdHostStandardController::DisableDeviceClock() {
|
|
/* Unconditionally disable the device clock. */
|
|
m_is_device_clock_enable = false;
|
|
reg::ReadWrite(m_registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
|
}
|
|
|
|
void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) {
|
|
m_check_transfer_interval_ms = ms;
|
|
}
|
|
|
|
void SdHostStandardController::SetDefaultCheckTransferInterval() {
|
|
m_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(m_is_device_clock_enable);
|
|
|
|
/* Check if we need to temporarily re-enable the device clock. */
|
|
const bool clock_disabled = reg::HasValue(m_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(m_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, m_device_clock_frequency_khz);
|
|
}
|
|
ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(m_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, m_device_clock_frequency_khz); };
|
|
|
|
R_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(m_is_device_clock_enable);
|
|
|
|
/* Check if we need to temporarily re-enable the device clock. */
|
|
const bool clock_disabled = reg::HasValue(m_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(m_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, m_device_clock_frequency_khz);
|
|
}
|
|
ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(m_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, m_device_clock_frequency_khz); };
|
|
|
|
R_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 == m_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] = m_last_response[0];
|
|
break;
|
|
case ResponseType_R2:
|
|
/* 128-bit response. */
|
|
AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4);
|
|
out_response[0] = m_last_response[0];
|
|
out_response[1] = m_last_response[1];
|
|
out_response[2] = m_last_response[2];
|
|
out_response[3] = m_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] = m_last_stop_transmission_response;
|
|
}
|
|
|
|
}
|