mirror of
https://github.com/Atmosphere-NX/Atmosphere
synced 2024-12-22 20:31:14 +00:00
sdmmc: implement most of SdmmcController
This commit is contained in:
parent
895b332a86
commit
2b84a49ed7
27 changed files with 1470 additions and 90 deletions
|
@ -40,4 +40,3 @@
|
|||
#include <exosphere/actmon.hpp>
|
||||
#include <exosphere/pmc.hpp>
|
||||
#include <exosphere/secmon.hpp>
|
||||
#include <exosphere/tegra.hpp>
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2020 Atmosphère-NX
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
|
||||
#include <exosphere/tegra/tegra_ahb_arbc.hpp>
|
||||
#include <exosphere/tegra/tegra_apb_misc.hpp>
|
||||
#include <exosphere/tegra/tegra_avp_cache.hpp>
|
||||
#include <exosphere/tegra/tegra_clkrst.hpp>
|
||||
#include <exosphere/tegra/tegra_emc.hpp>
|
||||
#include <exosphere/tegra/tegra_evp.hpp>
|
||||
#include <exosphere/tegra/tegra_flow_ctlr.hpp>
|
||||
#include <exosphere/tegra/tegra_ictlr.hpp>
|
||||
#include <exosphere/tegra/tegra_mc.hpp>
|
||||
#include <exosphere/tegra/tegra_mselect.hpp>
|
||||
#include <exosphere/tegra/tegra_pinmux.hpp>
|
||||
#include <exosphere/tegra/tegra_pg_up.hpp>
|
||||
#include <exosphere/tegra/tegra_pmc.hpp>
|
||||
#include <exosphere/tegra/tegra_sb.hpp>
|
||||
#include <exosphere/tegra/tegra_sysctr0.hpp>
|
||||
#include <exosphere/tegra/tegra_timer.hpp>
|
|
@ -26,6 +26,11 @@
|
|||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#if defined(ATMOSPHERE_BOARD_NINTENDO_NX)
|
||||
#include <vapours/tegra.hpp>
|
||||
#endif
|
||||
|
||||
#include <vapours/crypto.hpp>
|
||||
#include <vapours/svc.hpp>
|
||||
|
||||
|
|
|
@ -40,14 +40,26 @@ namespace ams::sdmmc {
|
|||
R_DEFINE_ERROR_RESULT(AutoCommandResponseTimeoutError, 44);
|
||||
R_DEFINE_ERROR_RESULT(CommandCompleteSoftwareTimeout, 45);
|
||||
R_DEFINE_ERROR_RESULT(TransferCompleteSoftwareTimeout, 46);
|
||||
R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74);
|
||||
R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75);
|
||||
R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76);
|
||||
R_DEFINE_ERROR_RESULT(BusySoftwareTimeout, 77);
|
||||
R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74);
|
||||
R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75);
|
||||
R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76);
|
||||
R_DEFINE_ERROR_RESULT(BusySoftwareTimeout, 77);
|
||||
R_DEFINE_ERROR_RESULT(IssueTuningCommandSoftwareTimeout, 78);
|
||||
R_DEFINE_ERROR_RESULT(TuningFailed, 79);
|
||||
R_DEFINE_ERROR_RESULT(SdCardNotReadyToVoltageSwitch, 96);
|
||||
R_DEFINE_ERROR_RESULT(SdCardNotCompleteVoltageSwitch, 97);
|
||||
|
||||
R_DEFINE_ERROR_RANGE(HostControllerUnexpected, 128, 158);
|
||||
R_DEFINE_ERROR_RESULT(SdHostStandardUnknownAutoCmdError, 130);
|
||||
R_DEFINE_ERROR_RESULT(SdHostStandardUnknownError, 131);
|
||||
R_DEFINE_ERROR_RESULT(InternalClockStableSoftwareTimeout, 129);
|
||||
R_DEFINE_ERROR_RESULT(SdHostStandardUnknownAutoCmdError, 130);
|
||||
R_DEFINE_ERROR_RESULT(SdHostStandardUnknownError, 131);
|
||||
R_DEFINE_ERROR_RESULT(SdmmcDllCalibrationSoftwareTimeout, 132);
|
||||
R_DEFINE_ERROR_RESULT(SdmmcDllApplicationSoftwareTimeout, 133);
|
||||
R_DEFINE_ERROR_RESULT(SdHostStandardFailSwitchTo1_8V, 134);
|
||||
R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationNotCompleted, 135);
|
||||
R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationSoftwareTimeout, 136);
|
||||
R_DEFINE_ERROR_RESULT(SdmmcCompShortToGnd, 137);
|
||||
R_DEFINE_ERROR_RESULT(SdmmcCompOpen, 138);
|
||||
|
||||
R_DEFINE_ERROR_RANGE(InternalError, 160, 190);
|
||||
R_DEFINE_ERROR_RESULT(NoWaitedInterrupt, 161);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#if defined(ATMOSPHERE_IS_EXOSPHERE)
|
||||
|
||||
//#define AMS_SDMMC_THREAD_SAFE
|
||||
//#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
|
||||
//#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
|
||||
//#define AMS_SDMMC_USE_OS_EVENTS
|
||||
|
@ -32,6 +33,7 @@
|
|||
|
||||
#elif defined(ATMOSPHERE_IS_MESOSPHERE)
|
||||
|
||||
//#define AMS_SDMMC_THREAD_SAFE
|
||||
//#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
|
||||
//#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
|
||||
//#define AMS_SDMMC_USE_OS_EVENTS
|
||||
|
@ -40,6 +42,7 @@
|
|||
|
||||
#elif defined(ATMOSPHERE_IS_STRATOSPHERE)
|
||||
|
||||
#define AMS_SDMMC_THREAD_SAFE
|
||||
#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS
|
||||
#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL
|
||||
#define AMS_SDMMC_USE_OS_EVENTS
|
||||
|
|
39
libraries/libvapours/include/vapours/tegra.hpp
Normal file
39
libraries/libvapours/include/vapours/tegra.hpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#include <vapours/tegra/tegra_ahb_arbc.hpp>
|
||||
#include <vapours/tegra/tegra_apb_misc.hpp>
|
||||
#include <vapours/tegra/tegra_avp_cache.hpp>
|
||||
#include <vapours/tegra/tegra_clkrst.hpp>
|
||||
#include <vapours/tegra/tegra_emc.hpp>
|
||||
#include <vapours/tegra/tegra_evp.hpp>
|
||||
#include <vapours/tegra/tegra_flow_ctlr.hpp>
|
||||
#include <vapours/tegra/tegra_ictlr.hpp>
|
||||
#include <vapours/tegra/tegra_mc.hpp>
|
||||
#include <vapours/tegra/tegra_mselect.hpp>
|
||||
#include <vapours/tegra/tegra_pinmux.hpp>
|
||||
#include <vapours/tegra/tegra_pg_up.hpp>
|
||||
#include <vapours/tegra/tegra_pmc.hpp>
|
||||
#include <vapours/tegra/tegra_sb.hpp>
|
||||
#include <vapours/tegra/tegra_sysctr0.hpp>
|
||||
#include <vapours/tegra/tegra_timer.hpp>
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define AHB_ARBC(x) (0x6000c000 + x)
|
||||
|
|
@ -14,11 +14,19 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define APB_MISC_PP_CONFIG_CTL (0x024)
|
||||
#define APB_MISC_PP_CONFIG_CTL (0x024)
|
||||
|
||||
#define APB_MISC_GP_ASDBGREG (0x810)
|
||||
#define APB_MISC_GP_ASDBGREG (0x810)
|
||||
|
||||
#define APB_MISC_GP_EMMC4_PAD_CFGPADCTRL (0xAB4)
|
||||
#define APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL (0xABC)
|
||||
|
||||
#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00)
|
||||
#define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00)
|
||||
|
@ -41,6 +49,15 @@ DEFINE_APB_MISC_REG_BIT_ENUM(PP_CONFIG_CTL_TBE, 7, DISABLE, ENABLE);
|
|||
|
||||
DEFINE_APB_MISC_REG(GP_ASDBGREG_CFG2TMC_RAM_SVOP_PDP, 24, 2);
|
||||
|
||||
DEFINE_APB_MISC_REG_BIT_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, 0, DISABLE, ENABLE);
|
||||
DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, 2, 6);
|
||||
DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, 8, 6);
|
||||
DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 14, 13);
|
||||
|
||||
DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 1, 1);
|
||||
DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 2, 1);
|
||||
DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 22, 1);
|
||||
|
||||
#define DEFINE_SLAVE_SECURITY_REG(RINDEX, INDEX, NAME) DEFINE_APB_MISC_REG_BIT_ENUM(SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG##RINDEX##_##NAME##_SECURITY_EN, INDEX, DISABLE, ENABLE)
|
||||
|
||||
DEFINE_SLAVE_SECURITY_REG(0, 29, STM);
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define AVP_CACHE_ADDRESS(x) (0x50040000 + x)
|
||||
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
/* Clock source enums. */
|
||||
#define CLK_RST_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (CLK_RST_CONTROLLER, NAME)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define EMC_ADDRESS(x) (0x7001B000 + x)
|
||||
#define EMC0_ADDRESS(x) (0x7001E000 + x)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define EVP_CPU_RESET_VECTOR (0x100)
|
||||
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define FLOW_CTLR_RAM_REPAIR (0x040)
|
||||
#define FLOW_CTLR_FLOW_DBG_QUAL (0x050)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define PRI_ICTLR(n) (0x60004000 + n)
|
||||
#define SEC_ICTLR(n) (0x60004100 + n)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define MC_INTSTATUS (0x000)
|
||||
#define MC_INTMASK (0x004)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define MSELECT(x) (0x50060000 + x)
|
||||
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define PG_UP(x) (0x60000000 + x)
|
||||
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define PINMUX_AUX_GEN1_I2C_SCL (0x30BC)
|
||||
#define PINMUX_AUX_GEN1_I2C_SDA (0x30C0)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define APBDEV_PMC_CNTRL (0x000)
|
||||
#define APBDEV_PMC_WAKE_MASK (0x00C)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define SB_CSR (0x200)
|
||||
#define SB_PFCFG (0x208)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define SYSCTR0_CNTCR (0x000)
|
||||
#define SYSCTR0_CNTCV0 (0x008)
|
|
@ -14,7 +14,12 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#pragma once
|
||||
#include <vapours.hpp>
|
||||
#include <vapours/common.hpp>
|
||||
#include <vapours/assert.hpp>
|
||||
#include <vapours/literals.hpp>
|
||||
#include <vapours/util.hpp>
|
||||
#include <vapours/results.hpp>
|
||||
#include <vapours/reg.hpp>
|
||||
|
||||
#define TIMERUS_USEC_CFG (0x014)
|
||||
#define TIMER_SHARED_TIMER_SECURE_CFG (0x1A4)
|
|
@ -62,6 +62,66 @@ namespace ams::sdmmc::impl {
|
|||
reg::Read(this->registers->clock_control);
|
||||
}
|
||||
|
||||
Result SdHostStandardController::EnableInternalClock() {
|
||||
/* Enable internal clock. */
|
||||
reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, OSCILLATE));
|
||||
this->EnsureControl();
|
||||
|
||||
/* Wait for the internal clock to become stable. */
|
||||
{
|
||||
ManualTimer timer(ControllerReactionTimeoutMilliSeconds);
|
||||
while (true) {
|
||||
/* Check if the clock is steady. */
|
||||
if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, READY))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* If not, check for timeout. */
|
||||
R_UNLESS(timer.Update(), sdmmc::ResultInternalClockStableSoftwareTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure to use host controlled divided clock. */
|
||||
reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER));
|
||||
reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK));
|
||||
|
||||
/* Set host version 4.0.0 enable. */
|
||||
reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, VERSION_4));
|
||||
|
||||
/* Set host 64 bit addressing enable. */
|
||||
AMS_ABORT_UNLESS(reg::HasValue(this->registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED)));
|
||||
reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING));
|
||||
|
||||
/* Select SDMA mode. */
|
||||
reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DMA_SELECT, SDMA));
|
||||
|
||||
/* Configure timeout control to use the maximum timeout value (TMCLK * 2^27) */
|
||||
reg::ReadWrite(this->registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void SdHostStandardController::SetBusPower(BusPower bus_power) {
|
||||
/* Check that we support the bus power. */
|
||||
AMS_ABORT_UNLESS(this->IsSupportedBusPower(bus_power));
|
||||
|
||||
/* Set the appropriate power. */
|
||||
switch (bus_power) {
|
||||
case BusPower_Off:
|
||||
reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF));
|
||||
break;
|
||||
case BusPower_1_8V:
|
||||
reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V));
|
||||
reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON));
|
||||
break;
|
||||
case BusPower_3_3V:
|
||||
reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V));
|
||||
reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON));
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
void SdHostStandardController::EnableInterruptStatus() {
|
||||
/* Set the status register interrupt enables. */
|
||||
reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED));
|
||||
|
@ -156,7 +216,7 @@ namespace ams::sdmmc::impl {
|
|||
|
||||
/* Configure block size. */
|
||||
AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax);
|
||||
reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, FIVE_TWELVE_KB),
|
||||
reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 512_KB),
|
||||
SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast<u16>(xfer_data->block_size)));
|
||||
|
||||
/* Configure transfer blocks. */
|
||||
|
@ -173,6 +233,31 @@ namespace ams::sdmmc::impl {
|
|||
SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE));
|
||||
}
|
||||
|
||||
void SdHostStandardController::SetTransferForTuning() {
|
||||
/* Get the tuning block size. */
|
||||
u16 tuning_block_size;
|
||||
switch (this->GetBusWidth()) {
|
||||
case BusWidth_4Bit:
|
||||
tuning_block_size = 64;
|
||||
break;
|
||||
case BusWidth_8Bit:
|
||||
tuning_block_size = 128;
|
||||
break;
|
||||
case BusWidth_1Bit:
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Configure block size. */
|
||||
AMS_ABORT_UNLESS(tuning_block_size <= SdHostStandardBlockSizeTransferBlockSizeMax);
|
||||
reg::Write(this->registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size));
|
||||
|
||||
/* Configure transfer blocks. */
|
||||
reg::Write(this->registers->block_count, 1);
|
||||
|
||||
/* Configure transfer mode. */
|
||||
reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, READ));
|
||||
}
|
||||
|
||||
void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) {
|
||||
/* Encode the command value. */
|
||||
u16 command_val = 0;
|
||||
|
@ -216,6 +301,11 @@ namespace ams::sdmmc::impl {
|
|||
reg::Write(this->registers->command, command_val);
|
||||
}
|
||||
|
||||
void SdHostStandardController::SetCommandForTuning(u32 command_index) {
|
||||
Command command(command_index, 0, ResponseType_R1, false);
|
||||
return this->SetCommand(std::addressof(command), true);
|
||||
}
|
||||
|
||||
Result SdHostStandardController::ResetCmdDatLine() {
|
||||
/* Set the software reset cmd/dat bits. */
|
||||
reg::ReadWrite(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET),
|
||||
|
@ -855,7 +945,7 @@ namespace ams::sdmmc::impl {
|
|||
AMS_ABORT_UNLESS(this->is_device_clock_enable);
|
||||
|
||||
/* Check if we need to temporarily re-enable the device clock. */
|
||||
bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
|
||||
/* Ensure that the clock is enabled and the device is usable for the period we're using it. */
|
||||
if (clock_disabled) {
|
||||
|
@ -884,7 +974,7 @@ namespace ams::sdmmc::impl {
|
|||
AMS_ABORT_UNLESS(this->is_device_clock_enable);
|
||||
|
||||
/* Check if we need to temporarily re-enable the device clock. */
|
||||
bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
|
||||
/* Ensure that the clock is enabled and the device is usable for the period we're using it. */
|
||||
if (clock_disabled) {
|
||||
|
|
|
@ -67,12 +67,18 @@ namespace ams::sdmmc::impl {
|
|||
}
|
||||
#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();
|
||||
|
@ -83,8 +89,10 @@ namespace ams::sdmmc::impl {
|
|||
#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();
|
||||
|
|
|
@ -83,7 +83,7 @@ namespace ams::sdmmc::impl {
|
|||
#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, FOUR_KB, EIGHT_KB, SIXTEEN_KB, THIRTY_TWO_KB, SIXTY_FOUR_KB, ONE_TWENTY_EIGHT_KB, TWO_FIFTY_SIX_KB, FIVE_TWELVE_KB);
|
||||
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);
|
||||
|
@ -105,6 +105,7 @@ namespace ams::sdmmc::impl {
|
|||
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);
|
||||
|
@ -114,7 +115,14 @@ namespace ams::sdmmc::impl {
|
|||
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);
|
||||
|
@ -139,30 +147,47 @@ namespace ams::sdmmc::impl {
|
|||
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_STATUS_ENABLE_COMMAND_COMPLETE, 0, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_TRANSFER_COMPLETE, 1, MASKED, ENABLED);
|
||||
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_STATUS_ENABLE_COMMAND_COMPLETE, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ENABLE_TRANSFER_COMPLETE, __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_STATUS_ENABLE_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_CRC_ERROR, 1, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_CRC_ERROR, 5, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_END_BIT_ERROR, 6, MASKED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_AUTO_CMD_ERROR, 8, MASKED, ENABLED);
|
||||
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_STATUS_ENABLE_COMMAND_TIMEOUT_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_CRC_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_END_BIT_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_COMMAND_INDEX_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_TIMEOUT_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_CRC_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_DATA_END_BIT_ERROR, __ENUM__), \
|
||||
SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_ENABLE_AUTO_CMD_ERROR, __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);
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,872 @@
|
|||
/*
|
||||
* 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_sdmmc_controller.board.nintendo_nx.hpp"
|
||||
#include "sdmmc_timer.hpp"
|
||||
|
||||
namespace ams::sdmmc::impl {
|
||||
|
||||
/* FOR REFERENCE: board-specific sdmmc registers. */
|
||||
//struct SdmmcRegisters {
|
||||
// /* Standard registers. */
|
||||
// volatile SdHostStandardRegisters sd_host_standard_registers;
|
||||
//
|
||||
// /* Vendor specific registers */
|
||||
// volatile uint32_t vendor_clock_cntrl;
|
||||
// volatile uint32_t vendor_sys_sw_cntrl;
|
||||
// volatile uint32_t vendor_err_intr_status;
|
||||
// volatile uint32_t vendor_cap_overrides;
|
||||
// volatile uint32_t vendor_boot_cntrl;
|
||||
// volatile uint32_t vendor_boot_ack_timeout;
|
||||
// volatile uint32_t vendor_boot_dat_timeout;
|
||||
// volatile uint32_t vendor_debounce_count;
|
||||
// volatile uint32_t vendor_misc_cntrl;
|
||||
// volatile uint32_t max_current_override;
|
||||
// volatile uint32_t max_current_override_hi;
|
||||
// volatile uint32_t _0x12c[0x20];
|
||||
// volatile uint32_t vendor_io_trim_cntrl;
|
||||
//
|
||||
// /* Start of sdmmc2/sdmmc4 only */
|
||||
// volatile uint32_t vendor_dllcal_cfg;
|
||||
// volatile uint32_t vendor_dll_ctrl0;
|
||||
// volatile uint32_t vendor_dll_ctrl1;
|
||||
// volatile uint32_t vendor_dllcal_cfg_sta;
|
||||
// /* End of sdmmc2/sdmmc4 only */
|
||||
//
|
||||
// volatile uint32_t vendor_tuning_cntrl0;
|
||||
// volatile uint32_t vendor_tuning_cntrl1;
|
||||
// volatile uint32_t vendor_tuning_status0;
|
||||
// volatile uint32_t vendor_tuning_status1;
|
||||
// volatile uint32_t vendor_clk_gate_hysteresis_count;
|
||||
// volatile uint32_t vendor_preset_val0;
|
||||
// volatile uint32_t vendor_preset_val1;
|
||||
// volatile uint32_t vendor_preset_val2;
|
||||
// volatile uint32_t sdmemcomppadctrl;
|
||||
// volatile uint32_t auto_cal_config;
|
||||
// volatile uint32_t auto_cal_interval;
|
||||
// volatile uint32_t auto_cal_status;
|
||||
// volatile uint32_t io_spare;
|
||||
// volatile uint32_t sdmmca_mccif_fifoctrl;
|
||||
// volatile uint32_t timeout_wcoal_sdmmca;
|
||||
// volatile uint32_t _0x1fc;
|
||||
//};
|
||||
|
||||
DEFINE_SD_REG_BIT_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, 2, NORMAL, OVERRIDE);
|
||||
DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TAP_VAL, 16, 8);
|
||||
DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TRIM_VAL, 24, 5);
|
||||
|
||||
DEFINE_SD_REG(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 8, 6);
|
||||
|
||||
DEFINE_SD_REG(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 2, 1);
|
||||
|
||||
DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, 31, DISABLE, ENABLE);
|
||||
|
||||
DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, 31, DONE, RUNNING);
|
||||
|
||||
DEFINE_SD_REG(VENDOR_TUNING_CNTRL0_MUL_M, 6, 7);
|
||||
DEFINE_SD_REG_THREE_BIT_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, 13, TRIES_40, TRIES_64, TRIES_128, TRIES_192, TRIES_256, RESERVED5, RESERVED6, RESERVED7);
|
||||
DEFINE_SD_REG_BIT_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, 17, NOT_UPDATED_BY_HW, UPDATED_BY_HW);
|
||||
|
||||
DEFINE_SD_REG(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, 0, 4);
|
||||
DEFINE_SD_REG(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 31, 1);
|
||||
|
||||
DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, 0, 7);
|
||||
DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, 8, 7);
|
||||
DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, 29, DISABLED, ENABLED);
|
||||
DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, 31, DISABLED, ENABLED);
|
||||
|
||||
DEFINE_SD_REG(AUTO_CAL_STATUS_AUTO_CAL_PULLUP, 0, 7);
|
||||
DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, 31, INACTIVE, ACTIVE);
|
||||
|
||||
DEFINE_SD_REG_BIT_ENUM(IO_SPARE_SPARE_OUT_3, 19, TWO_CYCLE_DELAY, ONE_CYCLE_DELAY);
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr inline u32 TuningCommandTimeoutMilliSeconds = 5;
|
||||
|
||||
constexpr void GetDividerSetting(u32 *out_target_clock_frequency_khz, u16 *out_x, SpeedMode speed_mode) {
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_MmcIdentification:
|
||||
*out_target_clock_frequency_khz = 26000;
|
||||
*out_x = 66;
|
||||
break;
|
||||
case SpeedMode_MmcLegacySpeed:
|
||||
*out_target_clock_frequency_khz = 26000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_MmcHighSpeed:
|
||||
*out_target_clock_frequency_khz = 52000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_MmcHs200:
|
||||
*out_target_clock_frequency_khz = 200000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_MmcHs400:
|
||||
*out_target_clock_frequency_khz = 200000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_SdCardIdentification:
|
||||
*out_target_clock_frequency_khz = 25000;
|
||||
*out_x = 64;
|
||||
break;
|
||||
case SpeedMode_SdCardDefaultSpeed:
|
||||
*out_target_clock_frequency_khz = 25000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_SdCardHighSpeed:
|
||||
*out_target_clock_frequency_khz = 50000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_SdCardSdr12:
|
||||
*out_target_clock_frequency_khz = 25000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_SdCardSdr50:
|
||||
*out_target_clock_frequency_khz = 100000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_SdCardSdr104:
|
||||
*out_target_clock_frequency_khz = 200000;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_GcAsicFpgaSpeed:
|
||||
*out_target_clock_frequency_khz = 40800;
|
||||
*out_x = 1;
|
||||
break;
|
||||
case SpeedMode_GcAsicSpeed:
|
||||
*out_target_clock_frequency_khz = 200000;
|
||||
*out_x = 2;
|
||||
break;
|
||||
case SpeedMode_SdCardSdr25:
|
||||
case SpeedMode_SdCardDdr50:
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
#if defined(AMS_SDMMC_THREAD_SAFE)
|
||||
constinit os::Mutex g_soc_mutex(false);
|
||||
|
||||
#define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex)
|
||||
|
||||
#else
|
||||
|
||||
#define AMS_SDMMC_LOCK_SOC_MUTEX()
|
||||
|
||||
#endif
|
||||
|
||||
constinit bool g_determined_soc = false;
|
||||
constinit bool g_is_soc_mariko = false;
|
||||
|
||||
}
|
||||
|
||||
bool IsSocMariko() {
|
||||
if (!g_determined_soc) {
|
||||
/* Ensure we have exclusive access to the soc variables. */
|
||||
AMS_SDMMC_LOCK_SOC_MUTEX();
|
||||
|
||||
/* Check the SocType. */
|
||||
#if defined(ATMOSPHERE_IS_EXOSPHERE)
|
||||
{
|
||||
g_is_soc_mariko = fuse::GetSocType() == fuse::SocType_Mariko;
|
||||
}
|
||||
#elif defined(ATMOSPHERE_IS_MESOSPHERE)
|
||||
{
|
||||
MESOSPHERE_TODO("Detect mariko via KSystemControl call?");
|
||||
}
|
||||
#elif defined(ATMOSPHERE_IS_STRATOSPHERE)
|
||||
{
|
||||
/* Connect to spl for the duration of our check. */
|
||||
spl::Initialize();
|
||||
ON_SCOPE_EXIT { spl::Finalize(); };
|
||||
|
||||
g_is_soc_mariko = spl::GetSocType() == spl::SocType_Mariko;
|
||||
}
|
||||
#else
|
||||
#error "Unknown execution context for ams::sdmmc::impl::IsSocMariko"
|
||||
#endif
|
||||
|
||||
/* Note that we determined the soc. */
|
||||
g_determined_soc = true;
|
||||
}
|
||||
|
||||
return g_is_soc_mariko;
|
||||
}
|
||||
|
||||
void SdmmcController::ReleaseReset(SpeedMode speed_mode) {
|
||||
/* Get the clock reset module. */
|
||||
const auto module = this->GetClockResetModule();
|
||||
|
||||
/* If the module is available, disable clock. */
|
||||
if (ClockResetController::IsAvailable(module)) {
|
||||
SdHostStandardController::DisableDeviceClock();
|
||||
SdHostStandardController::EnsureControl();
|
||||
}
|
||||
|
||||
/* Get the correct divider setting for the speed mode. */
|
||||
u32 target_clock_frequency_khz;
|
||||
u16 x;
|
||||
GetDividerSetting(std::addressof(target_clock_frequency_khz), std::addressof(x), speed_mode);
|
||||
|
||||
/* Release reset. */
|
||||
ClockResetController::ReleaseReset(module, target_clock_frequency_khz);
|
||||
}
|
||||
|
||||
void SdmmcController::AssertReset() {
|
||||
return ClockResetController::AssertReset(this->GetClockResetModule());
|
||||
}
|
||||
|
||||
Result SdmmcController::StartupCore(BusPower bus_power) {
|
||||
/* Set schmitt trigger. */
|
||||
this->SetSchmittTrigger(bus_power);
|
||||
|
||||
/* Select one-cycle delay version of cmd_oen. */
|
||||
reg::ReadWrite(this->sdmmc_registers->io_spare, SD_REG_BITS_ENUM(IO_SPARE_SPARE_OUT_3, ONE_CYCLE_DELAY));
|
||||
|
||||
/* Select regulated reference voltage for trimmer and DLL supply. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0));
|
||||
|
||||
/* Configure outbound tap value. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue()));
|
||||
|
||||
/* Configure SPI_MODE_CLKEN_OVERRIDE. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, NORMAL));
|
||||
|
||||
/* Set slew codes. */
|
||||
this->SetSlewCodes();
|
||||
|
||||
/* Set vref sel. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetOutboundTapValue()));
|
||||
|
||||
/* Perform drive strength calibration at the new power. */
|
||||
this->SetDriveCodeOffsets(bus_power);
|
||||
this->CalibrateDriveStrength(bus_power);
|
||||
|
||||
/* Enable internal clock. */
|
||||
R_TRY(SdHostStandardController::EnableInternalClock());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SdmmcController::SetClockTrimmer(SpeedMode speed_mode, u8 tap_value) {
|
||||
/* If speed mode is Hs400, set the dqs trim value. */
|
||||
if (speed_mode == SpeedMode_MmcHs400) {
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_cap_overrides, SD_REG_BITS_VALUE(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 40));
|
||||
}
|
||||
|
||||
/* Configure tap value as updated by software. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, NOT_UPDATED_BY_HW));
|
||||
|
||||
/* Set the inbound tap value. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TAP_VAL, tap_value));
|
||||
|
||||
/* Reset the cmd/dat line. */
|
||||
R_TRY(SdHostStandardController::ResetCmdDatLine());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
u8 SdmmcController::GetCurrentTapValue() {
|
||||
return static_cast<u8>(reg::GetValue(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_MASK(VENDOR_CLOCK_CNTRL_TAP_VAL)));
|
||||
}
|
||||
|
||||
Result SdmmcController::CalibrateDll() {
|
||||
/* Check if we need to temporarily re-enable the device clock. */
|
||||
const bool clock_disabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
|
||||
/* Ensure that the clock is enabled for the period we're using it. */
|
||||
if (clock_disabled) {
|
||||
/* Turn on the clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
}
|
||||
ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } };
|
||||
|
||||
/* Begin calibration. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE));
|
||||
|
||||
/* Wait up to 5ms for calibration to begin. */
|
||||
{
|
||||
ManualTimer timer(5);
|
||||
while (true) {
|
||||
/* If calibration is done, we're done. */
|
||||
if (!reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Otherwise, check if we've timed out. */
|
||||
R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllCalibrationSoftwareTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait up to 10ms for calibration to complete. */
|
||||
{
|
||||
ManualTimer timer(10);
|
||||
while (true) {
|
||||
/* If calibration is done, we're done. */
|
||||
if (reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg_sta, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, DONE))) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Otherwise, check if we've timed out. */
|
||||
R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllApplicationSoftwareTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SdmmcController::SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value) {
|
||||
/* Check if we need to temporarily disable the device clock. */
|
||||
const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
|
||||
/* Ensure that the clock is disabled for the period we're using it. */
|
||||
if (clock_enabled) {
|
||||
/* Turn off the clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
}
|
||||
|
||||
/* Set clock trimmer. */
|
||||
/* NOTE: Nintendo does not re-enable the clock if this fails... */
|
||||
R_TRY(this->SetClockTrimmer(speed_mode, tap_value));
|
||||
|
||||
/* Configure for the desired speed mode. */
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_MmcIdentification:
|
||||
case SpeedMode_SdCardIdentification:
|
||||
case SpeedMode_MmcLegacySpeed:
|
||||
case SpeedMode_SdCardDefaultSpeed:
|
||||
/* Set as normal speed, 3.3V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING));
|
||||
break;
|
||||
case SpeedMode_MmcHighSpeed:
|
||||
case SpeedMode_SdCardHighSpeed:
|
||||
/* Set as high speed, 3.3V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING));
|
||||
break;
|
||||
case SpeedMode_MmcHs200:
|
||||
/* Set as HS200, 1.8V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING));
|
||||
break;
|
||||
case SpeedMode_MmcHs400:
|
||||
/* Set as HS400, 1.8V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING));
|
||||
break;
|
||||
case SpeedMode_SdCardSdr12:
|
||||
/* Set as SDR12, 1.8V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING));
|
||||
break;
|
||||
case SpeedMode_SdCardSdr50:
|
||||
case SpeedMode_SdCardSdr104:
|
||||
case SpeedMode_GcAsicFpgaSpeed:
|
||||
case SpeedMode_GcAsicSpeed:
|
||||
/* Set as SDR104, 1.8V. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104));
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING));
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
SdHostStandardController::EnsureControl();
|
||||
|
||||
/* Get the divider setting. */
|
||||
u32 target_source_clock_frequency_khz;
|
||||
u16 x;
|
||||
GetDividerSetting(std::addressof(target_source_clock_frequency_khz), std::addressof(x), speed_mode);
|
||||
|
||||
/* Set the clock frequency. */
|
||||
u32 actual_source_clock_frequency_khz;
|
||||
ClockResetController::SetClockFrequencyKHz(std::addressof(actual_source_clock_frequency_khz), this->GetClockResetModule(), target_source_clock_frequency_khz);
|
||||
|
||||
/* Set the device clock frequency. */
|
||||
const u32 actual_device_clock_frequency_khz = util::DivideUp(actual_source_clock_frequency_khz, x);
|
||||
SdHostStandardController::SetDeviceClockFrequencyKHz(actual_device_clock_frequency_khz);
|
||||
|
||||
/* Check that the divider is correct. */
|
||||
AMS_ABORT_UNLESS((x == 1) || util::IsAligned(x, 2));
|
||||
|
||||
/* Write the divider val to clock control. */
|
||||
const u16 n = x / 2;
|
||||
const u16 upper_n = n >> 8;
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_VALUE(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, n),
|
||||
SD_REG_BITS_VALUE(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, upper_n));
|
||||
|
||||
/* Re-enable the clock, if we should. */
|
||||
if (clock_enabled) {
|
||||
/* Turn on the clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
}
|
||||
|
||||
/* If speed mode is Hs400, calibrate dll. */
|
||||
if (speed_mode == SpeedMode_MmcHs400) {
|
||||
R_TRY(this->CalibrateDll());
|
||||
}
|
||||
|
||||
/* Set the current speed mode. */
|
||||
this->current_speed_mode = speed_mode;
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SdmmcController::IssueTuningCommand(u32 command_index) {
|
||||
/* Check that we're not power saving enable. */
|
||||
AMS_ABORT_UNLESS(!SdHostStandardController::IsPowerSavingEnable());
|
||||
|
||||
/* Wait until command inhibit is done. */
|
||||
R_TRY(SdHostStandardController::WaitWhileCommandInhibit(true));
|
||||
|
||||
/* Set transfer for tuning. */
|
||||
SdHostStandardController::SetTransferForTuning();
|
||||
|
||||
/* If necessary, clear interrupt and enable buffer read ready signal. */
|
||||
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
||||
{
|
||||
this->ClearInterrupt();
|
||||
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Set the buffer read ready enable, and read status to ensure it takes. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
|
||||
reg::Read(this->sdmmc_registers->sd_host_standard_registers.normal_int_status);
|
||||
|
||||
/* Issue command with clock disabled. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
{
|
||||
SdHostStandardController::SetCommandForTuning(command_index);
|
||||
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitMicroSeconds(1);
|
||||
SdHostStandardController::AbortTransaction();
|
||||
}
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
|
||||
/* When we're done waiting, ensure that we clean up appropriately. */
|
||||
ON_SCOPE_EXIT {
|
||||
/* Clear the buffer read ready signal, if we should. */
|
||||
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED));
|
||||
#endif
|
||||
|
||||
/* Clear the buffer read ready enable. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED));
|
||||
|
||||
/* Wait 8 clocks to ensure configuration takes. */
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitClocks(8, SdHostStandardController::GetDeviceClockFrequencyKHz());
|
||||
};
|
||||
|
||||
/* Wait for the command to finish. */
|
||||
#if defined(AMS_SDMMC_USE_OS_EVENTS)
|
||||
{
|
||||
const auto result = SdHostStandardController::WaitInterrupt(TuningCommandTimeoutMilliSeconds);
|
||||
if (R_SUCCEEDED(result)) {
|
||||
/* If we succeeded, clear the interrupt. */
|
||||
reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
|
||||
this->ClearInterrupt();
|
||||
return ResultSuccess();
|
||||
} else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) {
|
||||
SdHostStandardController::AbortTransaction();
|
||||
return sdmmc::ResultIssueTuningCommandSoftwareTimeout();
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#else
|
||||
{
|
||||
SdHostStandardController::EnsureControl();
|
||||
ManualTimer timer(TuningCommandTimeoutMilliSeconds);
|
||||
while (true) {
|
||||
/* Check if we received the interrupt. */
|
||||
if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED))) {
|
||||
/* If we did, acknowledge it. */
|
||||
reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
/* Otherwise, check if we timed out. */
|
||||
if (!timer.Update()) {
|
||||
SdHostStandardController::AbortTransaction();
|
||||
return sdmmc::ResultIssueTuningCommandSoftwareTimeout();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void SdmmcController::SetDriveCodeOffsets(BusPower bus_power) {
|
||||
/* Get the offsets. */
|
||||
u8 pd, pu;
|
||||
this->GetAutoCalOffsets(std::addressof(pd), std::addressof(pu), bus_power);
|
||||
|
||||
/* Set the offsets. */
|
||||
reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd),
|
||||
SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu));
|
||||
}
|
||||
|
||||
void SdmmcController::CalibrateDriveStrength(BusPower bus_power) {
|
||||
/* Reset drive strength calibration status. */
|
||||
this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted();
|
||||
|
||||
/* Check if we need to temporarily disable the device clock. */
|
||||
const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
|
||||
/* Ensure that the clock is disabled for the period we're using it. */
|
||||
if (clock_enabled) {
|
||||
/* Turn off the clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
}
|
||||
|
||||
/* Calibrate with the clock disabled. */
|
||||
{
|
||||
/* Set SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */
|
||||
if (reg::HasValue(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) {
|
||||
reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 1));
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitMicroSeconds(1);
|
||||
}
|
||||
|
||||
/* Calibrate with SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD set. */
|
||||
{
|
||||
/* Begin autocal. */
|
||||
reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, ENABLED),
|
||||
SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, ENABLED));
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitMicroSeconds(2);
|
||||
|
||||
/* Wait up to 10ms for auto cal to complete. */
|
||||
ManualTimer timer(10);
|
||||
while (true) {
|
||||
/* Check if auto cal is inactive. */
|
||||
if (reg::HasValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, INACTIVE))) {
|
||||
/* Check the pullup status. */
|
||||
const u32 pullup = (reg::GetValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F;
|
||||
if (pullup == 0x1F) {
|
||||
this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd();
|
||||
}
|
||||
if (pullup == 0) {
|
||||
this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Otherwise, check if we've timed out. */
|
||||
if (!timer.Update()) {
|
||||
this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout();
|
||||
|
||||
this->SetDriveStrengthToDefaultValues(bus_power);
|
||||
reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, DISABLED));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Clear SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0));
|
||||
}
|
||||
|
||||
/* Re-enable the clock, if we should. */
|
||||
if (clock_enabled) {
|
||||
/* Turn on the clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
|
||||
}
|
||||
|
||||
/* If calibration didn't receive a replacement error, set internal state to success. */
|
||||
if (sdmmc::ResultDriveStrengthCalibrationNotCompleted::Includes(this->drive_strength_calibration_status)) {
|
||||
this->drive_strength_calibration_status = ResultSuccess();
|
||||
}
|
||||
}
|
||||
|
||||
Result SdmmcController::Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) {
|
||||
/* Verify that we're awake. */
|
||||
AMS_ABORT_UNLESS(this->is_awake);
|
||||
|
||||
/* Release the controller from reset. */
|
||||
this->ReleaseReset(speed_mode);
|
||||
|
||||
/* Mark that we're not shutdown. */
|
||||
this->is_shutdown = false;
|
||||
|
||||
/* Power on the controller. */
|
||||
R_TRY(this->PowerOn(bus_power));
|
||||
|
||||
/* Start up for the specific power. */
|
||||
R_TRY(this->StartupCore(bus_power));
|
||||
|
||||
/* Set our current power/width/speed. */
|
||||
SdHostStandardController::SetBusWidth(bus_width);
|
||||
SdHostStandardController::SetBusPower(bus_power);
|
||||
R_TRY(this->SetSpeedMode(speed_mode));
|
||||
this->SetPowerSaving(power_saving_enable);
|
||||
|
||||
/* Enable clock to the device. */
|
||||
SdHostStandardController::EnableDeviceClock();
|
||||
|
||||
/* Ensure that we can control the device. */
|
||||
SdHostStandardController::EnsureControl();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void SdmmcController::Shutdown() {
|
||||
/* If we're already shut down, there's nothing to do. */
|
||||
if (this->is_shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we're currently awake, we need to disable clock/power. */
|
||||
if (this->is_awake) {
|
||||
SdHostStandardController::DisableDeviceClock();
|
||||
SdHostStandardController::SetBusPower(BusPower_Off);
|
||||
SdHostStandardController::EnsureControl();
|
||||
}
|
||||
|
||||
/* Power off. */
|
||||
this->PowerOff();
|
||||
|
||||
/* If awake, assert reset. */
|
||||
if (this->is_awake) {
|
||||
this->AssertReset();
|
||||
}
|
||||
|
||||
/* Mark that we're shutdown. */
|
||||
this->is_shutdown = true;
|
||||
}
|
||||
|
||||
void SdmmcController::PutToSleep() {
|
||||
/* If we're already shut down or asleep, there's nothing to do. */
|
||||
if (this->is_shutdown || !this->is_awake) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Save values before sleep. */
|
||||
this->bus_power_before_sleep = SdHostStandardController::GetBusPower();
|
||||
this->bus_width_before_sleep = SdHostStandardController::GetBusWidth();
|
||||
this->speed_mode_before_sleep = this->current_speed_mode;
|
||||
this->tap_value_before_sleep = this->GetCurrentTapValue();
|
||||
this->is_powersaving_enable_before_sleep = SdHostStandardController::IsPowerSavingEnable();
|
||||
|
||||
/* Disable clock/power to the device. */
|
||||
SdHostStandardController::DisableDeviceClock();
|
||||
SdHostStandardController::SetBusPower(BusPower_Off);
|
||||
SdHostStandardController::EnsureControl();
|
||||
|
||||
/* Assert reset. */
|
||||
this->AssertReset();
|
||||
|
||||
/* Mark that we're asleep. */
|
||||
this->is_awake = false;
|
||||
}
|
||||
|
||||
Result SdmmcController::Awaken() {
|
||||
/* If we're shut down, or if we're awake already, there's nothing to do. */
|
||||
R_SUCCEED_IF(this->is_shutdown);
|
||||
R_SUCCEED_IF(this->is_awake);
|
||||
|
||||
/* Mark that we're awake. */
|
||||
this->is_awake = true;
|
||||
|
||||
/* Clear pad parked status. */
|
||||
this->ClearPadParked();
|
||||
|
||||
/* Release reset. */
|
||||
this->ReleaseReset(this->speed_mode_before_sleep);
|
||||
|
||||
/* Start up for the correct power. */
|
||||
R_TRY(this->StartupCore(this->bus_power_before_sleep));
|
||||
|
||||
/* Configure values to what they were before sleep. */
|
||||
SdHostStandardController::SetBusWidth(this->bus_width_before_sleep);
|
||||
SdHostStandardController::SetBusPower(this->bus_power_before_sleep);
|
||||
R_TRY(this->SetSpeedModeWithTapValue(this->speed_mode_before_sleep, this->tap_value_before_sleep));
|
||||
this->SetPowerSaving(this->is_powersaving_enable_before_sleep);
|
||||
|
||||
/* Enable clock to the device. */
|
||||
SdHostStandardController::EnableDeviceClock();
|
||||
SdHostStandardController::EnsureControl();
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SdmmcController::SwitchToSdr12() {
|
||||
/* Disable clock. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
|
||||
/* Check that the dat lines are all low. */
|
||||
R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch());
|
||||
|
||||
/* Set voltage to 1.8V. */
|
||||
SdHostStandardController::EnsureControl();
|
||||
R_TRY(this->LowerBusPower());
|
||||
this->SetSchmittTrigger(BusPower_1_8V);
|
||||
|
||||
/* Perform drive strength calibration at the new power. */
|
||||
this->SetDriveCodeOffsets(BusPower_1_8V);
|
||||
this->CalibrateDriveStrength(BusPower_1_8V);
|
||||
|
||||
/* Set the bus power in standard controller. */
|
||||
SdHostStandardController::SetBusPower(BusPower_1_8V);
|
||||
|
||||
/* Wait up to 5ms for the switch to take. */
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitMicroSeconds(5000);
|
||||
|
||||
/* Check that we switched to 1.8V. */
|
||||
R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)), sdmmc::ResultSdHostStandardFailSwitchTo1_8V());
|
||||
|
||||
/* Enable clock, and wait 1ms. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE));
|
||||
SdHostStandardController::EnsureControl();
|
||||
WaitMicroSeconds(1000);
|
||||
|
||||
/* Check that the dat lines are all high. */
|
||||
R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) {
|
||||
/* Get the tap value. */
|
||||
u8 tap_value;
|
||||
if (speed_mode == SpeedMode_MmcHs400) {
|
||||
AMS_ABORT_UNLESS(this->is_valid_tap_value_for_hs_400);
|
||||
tap_value = this->tap_value_for_hs_400;
|
||||
} else {
|
||||
tap_value = this->GetDefaultInboundTapValue();
|
||||
}
|
||||
|
||||
/* Set the speed mode. */
|
||||
R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value));
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void SdmmcController::SetPowerSaving(bool en) {
|
||||
/* If necessary, calibrate the drive strength. */
|
||||
if (this->IsNeedPeriodicDriveStrengthCalibration() && !en && SdHostStandardController::IsDeviceClockEnable()) {
|
||||
this->CalibrateDriveStrength(SdHostStandardController::GetBusPower());
|
||||
}
|
||||
|
||||
return SdHostStandardController::SetPowerSaving(en);
|
||||
}
|
||||
|
||||
void SdmmcController::EnableDeviceClock() {
|
||||
/* If necessary, calibrate the drive strength. */
|
||||
if (this->IsNeedPeriodicDriveStrengthCalibration() && !SdHostStandardController::IsPowerSavingEnable()) {
|
||||
this->CalibrateDriveStrength(SdHostStandardController::GetBusPower());
|
||||
}
|
||||
|
||||
return SdHostStandardController::EnableDeviceClock();
|
||||
}
|
||||
|
||||
Result SdmmcController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) {
|
||||
/* If necessary, calibrate the drive strength. */
|
||||
if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) {
|
||||
this->CalibrateDriveStrength(SdHostStandardController::GetBusPower());
|
||||
}
|
||||
|
||||
return SdHostStandardController::IssueCommand(command, xfer_data, out_num_transferred_blocks);
|
||||
}
|
||||
|
||||
Result SdmmcController::IssueStopTransmissionCommand(u32 *out_response) {
|
||||
/* If necessary, calibrate the drive strength. */
|
||||
if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) {
|
||||
this->CalibrateDriveStrength(SdHostStandardController::GetBusPower());
|
||||
}
|
||||
|
||||
return SdHostStandardController::IssueStopTransmissionCommand(out_response);
|
||||
}
|
||||
|
||||
Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) {
|
||||
/* Clear vendor tuning control 1. */
|
||||
reg::Write(this->sdmmc_registers->vendor_tuning_cntrl1, 0);
|
||||
|
||||
/* Determine/configure the number of tries. */
|
||||
int num_tries;
|
||||
switch (speed_mode) {
|
||||
case SpeedMode_MmcHs200:
|
||||
case SpeedMode_MmcHs400:
|
||||
case SpeedMode_SdCardSdr104:
|
||||
num_tries = 128;
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_128));
|
||||
break;
|
||||
case SpeedMode_SdCardSdr50:
|
||||
case SpeedMode_GcAsicFpgaSpeed:
|
||||
case SpeedMode_GcAsicSpeed:
|
||||
num_tries = 256;
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_256));
|
||||
break;
|
||||
AMS_UNREACHABLE_DEFAULT_CASE();
|
||||
}
|
||||
|
||||
/* Configure the multiplier. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_VALUE(VENDOR_TUNING_CNTRL0_MUL_M, 1));
|
||||
|
||||
/* Configure tap value to be updated by hardware. */
|
||||
reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, UPDATED_BY_HW));
|
||||
|
||||
/* Configure to execute tuning. */
|
||||
reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING));
|
||||
|
||||
/* Perform tuning num_tries times. */
|
||||
for (int i = 0; /* ... */; ++i) {
|
||||
/* Check if we've been removed. */
|
||||
R_TRY(this->CheckRemoved());
|
||||
|
||||
/* Issue the command. */
|
||||
this->IssueTuningCommand(command_index);
|
||||
|
||||
/* Check if tuning is done. */
|
||||
if (i >= num_tries) {
|
||||
break;
|
||||
}
|
||||
++i;
|
||||
|
||||
if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, TUNING_COMPLETED))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if we're using the tuned clock. */
|
||||
R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed());
|
||||
|
||||
return ResultSuccess();
|
||||
}
|
||||
|
||||
void SdmmcController::SaveTuningStatusForHs400() {
|
||||
/* Save the current tap value. */
|
||||
this->tap_value_for_hs_400 = GetCurrentTapValue();
|
||||
this->is_valid_tap_value_for_hs_400 = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -24,6 +24,9 @@ namespace ams::sdmmc::impl {
|
|||
|
||||
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 {
|
||||
|
@ -72,37 +75,303 @@ namespace ams::sdmmc::impl {
|
|||
static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize);
|
||||
private:
|
||||
SdmmcRegisters *sdmmc_registers;
|
||||
/* TODO */
|
||||
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;
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
class Sdmmc2And4Controller : public SdmmcController {
|
||||
/* TODO */
|
||||
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) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
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 Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600);
|
||||
|
||||
class Sdmmc4Controller : public Sdmmc2And4Controller {
|
||||
/* TODO */
|
||||
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) {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
/* TODO */
|
||||
};
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue