/*
 * 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()) {
                    static_cast<void>(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()) {
                    static_cast<void>(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)) {
                    static_cast<void>(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. */
                static_cast<void>(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()) {
                            static_cast<void>(this->AbortTransaction());
                            R_THROW(sdmmc::ResultCommandCompleteSoftwareTimeout());
                        }
                    } else {
                        /* Otherwise, we have a generic failure. */
                        static_cast<void>(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. */
                        static_cast<void>(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)) {
                        static_cast<void>(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)) {
                                    static_cast<void>(this->AbortTransaction());
                                    R_THROW(sdmmc::ResultTransferCompleteSoftwareTimeout());
                                }
                                break;
                            }
                        } else {
                            /* Otherwise, we have a generic failure. */
                            static_cast<void>(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()) {
                    static_cast<void>(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;
    }

}
