From 934018354119eba5300f59f275c19f92409ed661 Mon Sep 17 00:00:00 2001
From: Michael Scire <SciresM@gmail.com>
Date: Wed, 21 Oct 2020 12:26:15 -0700
Subject: [PATCH] sdmmc: complete abstract logic for Sdmmc1 power controller

---
 .../include/vapours/dd/dd_io_mapping.hpp      |   3 +
 .../libvapours/include/vapours/results.hpp    |   1 +
 .../include/vapours/results/pcv_results.hpp   |  26 ++
 .../include/vapours/tegra/tegra_pmc.hpp       |   4 +
 .../libvapours/source/dd/dd_io_mapping.cpp    |  28 +++
 .../impl/sdmmc_io_impl.board.nintendo_nx.cpp  |  66 +++++
 .../impl/sdmmc_io_impl.board.nintendo_nx.hpp  |  62 +++++
 ...mmc_sdmmc_controller.board.nintendo_nx.cpp | 232 ++++++++++++++++--
 ...mmc_sdmmc_controller.board.nintendo_nx.hpp |  22 ++
 9 files changed, 429 insertions(+), 15 deletions(-)
 create mode 100644 libraries/libvapours/include/vapours/results/pcv_results.hpp
 create mode 100644 libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp
 create mode 100644 libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp

diff --git a/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp b/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp
index 520575b03..f82552bb0 100644
--- a/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp
+++ b/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp
@@ -20,4 +20,7 @@ namespace ams::dd {
 
     uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size);
 
+    u32 ReadIoRegister(dd::PhysicalAddress phys_addr);
+    void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value);
+
 }
diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp
index 40d809b90..596114b95 100644
--- a/libraries/libvapours/include/vapours/results.hpp
+++ b/libraries/libvapours/include/vapours/results.hpp
@@ -41,6 +41,7 @@
 #include <vapours/results/nim_results.hpp>
 #include <vapours/results/ns_results.hpp>
 #include <vapours/results/os_results.hpp>
+#include <vapours/results/pcv_results.hpp>
 #include <vapours/results/pgl_results.hpp>
 #include <vapours/results/pm_results.hpp>
 #include <vapours/results/psc_results.hpp>
