mirror of
https://github.com/Atmosphere-NX/Atmosphere.git
synced 2025-05-27 21:24:11 -04:00
sdmmc: implement driver suitable for fs + bootloader
* sdmmc: begin skeletoning sdmmc driver * sdmmc: add most of SdHostStandardController * sdmmc: implement most of SdmmcController * sdmmc: Sdmmc2Controller * sdmmc: skeleton implementation of Sdmmc1Controller * sdmmc: complete abstract logic for Sdmmc1 power controller * sdmmc: implement gpio handling for sdmmc1-register-control * sdmmc: implement pinmux handling for sdmmc1-register-control * sdmmc: fix building for arm32 and in stratosphere context * sdmmc: implement voltage enable/set for sdmmc1-register-control * util: move T(V)SNPrintf from kernel to util * sdmmc: implement BaseDeviceAccessor * sdmmc: implement MmcDeviceAccessor * sdmmc: implement clock reset controller for register api * sdmmc: fix bug in WaitWhileCommandInhibit, add mmc accessors * exo: add sdmmc test program * sdmmc: fix speed mode extension, add CheckMmcConnection for debug * sdmmc: add DeviceDetector, gpio: implement client api * gpio: modernize client api instead of doing it the lazy way * sdmmc: SdCardDeviceAccessor impl * sdmmc: update test program to read first two sectors of sd card * sdmmc: fix vref sel * sdmmc: finish outward-facing api (untested) * ams: changes for libvapours including tegra register defs * sdmmc: remove hwinit
This commit is contained in:
parent
ac04e02a08
commit
166318ba77
143 changed files with 13696 additions and 1569 deletions
|
@ -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 <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_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<u8>((this->csd[2] >> 7) & 0x7);
|
||||
*out_read_bl_len = static_cast<u8>((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<DeviceState>((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<uintptr_t>(dst), alignof(u32)));
|
||||
AMS_ABORT_UNLESS(dst_size >= DeviceCidSize);
|
||||
this->host_controller->GetLastResponse(static_cast<u32 *>(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<u32>(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<u32>(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<uintptr_t>(dst), alignof(u32)));
|
||||
AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize);
|
||||
this->host_controller->GetLastResponse(static_cast<u32 *>(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<u32>(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<u8 *>(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
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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<size_t>(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<int>(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<int>(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<u32>(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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <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_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<Module>(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<Module>(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
|
@ -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 <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_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
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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);
|
||||
|
||||
}
|
|
@ -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 <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_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<u32>(n)); break;
|
||||
case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast<u32>(n)); break;
|
||||
case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast<u32>(n)); break;
|
||||
case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast<u32>(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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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);
|
||||
|
||||
}
|
270
libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp
Normal file
270
libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp
Normal file
|
@ -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 <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
|
||||
#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
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR)
|
||||
#include <stratosphere.hpp>
|
||||
|
||||
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<DeviceDetector *>(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
|
|
@ -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 <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_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<void *>(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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
||||
#include <stratosphere/os.hpp>
|
||||
#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();
|
||||
virtual void PutToSleep();
|
||||
virtual Result Awaken();
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
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);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <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_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<const u8 *>(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<const u8 *>(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<uintptr_t>(wb), alignof(u32)));
|
||||
this->mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast<const u32 *>(wb)));
|
||||
|
||||
/* If the mmc is manufactured by toshiba, try to enable bkops auto. */
|
||||
if (is_toshiba && !IsBkopAutoEnable(static_cast<const u8 *>(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<const u8 *>(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<int>(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<const u8 *>(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<const u8 *>(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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <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_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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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();
|
||||
|
||||
}
|
51
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp
Normal file
51
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp
Normal file
|
@ -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 <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_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);
|
||||
}
|
||||
|
||||
}
|
28
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp
Normal file
28
libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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();
|
||||
|
||||
}
|
|
@ -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 <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_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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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();
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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<CommandIndex>::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<SdCardDeviceAccessor *>(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
|
||||
}
|
||||
};
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
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);
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#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<SdmmcRegisters *>(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_EMMC4_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) {
|
||||
/* ... */
|
||||
}
|
||||
};
|
||||
|
||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
#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
|
90
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp
Normal file
90
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp
Normal file
|
@ -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 <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_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));
|
||||
}
|
||||
|
||||
}
|
68
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp
Normal file
68
libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp
Normal file
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
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
|
||||
|
||||
}
|
145
libraries/libvapours/source/sdmmc/sdmmc_common.cpp
Normal file
145
libraries/libvapours/source/sdmmc/sdmmc_common.cpp
Normal file
|
@ -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 <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 "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<void *>(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);
|
||||
}
|
||||
|
||||
}
|
85
libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp
Normal file
85
libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp
Normal file
|
@ -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 <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 "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();
|
||||
}
|
||||
|
||||
}
|
81
libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp
Normal file
81
libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp
Normal file
|
@ -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 <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 "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);
|
||||
}
|
||||
|
||||
}
|
103
libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp
Normal file
103
libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp
Normal file
|
@ -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 <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 "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();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue