mirror of
				https://github.com/Atmosphere-NX/Atmosphere.git
				synced 2025-10-25 01:35:51 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1312 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1312 lines
		
	
	
		
			55 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) Atmosphère-NX
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify it
 | |
|  * under the terms and conditions of the GNU General Public License,
 | |
|  * version 2, as published by the Free Software Foundation.
 | |
|  *
 | |
|  * This program is distributed in the hope it will be useful, but WITHOUT
 | |
|  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | |
|  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | |
|  * more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU General Public License
 | |
|  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| #if defined(ATMOSPHERE_IS_STRATOSPHERE)
 | |
| #include <stratosphere.hpp>
 | |
| #elif defined(ATMOSPHERE_IS_MESOSPHERE)
 | |
| #include <mesosphere.hpp>
 | |
| #elif defined(ATMOSPHERE_IS_EXOSPHERE)
 | |
| #include <exosphere.hpp>
 | |
| #else
 | |
| #include <vapours.hpp>
 | |
| #endif
 | |
| #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp"
 | |
| #include "sdmmc_io_impl.board.nintendo_nx.hpp"
 | |
| #include "sdmmc_timer.hpp"
 | |
| 
 | |
| namespace ams::sdmmc::impl {
 | |
| 
 | |
|     /* FOR REFERENCE: board-specific sdmmc registers. */
 | |
|     //struct SdmmcRegisters {
 | |
|     //    /* Standard registers. */
 | |
|     //    volatile SdHostStandardRegisters sd_host_standard_registers;
 | |
|     //
 | |
|     //    /* Vendor specific registers */
 | |
|     //    volatile uint32_t vendor_clock_cntrl;
 | |
|     //    volatile uint32_t vendor_sys_sw_cntrl;
 | |
|     //    volatile uint32_t vendor_err_intr_status;
 | |
|     //    volatile uint32_t vendor_cap_overrides;
 | |
|     //    volatile uint32_t vendor_boot_cntrl;
 | |
|     //    volatile uint32_t vendor_boot_ack_timeout;
 | |
|     //    volatile uint32_t vendor_boot_dat_timeout;
 | |
|     //    volatile uint32_t vendor_debounce_count;
 | |
|     //    volatile uint32_t vendor_misc_cntrl;
 | |
|     //    volatile uint32_t max_current_override;
 | |
|     //    volatile uint32_t max_current_override_hi;
 | |
|     //    volatile uint32_t _0x12c[0x20];
 | |
|     //    volatile uint32_t vendor_io_trim_cntrl;
 | |
|     //
 | |
|     //    /* Start of sdmmc2/sdmmc4 only */
 | |
|     //    volatile uint32_t vendor_dllcal_cfg;
 | |
|     //    volatile uint32_t vendor_dll_ctrl0;
 | |
|     //    volatile uint32_t vendor_dll_ctrl1;
 | |
|     //    volatile uint32_t vendor_dllcal_cfg_sta;
 | |
|     //    /* End of sdmmc2/sdmmc4 only */
 | |
|     //
 | |
|     //    volatile uint32_t vendor_tuning_cntrl0;
 | |
|     //    volatile uint32_t vendor_tuning_cntrl1;
 | |
|     //    volatile uint32_t vendor_tuning_status0;
 | |
|     //    volatile uint32_t vendor_tuning_status1;
 | |
|     //    volatile uint32_t vendor_clk_gate_hysteresis_count;
 | |
|     //    volatile uint32_t vendor_preset_val0;
 | |
|     //    volatile uint32_t vendor_preset_val1;
 | |
|     //    volatile uint32_t vendor_preset_val2;
 | |
|     //    volatile uint32_t sdmemcomppadctrl;
 | |
|     //    volatile uint32_t auto_cal_config;
 | |
|     //    volatile uint32_t auto_cal_interval;
 | |
|     //    volatile uint32_t auto_cal_status;
 | |
|     //    volatile uint32_t io_spare;
 | |
|     //    volatile uint32_t sdmmca_mccif_fifoctrl;
 | |
|     //    volatile uint32_t timeout_wcoal_sdmmca;
 | |
|     //    volatile uint32_t _0x1fc;
 | |
|     //};
 | |
| 
 | |
|     DEFINE_SD_REG_BIT_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, 2, NORMAL, OVERRIDE);
 | |
|     DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TAP_VAL,  16, 8);
 | |
|     DEFINE_SD_REG(VENDOR_CLOCK_CNTRL_TRIM_VAL, 24, 5);
 | |
| 
 | |
|     DEFINE_SD_REG(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 8, 6);
 | |
| 
 | |
|     DEFINE_SD_REG(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 2, 1);
 | |
| 
 | |
|     DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, 31, DISABLE, ENABLE);
 | |
| 
 | |
|     DEFINE_SD_REG_BIT_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, 31, DONE, RUNNING);
 | |
| 
 | |
|     DEFINE_SD_REG(VENDOR_TUNING_CNTRL0_MUL_M, 6, 7);
 | |
|     DEFINE_SD_REG_THREE_BIT_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, 13, TRIES_40, TRIES_64, TRIES_128, TRIES_192, TRIES_256, RESERVED5, RESERVED6, RESERVED7);
 | |
|     DEFINE_SD_REG_BIT_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, 17, NOT_UPDATED_BY_HW, UPDATED_BY_HW);
 | |
| 
 | |
|     DEFINE_SD_REG(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL,  0, 4);
 | |
|     DEFINE_SD_REG(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD,            31, 1);
 | |
| 
 | |
|     DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, 0, 7);
 | |
|     DEFINE_SD_REG(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, 8, 7);
 | |
|     DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, 29, DISABLED, ENABLED);
 | |
|     DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START,  31, DISABLED, ENABLED);
 | |
| 
 | |
|     DEFINE_SD_REG(AUTO_CAL_STATUS_AUTO_CAL_PULLUP, 0, 7);
 | |
|     DEFINE_SD_REG_BIT_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, 31, INACTIVE, ACTIVE);
 | |
| 
 | |
|     DEFINE_SD_REG_BIT_ENUM(IO_SPARE_SPARE_OUT_3, 19, TWO_CYCLE_DELAY, ONE_CYCLE_DELAY);
 | |
| 
 | |
|     namespace {
 | |
| 
 | |
|         constexpr inline u32 TuningCommandTimeoutMilliSeconds = 5;
 | |
| 
 | |
|         constexpr void GetDividerSetting(u32 *out_target_clock_frequency_khz, u16 *out_x, SpeedMode speed_mode) {
 | |
|             switch (speed_mode) {
 | |
|                 case SpeedMode_MmcIdentification:
 | |
|                     *out_target_clock_frequency_khz = 26000;
 | |
|                     *out_x                          = 66;
 | |
|                     break;
 | |
|                 case SpeedMode_MmcLegacySpeed:
 | |
|                     *out_target_clock_frequency_khz = 26000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_MmcHighSpeed:
 | |
|                     *out_target_clock_frequency_khz = 52000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_MmcHs200:
 | |
|                     *out_target_clock_frequency_khz = 200000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_MmcHs400:
 | |
|                     *out_target_clock_frequency_khz = 200000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardIdentification:
 | |
|                     *out_target_clock_frequency_khz = 25000;
 | |
|                     *out_x                          = 64;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardDefaultSpeed:
 | |
|                     *out_target_clock_frequency_khz = 25000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardHighSpeed:
 | |
|                     *out_target_clock_frequency_khz = 50000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardSdr12:
 | |
|                     *out_target_clock_frequency_khz = 25000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardSdr50:
 | |
|                     *out_target_clock_frequency_khz = 100000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardSdr104:
 | |
|                     *out_target_clock_frequency_khz = 200000;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_GcAsicFpgaSpeed:
 | |
|                     *out_target_clock_frequency_khz = 40800;
 | |
|                     *out_x                          = 1;
 | |
|                     break;
 | |
|                 case SpeedMode_GcAsicSpeed:
 | |
|                     *out_target_clock_frequency_khz = 200000;
 | |
|                     *out_x                          = 2;
 | |
|                     break;
 | |
|                 case SpeedMode_SdCardSdr25:
 | |
|                 case SpeedMode_SdCardDdr50:
 | |
|                 AMS_UNREACHABLE_DEFAULT_CASE();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|     }
 | |
| 
 | |
|     namespace {
 | |
| 
 | |
|         #if defined(AMS_SDMMC_THREAD_SAFE)
 | |
|             constinit os::SdkMutex g_soc_mutex;
 | |
| 
 | |
|             #define AMS_SDMMC_LOCK_SOC_MUTEX() std::scoped_lock lk(g_soc_mutex)
 | |
| 
 | |
|         #else
 | |
| 
 | |
|             #define AMS_SDMMC_LOCK_SOC_MUTEX()
 | |
| 
 | |
|         #endif
 | |
| 
 | |
|         constinit bool g_determined_soc = false;
 | |
|         constinit bool g_is_soc_mariko  = false;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     bool IsSocMariko() {
 | |
|         if (!g_determined_soc) {
 | |
|             /* Ensure we have exclusive access to the soc variables. */
 | |
|             AMS_SDMMC_LOCK_SOC_MUTEX();
 | |
| 
 | |
|             /* Check the SocType. */
 | |
|             #if defined(ATMOSPHERE_IS_EXOSPHERE)
 | |
|             {
 | |
|                 g_is_soc_mariko = fuse::GetSocType() == fuse::SocType_Mariko;
 | |
|             }
 | |
|             #elif defined(ATMOSPHERE_IS_MESOSPHERE)
 | |
|             {
 | |
|                 MESOSPHERE_TODO("Detect mariko via KSystemControl call?");
 | |
|             }
 | |
|             #elif defined(ATMOSPHERE_IS_STRATOSPHERE)
 | |
|             {
 | |
|                 /* Connect to spl for the duration of our check. */
 | |
|                 spl::Initialize();
 | |
|                 ON_SCOPE_EXIT { spl::Finalize(); };
 | |
| 
 | |
|                 g_is_soc_mariko = spl::GetSocType() == spl::SocType_Mariko;
 | |
|             }
 | |
|             #else
 | |
|                 #error "Unknown execution context for ams::sdmmc::impl::IsSocMariko"
 | |
|             #endif
 | |
| 
 | |
|             /* Note that we determined the soc. */
 | |
|             g_determined_soc = true;
 | |
|         }
 | |
| 
 | |
|         return g_is_soc_mariko;
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::ReleaseReset(SpeedMode speed_mode) {
 | |
|         /* Get the clock reset module. */
 | |
|         const auto module = this->GetClockResetModule();
 | |
| 
 | |
|         /* If the module is available, disable clock. */
 | |
|         if (ClockResetController::IsAvailable(module)) {
 | |
|             SdHostStandardController::DisableDeviceClock();
 | |
|             SdHostStandardController::EnsureControl();
 | |
|         }
 | |
| 
 | |
|         /* Get the correct divider setting for the speed mode. */
 | |
|         u32 target_clock_frequency_khz;
 | |
|         u16 x;
 | |
|         GetDividerSetting(std::addressof(target_clock_frequency_khz), std::addressof(x), speed_mode);
 | |
| 
 | |
|         /* Release reset. */
 | |
|         ClockResetController::ReleaseReset(module, target_clock_frequency_khz);
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::AssertReset() {
 | |
|         return ClockResetController::AssertReset(this->GetClockResetModule());
 | |
|     }
 | |
| 
 | |
|     Result SdmmcController::StartupCore(BusPower bus_power) {
 | |
|         /* Set schmitt trigger. */
 | |
|         this->SetSchmittTrigger(bus_power);
 | |
| 
 | |
|         /* Select one-cycle delay version of cmd_oen. */
 | |
|         reg::ReadWrite(m_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(m_sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0));
 | |
| 
 | |
|         /* Configure outbound tap value. */
 | |
|         reg::ReadWrite(m_sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue()));
 | |
| 
 | |
|         /* Configure SPI_MODE_CLKEN_OVERRIDE. */
 | |
|         reg::ReadWrite(m_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(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetVrefSelValue()));
 | |
| 
 | |
|         /* Perform drive strength calibration at the new power. */
 | |
|         this->SetDriveCodeOffsets(bus_power);
 | |
|         this->CalibrateDriveStrength(bus_power);
 | |
| 
 | |
|         /* Enable internal clock. */
 | |
|         R_TRY(SdHostStandardController::EnableInternalClock());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     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(m_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(m_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(m_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());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     u8 SdmmcController::GetCurrentTapValue() {
 | |
|         return static_cast<u8>(reg::GetValue(m_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(m_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(m_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(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } };
 | |
| 
 | |
|         /* Begin calibration. */
 | |
|         reg::ReadWrite(m_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(m_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(m_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());
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     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(m_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(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control,  SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED));
 | |
|                 reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control,  SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED));
 | |
|                 reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200));
 | |
|                 reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400));
 | |
|                 reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12));
 | |
|                 reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104));
 | |
|                 reg::ReadWrite(m_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(m_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(m_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. */
 | |
|         m_current_speed_mode = speed_mode;
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     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(m_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(m_sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
 | |
|         reg::Write(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, reg::Read(m_sdmmc_registers->sd_host_standard_registers.normal_int_status));
 | |
| 
 | |
|         /* Issue command with clock disabled. */
 | |
|         reg::ReadWrite(m_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(m_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(m_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(m_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(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
 | |
|                 this->ClearInterrupt();
 | |
|                 R_SUCCEED();
 | |
|             } else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) {
 | |
|                 SdHostStandardController::AbortTransaction();
 | |
|                 R_THROW(sdmmc::ResultIssueTuningCommandSoftwareTimeout());
 | |
|             } else {
 | |
|                 R_RETURN(result);
 | |
|             }
 | |
|         }
 | |
|         #else
 | |
|         {
 | |
|             SdHostStandardController::EnsureControl();
 | |
|             ManualTimer timer(TuningCommandTimeoutMilliSeconds);
 | |
|             while (true) {
 | |
|                 /* Check if we received the interrupt. */
 | |
|                 if (reg::HasValue(m_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(m_sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED));
 | |
|                     R_SUCCEED();
 | |
|                 }
 | |
| 
 | |
|                 /* Otherwise, check if we timed out. */
 | |
|                 if (!timer.Update()) {
 | |
|                     SdHostStandardController::AbortTransaction();
 | |
|                     R_THROW(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(m_sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd),
 | |
|                                                                SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu));
 | |
| 
 | |
|         /* Wait for 1ms to ensure that our configuration takes. */
 | |
|         /* NOTE/HACK: Nintendo does not (need) to do this, but they use SvcSleepThread for waits. */
 | |
|         /* It's unclear why this wait is necessary, but not doing it causes drive strength calibration to fail if done immediately afterwards. */
 | |
|         util::WaitMicroSeconds(1000);
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::CalibrateDriveStrength(BusPower bus_power) {
 | |
|         /* Reset drive strength calibration status. */
 | |
|         m_drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted();
 | |
| 
 | |
|         /* Check if we need to temporarily disable the device clock. */
 | |
|         const bool clock_enabled = reg::HasValue(m_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(m_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(m_sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) {
 | |
|                 reg::ReadWrite(m_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(m_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(m_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(m_sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F;
 | |
|                         if (pullup == 0x1F) {
 | |
|                             m_drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd();
 | |
|                         }
 | |
|                         if (pullup == 0) {
 | |
|                             m_drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen();
 | |
|                         }
 | |
| 
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     /* Otherwise, check if we've timed out. */
 | |
|                     if (!timer.Update()) {
 | |
|                         m_drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout();
 | |
| 
 | |
|                         this->SetDriveStrengthToDefaultValues(bus_power);
 | |
|                         reg::ReadWrite(m_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(m_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(m_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(m_drive_strength_calibration_status)) {
 | |
|             m_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(m_is_awake);
 | |
| 
 | |
|         /* Release the controller from reset. */
 | |
|         this->ReleaseReset(speed_mode);
 | |
| 
 | |
|         /* Mark that we're not shutdown. */
 | |
|         m_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();
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::Shutdown() {
 | |
|         /* If we're already shut down, there's nothing to do. */
 | |
|         if (m_is_shutdown) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         /* If we're currently awake, we need to disable clock/power. */
 | |
|         if (m_is_awake) {
 | |
|             SdHostStandardController::DisableDeviceClock();
 | |
|             SdHostStandardController::SetBusPower(BusPower_Off);
 | |
|             SdHostStandardController::EnsureControl();
 | |
|         }
 | |
| 
 | |
|         /* Power off. */
 | |
|         this->PowerOff();
 | |
| 
 | |
|         /* If awake, assert reset. */
 | |
|         if (m_is_awake) {
 | |
|             this->AssertReset();
 | |
|         }
 | |
| 
 | |
|         /* Mark that we're shutdown. */
 | |
|         m_is_shutdown = true;
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::PutToSleep() {
 | |
|         /* If we're already shut down or asleep, there's nothing to do. */
 | |
|         if (m_is_shutdown || !m_is_awake) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         /* Save values before sleep. */
 | |
|         m_bus_power_before_sleep             = SdHostStandardController::GetBusPower();
 | |
|         m_bus_width_before_sleep             = SdHostStandardController::GetBusWidth();
 | |
|         m_speed_mode_before_sleep            = m_current_speed_mode;
 | |
|         m_tap_value_before_sleep             = this->GetCurrentTapValue();
 | |
|         m_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. */
 | |
|         m_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(m_is_shutdown);
 | |
|         R_SUCCEED_IF(m_is_awake);
 | |
| 
 | |
|         /* Mark that we're awake. */
 | |
|         m_is_awake = true;
 | |
| 
 | |
|         /* Clear pad parked status. */
 | |
|         this->ClearPadParked();
 | |
| 
 | |
|         /* Release reset. */
 | |
|         this->ReleaseReset(m_speed_mode_before_sleep);
 | |
| 
 | |
|         /* Start up for the correct power. */
 | |
|         R_TRY(this->StartupCore(m_bus_power_before_sleep));
 | |
| 
 | |
|         /* Configure values to what they were before sleep. */
 | |
|         SdHostStandardController::SetBusWidth(m_bus_width_before_sleep);
 | |
|         SdHostStandardController::SetBusPower(m_bus_power_before_sleep);
 | |
|         R_TRY(this->SetSpeedModeWithTapValue(m_speed_mode_before_sleep, m_tap_value_before_sleep));
 | |
|         this->SetPowerSaving(m_is_powersaving_enable_before_sleep);
 | |
| 
 | |
|         /* Enable clock to the device. */
 | |
|         SdHostStandardController::EnableDeviceClock();
 | |
|         SdHostStandardController::EnsureControl();
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     Result SdmmcController::SwitchToSdr12() {
 | |
|         /* Disable clock. */
 | |
|         reg::ReadWrite(m_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(m_sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch());
 | |
| 
 | |
|         /* Set Speed Mode. */
 | |
|         R_TRY(this->SetSpeedMode(SpeedMode_SdCardSdr12));
 | |
| 
 | |
|         /* Set voltage to 1.8V. */
 | |
|         SdHostStandardController::EnsureControl();
 | |
|         R_TRY(this->LowerBusPower());
 | |
|         this->SetSchmittTrigger(BusPower_1_8V);
 | |
| 
 | |
|         /* Perform drive strength calibration at the new power. */
 | |
|         this->SetDriveCodeOffsets(BusPower_1_8V);
 | |
|         this->CalibrateDriveStrength(BusPower_1_8V);
 | |
| 
 | |
|         /* Set the bus power in standard controller. */
 | |
|         SdHostStandardController::SetBusPower(BusPower_1_8V);
 | |
| 
 | |
|         /* Wait up to 5ms for the switch to take. */
 | |
|         SdHostStandardController::EnsureControl();
 | |
|         WaitMicroSeconds(5000);
 | |
| 
 | |
|         /* Check that we switched to 1.8V. */
 | |
|         R_UNLESS(reg::HasValue(m_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(m_sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE));
 | |
|         SdHostStandardController::EnsureControl();
 | |
|         WaitMicroSeconds(1000);
 | |
| 
 | |
|         /* Check that the dat lines are all high. */
 | |
|         R_UNLESS(reg::HasValue(m_sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) {
 | |
|         /* Get the tap value. */
 | |
|         u8 tap_value;
 | |
|         if (speed_mode == SpeedMode_MmcHs400) {
 | |
|             AMS_ABORT_UNLESS(m_is_valid_tap_value_for_hs_400);
 | |
|             tap_value = m_tap_value_for_hs_400;
 | |
|         } else {
 | |
|             tap_value = this->GetDefaultInboundTapValue();
 | |
|         }
 | |
| 
 | |
|         /* Set the speed mode. */
 | |
|         R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value));
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     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());
 | |
|         }
 | |
| 
 | |
|         R_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());
 | |
|         }
 | |
| 
 | |
|         R_RETURN(SdHostStandardController::IssueStopTransmissionCommand(out_response));
 | |
|     }
 | |
| 
 | |
|     Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) {
 | |
|         /* Clear vendor tuning control 1. */
 | |
|         reg::Write(m_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(m_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(m_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(m_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(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING));
 | |
| 
 | |
|         /* Perform tuning num_tries times. */
 | |
|         for (int i = 0; /* ... */; ++i) {
 | |
|             /* Check if we've been removed. */
 | |
|             R_TRY(this->CheckRemoved());
 | |
| 
 | |
|             /* Issue the command. */
 | |
|             this->IssueTuningCommand(command_index);
 | |
| 
 | |
|             /* Check if tuning is done. */
 | |
|             if (i >= num_tries) {
 | |
|                 break;
 | |
|             }
 | |
| 
 | |
|             if (reg::HasValue(m_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(m_sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void SdmmcController::SaveTuningStatusForHs400() {
 | |
|         /* Save the current tap value. */
 | |
|         m_tap_value_for_hs_400          = GetCurrentTapValue();
 | |
|         m_is_valid_tap_value_for_hs_400 = true;
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerOnForRegisterControl(BusPower bus_power) {
 | |
|         AMS_ABORT_UNLESS(bus_power == BusPower_3_3V);
 | |
| 
 | |
|         /* Nintendo sets the current bus power regardless of whether the call succeeds. */
 | |
|         ON_SCOPE_EXIT { m_current_bus_power = BusPower_3_3V; };
 | |
| 
 | |
|         /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */
 | |
|         R_TRY(m_power_controller->PowerOn(BusPower_3_3V));
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::PowerOffForRegisterControl() {
 | |
|         /* If we're already off, there's nothing to do. */
 | |
|         if (m_current_bus_power == BusPower_Off) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         /* If we're at 3.3V, lower to 1.8V. */
 | |
|         if (m_current_bus_power == BusPower_3_3V) {
 | |
|             /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
 | |
|             m_power_controller->LowerBusPower();
 | |
| 
 | |
|             /* Set our bus power. */
 | |
|             m_current_bus_power = BusPower_1_8V;
 | |
|         }
 | |
| 
 | |
|         /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
 | |
|         pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh);
 | |
| 
 | |
| 
 | |
|         /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
 | |
|         m_power_controller->PowerOff();
 | |
| 
 | |
|         /* Set our bus power. */
 | |
|         m_current_bus_power = BusPower_Off;
 | |
| 
 | |
|         /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */
 | |
|         pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1ResetState);
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::LowerBusPowerForRegisterControl() {
 | |
|         /* Nintendo sets the current bus power regardless of whether the call succeeds. */
 | |
|         ON_SCOPE_EXIT { m_current_bus_power = BusPower_1_8V; };
 | |
| 
 | |
|         /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
 | |
|         R_TRY(m_power_controller->LowerBusPower());
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::SetSchmittTriggerForRegisterControl(BusPower bus_power) {
 | |
|         SdHostStandardController::EnsureControl();
 | |
| 
 | |
|         if (IsSocMariko()) {
 | |
|             /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
 | |
|             pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
 | |
|         } else {
 | |
|             switch (bus_power) {
 | |
|                 case BusPower_1_8V:
 | |
|                     /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
 | |
|                     pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
 | |
|                     break;
 | |
|                 case BusPower_3_3V:
 | |
|                     /* pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
 | |
|                     pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable);
 | |
|                     break;
 | |
|                 case BusPower_Off:
 | |
|                 AMS_UNREACHABLE_DEFAULT_CASE();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|     Result Sdmmc1Controller::PowerOnForPcvControl(BusPower bus_power) {
 | |
|         AMS_ABORT_UNLESS(bus_power == BusPower_3_3V);
 | |
| 
 | |
|         /* Nintendo sets the current bus power regardless of whether the call succeeds. */
 | |
|         ON_SCOPE_EXIT { m_current_bus_power = BusPower_3_3V; };
 | |
| 
 | |
|         /* TODO: R_RETURN(pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000)); */
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::PowerOffForPcvControl() {
 | |
|         /* If we're already off, there's nothing to do. */
 | |
|         if (m_current_bus_power == BusPower_Off) {
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         /* If we're at 3.3V, lower to 1.8V. */
 | |
|         {
 | |
|             /* TODO: pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
 | |
|             m_current_bus_power = BusPower_1_8V;
 | |
|         }
 | |
| 
 | |
|         /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
 | |
| 
 | |
|         /* TODO: pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
 | |
|         m_current_bus_power = BusPower_Off;
 | |
| 
 | |
|         /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */
 | |
| 
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::LowerBusPowerForPcvControl() {
 | |
|         /* Nintendo sets the current bus power regardless of whether the call succeeds. */
 | |
|         ON_SCOPE_EXIT { m_current_bus_power = BusPower_1_8V; };
 | |
| 
 | |
|         /* TODO: R_RETURN(pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000)); */
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::SetSchmittTriggerForPcvControl(BusPower bus_power) {
 | |
|         SdHostStandardController::EnsureControl();
 | |
| 
 | |
|         if (IsSocMariko()) {
 | |
|             /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
 | |
|         } else {
 | |
|             switch (bus_power) {
 | |
|                 case BusPower_1_8V:
 | |
|                     /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
 | |
|                     break;
 | |
|                 case BusPower_3_3V:
 | |
|                     /* TODO: pinmux::SetPinAssignment(std::addressof(m_pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
 | |
|                     break;
 | |
|                 case BusPower_Off:
 | |
|                 AMS_UNREACHABLE_DEFAULT_CASE();
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     #endif
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerOn(BusPower bus_power) {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         if (m_is_pcv_control) {
 | |
|             R_RETURN(this->PowerOnForPcvControl(bus_power));
 | |
|         } else
 | |
|         #endif
 | |
|         {
 | |
|             R_RETURN(this->PowerOnForRegisterControl(bus_power));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::PowerOff() {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         if (m_is_pcv_control) {
 | |
|             return this->PowerOffForPcvControl();
 | |
|         } else
 | |
|         #endif
 | |
|         {
 | |
|             return this->PowerOffForRegisterControl();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::LowerBusPower() {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         if (m_is_pcv_control) {
 | |
|             R_RETURN(this->LowerBusPowerForPcvControl());
 | |
|         } else
 | |
|         #endif
 | |
|         {
 | |
|             R_RETURN(this->LowerBusPowerForRegisterControl());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::SetSchmittTrigger(BusPower bus_power) {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         if (m_is_pcv_control) {
 | |
|             return this->SetSchmittTriggerForPcvControl(bus_power);
 | |
|         } else
 | |
|         #endif
 | |
|         {
 | |
|             return this->SetSchmittTriggerForRegisterControl(bus_power);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::Initialize() {
 | |
|         return this->InitializeForRegisterControl();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::Finalize() {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         if (m_is_pcv_control) {
 | |
|             return this->FinalizeForPcvControl();
 | |
|         } else
 | |
|         #endif
 | |
|         {
 | |
|             return this->FinalizeForRegisterControl();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::InitializeForRegisterControl() {
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         /* Mark ourselves as initialized by register control. */
 | |
|         m_is_pcv_control = false;
 | |
|         #endif
 | |
| 
 | |
|         /* pinmux::Initialize(); */
 | |
|         /* This just opens a session handle to pinmux service, no work to do. */
 | |
| 
 | |
|         /* pinmux::OpenSession(std::addressof(m_pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */
 | |
|         /* This just sets the session's internal value to the pin group name, so nothing to do here either. */
 | |
| 
 | |
|         /* pcv::Initialize(); */
 | |
|         /* This initializes a lot of globals in pcv, most of which we don't care about. */
 | |
|         /* However, we do care about the Sdmmc1PowerController. */
 | |
|         AMS_ABORT_UNLESS(m_power_controller == nullptr);
 | |
|         m_power_controller = util::ConstructAt(m_power_controller_storage);
 | |
| 
 | |
|         /* Perform base initialization. */
 | |
|         SdmmcController::Initialize();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::FinalizeForRegisterControl() {
 | |
|         /* Perform base finalization. */
 | |
|         SdmmcController::Finalize();
 | |
| 
 | |
|         /* pcv::Finalize(); */
 | |
|         /* As with initialize, we mostly don't care about the globals this touches. */
 | |
|         /* However, we do want to finalize the Sdmmc1PowerController. */
 | |
|         AMS_ABORT_UNLESS(m_power_controller != nullptr);
 | |
|         m_power_controller = nullptr;
 | |
|         util::DestroyAt(m_power_controller_storage);
 | |
| 
 | |
|         /* pinmux::CloseSession(std::addressof(m_pinmux_session)); */
 | |
|         /* This does nothing. */
 | |
| 
 | |
|         /* pinmux::Finalize(); */
 | |
|         /* This does nothing. */
 | |
| 
 | |
|         #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|         /* Mark ourselves as initialized by register control. */
 | |
|         m_is_pcv_control = false;
 | |
|         #endif
 | |
|     }
 | |
| 
 | |
|     #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
 | |
|     void Sdmmc1Controller::InitializeForPcvControl() {
 | |
|         /* Mark ourselves as initialized by pcv control. */
 | |
|         m_is_pcv_control = true;
 | |
| 
 | |
|         /* TODO: pinmux::Initialize(); */
 | |
|         /* TODO: pinmux::OpenSession(std::addressof(m_pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */
 | |
|         /* TODO: pcv::Initialize(); */
 | |
| 
 | |
|         /* Perform base initialization. */
 | |
|         SdmmcController::Initialize();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::FinalizeForPcvControl() {
 | |
|         /* Perform base finalization. */
 | |
|         SdmmcController::Finalize();
 | |
| 
 | |
|         /* TODO: pcv::Finalize(); */
 | |
|         /* TODO: pinmux::CloseSession(std::addressof(m_pinmux_session)); */
 | |
|         /* TODO: pinmux::Finalize(); */
 | |
| 
 | |
|         /* Mark ourselves as initialized by register control. */
 | |
|         m_is_pcv_control = false;
 | |
|     }
 | |
|     #endif
 | |
| 
 | |
|     namespace {
 | |
| 
 | |
|         constexpr inline dd::PhysicalAddress PmcRegistersPhysicalAddress = 0x7000E400;
 | |
| 
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerController::ControlVddioSdmmc1(BusPower bus_power) {
 | |
|         /* Configure appropriate voltage. */
 | |
|         switch (bus_power) {
 | |
|             case BusPower_Off:
 | |
|                 R_TRY(SetSdCardVoltageEnabled(false));
 | |
|                 break;
 | |
|             case BusPower_1_8V:
 | |
|                 R_TRY(SetSdCardVoltageValue(1'800'000));
 | |
|                 R_TRY(SetSdCardVoltageEnabled(true));
 | |
|                 break;
 | |
|             case BusPower_3_3V:
 | |
|                 R_TRY(SetSdCardVoltageValue(3'300'000));
 | |
|                 R_TRY(SetSdCardVoltageEnabled(true));
 | |
|                 break;
 | |
|             AMS_UNREACHABLE_DEFAULT_CASE();
 | |
|         }
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::PowerController::SetSdmmcIoMode(bool is_3_3V) {
 | |
|         /* Determine the address we're updating. */
 | |
|         constexpr dd::PhysicalAddress ApbdevPmcPwrDetValAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_PWR_DET_VAL;
 | |
| 
 | |
|         /* Read the current value. */
 | |
|         u32 value = dd::ReadIoRegister(ApbdevPmcPwrDetValAddress);
 | |
| 
 | |
|         /* Mask out the existing bits. */
 | |
|         value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(PWR_DET_VAL_SDMMC1)));
 | |
| 
 | |
|         /* ORR in the new bits. */
 | |
|         value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(PWR_DET_VAL_SDMMC1, is_3_3V, ENABLE, DISABLE));
 | |
| 
 | |
|         /* Write the new value. */
 | |
|         dd::WriteIoRegister(ApbdevPmcPwrDetValAddress, value);
 | |
| 
 | |
|         /* Read the value back to be sure our write takes. */
 | |
|         dd::ReadIoRegister(ApbdevPmcPwrDetValAddress);
 | |
|     }
 | |
| 
 | |
|     void Sdmmc1Controller::PowerController::ControlRailSdmmc1Io(bool is_power_on) {
 | |
|         /* Determine the address we're updating. */
 | |
|         constexpr dd::PhysicalAddress ApbdevPmcNoIoPowerAddress = PmcRegistersPhysicalAddress + APBDEV_PMC_NO_IOPOWER;
 | |
| 
 | |
|         /* Read the current value. */
 | |
|         u32 value = dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress);
 | |
| 
 | |
|         /* Mask out the existing bits. */
 | |
|         value &= ~(reg::EncodeMask(PMC_REG_BITS_MASK(NO_IOPOWER_SDMMC1)));
 | |
| 
 | |
|         /* ORR in the new bits. */
 | |
|         value |= reg::Encode(PMC_REG_BITS_ENUM_SEL(NO_IOPOWER_SDMMC1, is_power_on, DISABLE, ENABLE));
 | |
| 
 | |
|         /* Write the new value. */
 | |
|         dd::WriteIoRegister(ApbdevPmcNoIoPowerAddress, value);
 | |
| 
 | |
|         /* Read the value back to be sure our write takes. */
 | |
|         dd::ReadIoRegister(ApbdevPmcNoIoPowerAddress);
 | |
|     }
 | |
| 
 | |
|     Sdmmc1Controller::PowerController::PowerController() : m_current_bus_power(BusPower_Off) {
 | |
|         /* gpio::Initialize(); */
 | |
|         /* ... */
 | |
| 
 | |
|         /* Open gpio session. */
 | |
|         /* gpio::OpenSession(std::addressof(m_gpio_pad_session), gpio::GpioPadName_PowSdEn); */
 | |
|         gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn);
 | |
| 
 | |
|         /* Configure the gpio as low/output. */
 | |
|         /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_Low); */
 | |
|         gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low);
 | |
| 
 | |
|         /* gpio::SetDirection(std::addressof(m_gpio_pad_session), gpio::Direction_Output); */
 | |
|         gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output);
 | |
|     }
 | |
| 
 | |
|     Sdmmc1Controller::PowerController::~PowerController() {
 | |
|         /* gpio::CloseSession(std::addressof(m_gpio_pad_session)); */
 | |
|         gpio_impl::CloseSession(gpio_impl::GpioPadName_PowSdEn);
 | |
| 
 | |
|         /* gpio::Finalize(); */
 | |
|         /* ... */
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerController::PowerOn(BusPower bus_power) {
 | |
|         /* Bus power should be off, and if it's not we don't need to do anything. */
 | |
|         AMS_ASSERT(m_current_bus_power == BusPower_Off);
 | |
|         R_SUCCEED_IF(m_current_bus_power != BusPower_Off);
 | |
| 
 | |
|         /* Power on requires the target bus power be 3.3V. */
 | |
|         AMS_ABORT_UNLESS(bus_power == BusPower_3_3V);
 | |
| 
 | |
|         /* Enable the rail. */
 | |
|         this->ControlRailSdmmc1Io(true);
 | |
| 
 | |
|         /* Set the SD power GPIO to high. */
 | |
|         /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_High); */
 | |
|         gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_High);
 | |
| 
 | |
|         /* Wait 10ms for power change to take. */
 | |
|         WaitMicroSeconds(10000);
 | |
| 
 | |
|         /* Configure Sdmmc1 IO as 3.3V. */
 | |
|         this->SetSdmmcIoMode(true);
 | |
|         R_TRY(this->ControlVddioSdmmc1(BusPower_3_3V));
 | |
| 
 | |
|         /* Wait 130 us for changes to take. */
 | |
|         WaitMicroSeconds(130);
 | |
| 
 | |
|         /* Update our current bus power. */
 | |
|         m_current_bus_power = bus_power;
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerController::PowerOff() {
 | |
|         /* Bus power should be on, and if it's not we don't need to do anything. */
 | |
|         AMS_ASSERT(m_current_bus_power != BusPower_Off);
 | |
|         R_SUCCEED_IF(m_current_bus_power == BusPower_Off);
 | |
| 
 | |
|         /* Bus power should be 1.8V. */
 | |
|         /* NOTE: the result returned here is 0x8C0 (regulator::ResultIllegalRequest()) on newer firmwares. */
 | |
|         AMS_ASSERT(m_current_bus_power == BusPower_1_8V);
 | |
|         R_UNLESS(m_current_bus_power == BusPower_1_8V, pcv::ResultIllegalRequest());
 | |
| 
 | |
|         /* Disable vddio, and wait 4 ms. */
 | |
|         this->ControlVddioSdmmc1(BusPower_Off);
 | |
|         WaitMicroSeconds(4000);
 | |
| 
 | |
|         /* Set the SD power GPIO to low. */
 | |
|         /* gpio::SetValue(std::addressof(m_gpio_pad_session), gpio::GpioValue_Low); */
 | |
|         gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low);
 | |
| 
 | |
|         /* Wait 239ms for the gpio config to take. */
 | |
|         WaitMicroSeconds(239000);
 | |
| 
 | |
|         /* Disable the rail. */
 | |
|         this->ControlRailSdmmc1Io(false);
 | |
|         this->SetSdmmcIoMode(true);
 | |
| 
 | |
|         /* Update our current bus power. */
 | |
|         m_current_bus_power = BusPower_Off;
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
|     Result Sdmmc1Controller::PowerController::LowerBusPower() {
 | |
|         /* Bus power should be 3.3V, and if it's not we don't need to do anything. */
 | |
|         AMS_ASSERT(m_current_bus_power == BusPower_3_3V);
 | |
|         R_SUCCEED_IF(m_current_bus_power != BusPower_3_3V);
 | |
| 
 | |
|         /* Configure as 1.8V, then wait 150us for it to take. */
 | |
|         R_TRY(this->ControlVddioSdmmc1(BusPower_1_8V));
 | |
|         WaitMicroSeconds(150);
 | |
|         this->SetSdmmcIoMode(false);
 | |
| 
 | |
|         /* Update our current bus power. */
 | |
|         m_current_bus_power = BusPower_1_8V;
 | |
| 
 | |
|         R_SUCCEED();
 | |
|     }
 | |
| 
 | |
| }
 |