diff --git a/libraries/libvapours/include/vapours/results/pcv_results.hpp b/libraries/libvapours/include/vapours/results/pcv_results.hpp
new file mode 100644
index 000000000..ec0aa6d46
--- /dev/null
+++ b/libraries/libvapours/include/vapours/results/pcv_results.hpp
@@ -0,0 +1,26 @@
+/*
+ * 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/results/results_common.hpp>
+
+namespace ams::pcv {
+
+    R_DEFINE_NAMESPACE_RESULT_MODULE(133);
+
+    R_DEFINE_ERROR_RESULT(IllegalRequest, 16);
+
+}
diff --git a/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp
index f80f61b70..635917e97 100644
--- a/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp
+++ b/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp
@@ -202,6 +202,10 @@ DEFINE_PMC_REG_BIT_ENUM(PWRGATE_STATUS_VE2,   29, OFF, ON);
 
 DEFINE_PMC_REG(PWRGATE_STATUS_CE123, 9, 3);
 
+DEFINE_PMC_REG_BIT_ENUM(NO_IOPOWER_SDMMC1,  12, DISABLE, ENABLE);
+DEFINE_PMC_REG_BIT_ENUM(PWR_DET_SDMMC1,     12, DISABLE, ENABLE);
+DEFINE_PMC_REG_BIT_ENUM(PWR_DET_VAL_SDMMC1, 12, DISABLE, ENABLE);
+
 DEFINE_PMC_REG(SET_SW_CLAMP_CRAIL, 0, 1);
 
 DEFINE_PMC_REG_TWO_BIT_ENUM(IO_DPD_REQ_CODE, 30, IDLE, DPD_OFF, DPD_ON, RESERVED3);
diff --git a/libraries/libvapours/source/dd/dd_io_mapping.cpp b/libraries/libvapours/source/dd/dd_io_mapping.cpp
index c029a0434..3ece3e2c4 100644
--- a/libraries/libvapours/source/dd/dd_io_mapping.cpp
+++ b/libraries/libvapours/source/dd/dd_io_mapping.cpp
@@ -66,4 +66,32 @@ namespace ams::dd {
         #endif
     }
 
+    u32 ReadIoRegister(dd::PhysicalAddress phys_addr) {
+        #if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE)
+            return reg::Read(dd::QueryIoMapping(phys_addr, sizeof(u32)));
+        #elif defined(ATMOSPHERE_IS_STRATOSPHERE)
+
+            u32 val;
+            R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(val), phys_addr, 0, 0));
+            return val;
+
+        #else
+            #error "Unknown execution context for ams::dd::ReadIoRegister!"
+        #endif
+    }
+
+    void WriteIoRegister(dd::PhysicalAddress phys_addr, u32 value) {
+        #if defined(ATMOSPHERE_IS_EXOSPHERE) || defined(ATMOSPHERE_IS_MESOSPHERE)
+            reg::Write(dd::QueryIoMapping(phys_addr, sizeof(u32)), value);
+        #elif defined(ATMOSPHERE_IS_STRATOSPHERE)
+
+            u32 out_val;
+            R_ABORT_UNLESS(svc::ReadWriteRegister(std::addressof(val), phys_addr, 0xFFFFFFFF, value));
+            AMS_UNUSED(out_val);
+
+        #else
+            #error "Unknown execution context for ams::dd::WriteIoRegister!"
+        #endif
+    }
+
 }
diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp
new file mode 100644
index 000000000..82c9de3b0
--- /dev/null
+++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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_io_impl.board.nintendo_nx.hpp"
+
+namespace ams::sdmmc::impl {
+
+    Result SetSdCardVoltageEnabled(bool en) {
+        /* TODO */
+    }
+
+    Result SetSdCardVoltageValue(u32 micro_volts) {
+        /* TODO */
+    }
+
+    namespace pinmux_impl {
+
+        void SetPinAssignment(PinAssignment assignment) {
+            /* TODO */
+        }
+
+    }
+
+    namespace gpio_impl {
+
+        void OpenSession(GpioPadName pad) {
+            /* TODO */
+        }
+
+        void CloseSession(GpioPadName pad) {
+            /* TODO */
+        }
+
+        void SetDirection(GpioPadName pad, Direction direction) {
+            /* TODO */
+        }
+
+        void SetValue(GpioPadName pad, GpioValue value) {
+            /* TODO */
+        }
+
+
+    }
+
+}
diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp
new file mode 100644
index 000000000..89b623073
--- /dev/null
+++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.hpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2018-2020 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#pragma once
+#include <vapours.hpp>
+
+namespace ams::sdmmc::impl {
+
+    Result SetSdCardVoltageEnabled(bool en);
+    Result SetSdCardVoltageValue(u32 micro_volts);
+
+    namespace pinmux_impl {
+
+        enum PinAssignment {
+            PinAssignment_Sdmmc1OutputHigh   = 2,
+            PinAssignment_Sdmmc1ResetState   = 3,
+            PinAssignment_Sdmmc1SchmtEnable  = 4,
+            PinAssignment_Sdmmc1SchmtDisable = 5,
+        };
+
+        void SetPinAssignment(PinAssignment assignment);
+
+    }
+
+    namespace gpio_impl {
+
+        enum GpioValue {
+            GpioValue_Low  = 0,
+            GpioValue_High = 1
+        };
+
+        enum Direction {
+            Direction_Input  = 0,
+            Direction_Output = 1,
+        };
+
+        enum GpioPadName {
+            GpioPadName_PowSdEn = 2,
+        };
+
+        void OpenSession(GpioPadName pad);
+        void CloseSession(GpioPadName pad);
+
+        void SetDirection(GpioPadName pad, Direction direction);
+        void SetValue(GpioPadName pad, GpioValue value);
+
+
+    }
+
+}
diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp
index fffaad71a..a8fe01d26 100644
--- a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp
+++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp
@@ -23,6 +23,7 @@
 #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 {
@@ -875,7 +876,9 @@ namespace ams::sdmmc::impl {
         /* Nintendo sets the current bus power regardless of whether the call succeeds. */
         ON_SCOPE_EXIT { this->current_bus_power = BusPower_3_3V; };
 
-        /* TODO: equivalent of return pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */
+        /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */
+        R_TRY(this->power_controller->PowerOn(BusPower_3_3V));
+
         return ResultSuccess();
     }
 
@@ -887,23 +890,34 @@ namespace ams::sdmmc::impl {
 
         /* If we're at 3.3V, lower to 1.8V. */
         {
-            /* TODO: equivalent of pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
+            /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
+            this->power_controller->LowerBusPower();
+
+            /* Set our bus power. */
             this->current_bus_power = BusPower_1_8V;
         }
 
-        /* TODO: Equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
+        /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */
+        pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh);
 
-        /* TODO: Equivalent of pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
+
+        /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */
+        this->power_controller->PowerOff();
+
+        /* Set our bus power. */
         this->current_bus_power = BusPower_Off;
 
-        /* TODO: Equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */
+        /* pinmux::SetPinAssignment(std::addressof(this->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 { this->current_bus_power = BusPower_1_8V; };
 
-        /* TODO: equivalent of return pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
+        /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */
+        R_TRY(this->power_controller->LowerBusPower());
+
         return ResultSuccess();
     }
 
@@ -911,14 +925,17 @@ namespace ams::sdmmc::impl {
         SdHostStandardController::EnsureControl();
 
         if (IsSocMariko()) {
-            /* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
+            /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
+            pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
         } else {
             switch (bus_power) {
                 case BusPower_1_8V:
-                    /* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
+                    /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */
+                    pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable);
                     break;
                 case BusPower_3_3V:
-                    /* TODO: equivalent of pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
+                    /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtDisable); */
+                    pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtDisable);
                     break;
                 case BusPower_Off:
                 AMS_UNREACHABLE_DEFAULT_CASE();
@@ -1051,9 +1068,17 @@ namespace ams::sdmmc::impl {
         this->is_pcv_control = false;
         #endif
 
-        /* TODO: equivalent of pinmux::Initialize(); */
-        /* TODO: equivalent of pinmux::OpenSession(std::addressof(this->pinmux_session), pinmux::AssignablePinGroupName_Sdmmc1); */
-        /* TODO: equivalent of pcv::Initialize(); */
+        /* pinmux::Initialize(); */
+        /* This just opens a session handle to pinmux service, no work to do. */
+
+        /* pinmux::OpenSession(std::addressof(this->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(this->power_controller == nullptr);
+        this->power_controller = new (GetPointer(this->power_controller_storage)) PowerController;
 
         /* Perform base initialization. */
         SdmmcController::Initialize();
@@ -1063,9 +1088,18 @@ namespace ams::sdmmc::impl {
         /* Perform base finalization. */
         SdmmcController::Finalize();
 
-        /* TODO: equivalent of pcv::Finalize(); */
-        /* TODO: equivalent of pinmux::CloseSession(std::addressof(this->pinmux_session)); */
-        /* TODO: equivalent of pinmux::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(this->power_controller != nullptr);
+        this->power_controller->~PowerController();
+        this->power_controller = nullptr;
+
+        /* pinmux::CloseSession(std::addressof(this->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. */
@@ -1099,4 +1133,172 @@ namespace ams::sdmmc::impl {
     }
     #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();
+        }
+
+        return ResultSuccess();
+    }
+
+    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() : current_bus_power(BusPower_Off) {
+        /* gpio::Initialize(); */
+        /* ... */
+
+        /* Open gpio session. */
+        /* gpio::OpenSession(std::addressof(this->gpio_pad_session), gpio::GpioPadName_PowSdEn); */
+        gpio_impl::OpenSession(gpio_impl::GpioPadName_PowSdEn);
+
+        /* Configure the gpio as low/output. */
+        /* gpio::SetValue(std::addressof(this->gpio_pad_session), gpio::GpioValue_Low); */
+        gpio_impl::SetValue(gpio_impl::GpioPadName_PowSdEn, gpio_impl::GpioValue_Low);
+
+        /* gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Output); */
+        gpio_impl::SetDirection(gpio_impl::GpioPadName_PowSdEn, gpio_impl::Direction_Output);
+    }
+
+    Sdmmc1Controller::PowerController::~PowerController() {
+        /* gpio::CloseSession(std::addressof(this->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(this->current_bus_power == BusPower_Off);
+        R_SUCCEED_IF(this->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(this->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. */
+        this->current_bus_power = bus_power;
+
+        return ResultSuccess();
+    }
+
+    Result Sdmmc1Controller::PowerController::PowerOff() {
+        /* Bus power should be on, and if it's not we don't need to do anything. */
+        AMS_ASSERT(this->current_bus_power != BusPower_Off);
+        R_SUCCEED_IF(this->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(this->current_bus_power == BusPower_1_8V);
+        R_UNLESS(this->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(this->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. */
+        this->current_bus_power = BusPower_Off;
+
+        return ResultSuccess();
+    }
+
+    Result Sdmmc1Controller::PowerController::LowerBusPower() {
+        /* Bus power should be 3.3V, and if it's not we don't need to do anything. */
+        AMS_ASSERT(this->current_bus_power == BusPower_3_3V);
+        R_SUCCEED_IF(this->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. */
+        this->current_bus_power = BusPower_1_8V;
+
+        return ResultSuccess();
+    }
+
 }
diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp
index 830f9c9d9..4c5fba691 100644
--- a/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp
+++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp
@@ -210,6 +210,25 @@ namespace ams::sdmmc::impl {
             #if defined(AMS_SDMMC_USE_OS_EVENTS)
                 static constinit inline os::InterruptEventType s_interrupt_event{};
             #endif
+
+            /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */
+            class PowerController {
+                NON_COPYABLE(PowerController);
+                NON_MOVEABLE(PowerController);
+                private:
+                    BusPower current_bus_power;
+                private:
+                    Result ControlVddioSdmmc1(BusPower bus_power);
+                    void   SetSdmmcIoMode(bool is_3_3V);
+                    void   ControlRailSdmmc1Io(bool is_power_on);
+                public:
+                    PowerController();
+                    ~PowerController();
+
+                    Result PowerOn(BusPower bus_power);
+                    Result PowerOff();
+                    Result LowerBusPower();
+            };
         private:
             #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
             /* TODO: pinmux::PinmuxSession pinmux_session; */
@@ -218,6 +237,8 @@ namespace ams::sdmmc::impl {
             #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
             bool is_pcv_control;
             #endif
+            TYPED_STORAGE(PowerController) power_controller_storage;
+            PowerController *power_controller;
         private:
             Result PowerOnForRegisterControl(BusPower bus_power);
             void PowerOffForRegisterControl();
@@ -381,6 +402,7 @@ namespace ams::sdmmc::impl {
                 #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
                 this->is_pcv_control = false;
                 #endif
+                this->power_controller = nullptr;
             }
 
             virtual void Initialize() override;