/*
 * 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/>.
 */
#pragma once
#include <vapours.hpp>
#include "sdmmc_sd_host_standard_controller.hpp"
#include "sdmmc_clock_reset_controller.hpp"

namespace ams::sdmmc::impl {

    bool IsSocMariko();

    constexpr inline size_t SdmmcRegistersSize = 0x200;

    constexpr inline dd::PhysicalAddress ApbMiscRegistersPhysicalAddress = UINT64_C(0x70000000);
    constexpr inline size_t ApbMiscRegistersSize = 16_KB;

    class SdmmcController : public SdHostStandardController {
        private:
            struct SdmmcRegisters {
                /* Standard registers. */
                volatile SdHostStandardRegisters sd_host_standard_registers;

                /* Vendor specific registers */
                volatile uint32_t vendor_clock_cntrl;
                volatile uint32_t vendor_sys_sw_cntrl;
                volatile uint32_t vendor_err_intr_status;
                volatile uint32_t vendor_cap_overrides;
                volatile uint32_t vendor_boot_cntrl;
                volatile uint32_t vendor_boot_ack_timeout;
                volatile uint32_t vendor_boot_dat_timeout;
                volatile uint32_t vendor_debounce_count;
                volatile uint32_t vendor_misc_cntrl;
                volatile uint32_t max_current_override;
                volatile uint32_t max_current_override_hi;
                volatile uint32_t _0x12c[0x20];
                volatile uint32_t vendor_io_trim_cntrl;

                /* Start of sdmmc2/sdmmc4 only */
                volatile uint32_t vendor_dllcal_cfg;
                volatile uint32_t vendor_dll_ctrl0;
                volatile uint32_t vendor_dll_ctrl1;
                volatile uint32_t vendor_dllcal_cfg_sta;
                /* End of sdmmc2/sdmmc4 only */

                volatile uint32_t vendor_tuning_cntrl0;
                volatile uint32_t vendor_tuning_cntrl1;
                volatile uint32_t vendor_tuning_status0;
                volatile uint32_t vendor_tuning_status1;
                volatile uint32_t vendor_clk_gate_hysteresis_count;
                volatile uint32_t vendor_preset_val0;
                volatile uint32_t vendor_preset_val1;
                volatile uint32_t vendor_preset_val2;
                volatile uint32_t sdmemcomppadctrl;
                volatile uint32_t auto_cal_config;
                volatile uint32_t auto_cal_interval;
                volatile uint32_t auto_cal_status;
                volatile uint32_t io_spare;
                volatile uint32_t sdmmca_mccif_fifoctrl;
                volatile uint32_t timeout_wcoal_sdmmca;
                volatile uint32_t _0x1fc;
            };
            static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize);
        private:
            SdmmcRegisters *m_sdmmc_registers;
            bool m_is_shutdown;
            bool m_is_awake;
            SpeedMode m_current_speed_mode;
            BusPower m_bus_power_before_sleep;
            BusWidth m_bus_width_before_sleep;
            SpeedMode m_speed_mode_before_sleep;
            u8 m_tap_value_before_sleep;
            bool m_is_powersaving_enable_before_sleep;
            u8 m_tap_value_for_hs_400;
            bool m_is_valid_tap_value_for_hs_400;
            Result m_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(AMS_OFFSETOF(SdmmcRegisters, sd_host_standard_registers) == 0);
                m_sdmmc_registers = reinterpret_cast<SdmmcRegisters *>(m_registers);

                m_is_shutdown                        = true;
                m_is_awake                           = true;
                m_is_valid_tap_value_for_hs_400      = false;
                m_drive_strength_calibration_status  = sdmmc::ResultDriveStrengthCalibrationNotCompleted();
                m_tap_value_for_hs_400               = 0;
                m_current_speed_mode                 = SpeedMode_MmcIdentification;
                m_bus_power_before_sleep             = BusPower_Off;
                m_bus_width_before_sleep             = BusWidth_1Bit;
                m_speed_mode_before_sleep            = SpeedMode_MmcIdentification;
                m_tap_value_before_sleep             = 0;
                m_is_powersaving_enable_before_sleep = false;
            }

            virtual void Initialize() override {
                /* Set pad. */
                this->SetPad();

                /* Initialize our clock/reset module. */
                ClockResetController::Initialize(this->GetClockResetModule());

                /* If necessary, initialize our interrupt event. */
                #if defined(AMS_SDMMC_USE_OS_EVENTS)
                {
                    os::InterruptEventType *interrupt_event = this->GetInterruptEvent();
                    os::InitializeInterruptEvent(interrupt_event, this->GetInterruptNumber(), os::EventClearMode_ManualClear);
                    SdHostStandardController::PreSetInterruptEvent(interrupt_event);
                }
                #endif

                /* Perform base initialization. */
                SdHostStandardController::Initialize();
            }

            virtual void Finalize() override {
                /* Perform base finalization. */
                SdHostStandardController::Finalize();

                /* If necessary, finalize our interrupt event. */
                #if defined(AMS_SDMMC_USE_OS_EVENTS)
                {
                    os::FinalizeInterruptEvent(this->GetInterruptEvent());
                }
                #endif

                /* Finalize our clock/reset module. */
                ClockResetController::Finalize(this->GetClockResetModule());
            }

            virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) override;
            virtual void Shutdown() override;
            virtual void PutToSleep() override;
            virtual Result Awaken() override;
            virtual Result SwitchToSdr12() override;
            virtual Result SetSpeedMode(SpeedMode speed_mode) override;

            virtual SpeedMode GetSpeedMode() const override {
                return m_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 m_drive_strength_calibration_status;
            }
    };

    constexpr inline dd::PhysicalAddress Sdmmc1RegistersPhysicalAddress = UINT64_C(0x700B0000);

    class Sdmmc1Controller : public SdmmcController {
        private:
            #if defined(AMS_SDMMC_USE_OS_EVENTS)
                static constinit inline os::InterruptEventType s_interrupt_event{};
            #endif

            /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */
            class PowerController {
                NON_COPYABLE(PowerController);
                NON_MOVEABLE(PowerController);
                private:
                    BusPower m_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 m_pinmux_session; */
            #endif
            BusPower m_current_bus_power;
            #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
            bool m_is_pcv_control;
            #endif
            util::TypedStorage<PowerController> m_power_controller_storage;
            PowerController *m_power_controller;
        private:
            Result PowerOnForRegisterControl(BusPower bus_power);
            void PowerOffForRegisterControl();
            Result LowerBusPowerForRegisterControl();
            void SetSchmittTriggerForRegisterControl(BusPower bus_power);

            #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
            Result PowerOnForPcvControl(BusPower bus_power);
            void PowerOffForPcvControl();
            Result LowerBusPowerForPcvControl();
            void SetSchmittTriggerForPcvControl(BusPower bus_power);
            #endif
        protected:
            virtual void SetPad() override {
                /* Nothing is needed here. */
            }

            virtual ClockResetController::Module GetClockResetModule() const override {
                return ClockResetController::Module_Sdmmc1;
            }

            #if defined(AMS_SDMMC_USE_OS_EVENTS)
            virtual int GetInterruptNumber() const override {
                return 46;
            }

            virtual os::InterruptEventType *GetInterruptEvent() const override {
                return std::addressof(s_interrupt_event);
            }
            #endif

            virtual bool IsNeedPeriodicDriveStrengthCalibration() override {
                return !IsSocMariko();
            }

            virtual void ClearPadParked() override {
                /* Nothing is needed here. */
            }

            virtual Result PowerOn(BusPower bus_power) override;
            virtual void PowerOff() override;
            virtual Result LowerBusPower() override;

            virtual void SetSchmittTrigger(BusPower bus_power) override;

            virtual u8 GetOutboundTapValue() const override {
                if (IsSocMariko()) {
                    return 0xE;
                } else {
                    return 0x2;
                }
            }

            virtual u8 GetDefaultInboundTapValue() const override {
                if (IsSocMariko()) {
                    return 0xB;
                } else {
                    return 0x4;
                }
            }

            virtual u8 GetVrefSelValue() const override {
                if (IsSocMariko()) {
                    return 0x0;
                } else {
                    return 0x7;
                }
            }

            virtual void SetSlewCodes() override {
                if (IsSocMariko()) {
                    /* Do nothing. */
                } else {
                    /* Ensure that we can control registers. */
                    SdHostStandardController::EnsureControl();

                    /* Get the apb registers address. */
                    const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                    /* Write the slew values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 0x1),
                                                                                    APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 0x1));

                    /* Read to be sure our config takes. */
                    reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL);
                }
            }

            virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override {
                /* Ensure that we can write the offsets. */
                AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr);
                AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr);

                /* Set the offsets. */
                if (IsSocMariko()) {
                    switch (bus_power) {
                        case BusPower_1_8V:
                            *out_auto_cal_pd_offset = 6;
                            *out_auto_cal_pu_offset = 6;
                            break;
                        case BusPower_3_3V:
                            *out_auto_cal_pd_offset = 0;
                            *out_auto_cal_pu_offset = 0;
                            break;
                        case BusPower_Off:
                        AMS_UNREACHABLE_DEFAULT_CASE();
                    }
                } else {
                    switch (bus_power) {
                        case BusPower_1_8V:
                            *out_auto_cal_pd_offset = 0x7B;
                            *out_auto_cal_pu_offset = 0x7B;
                            break;
                        case BusPower_3_3V:
                            *out_auto_cal_pd_offset = 0x7D;
                            *out_auto_cal_pu_offset = 0;
                            break;
                        case BusPower_Off:
                        AMS_UNREACHABLE_DEFAULT_CASE();
                    }
                }
            }

            virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override {
                /* Ensure that we can control registers. */
                SdHostStandardController::EnsureControl();

                /* Get the apb registers address. */
                const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                /* Determine the drive code values. */
                u8 drvdn, drvup;
                if (IsSocMariko()) {
                    drvdn = 0x8;
                    drvup = 0x8;
                } else {
                    switch (bus_power) {
                        case BusPower_1_8V:
                            drvdn = 0xF;
                            drvup = 0xB;
                            break;
                        case BusPower_3_3V:
                            drvdn = 0xC;
                            drvup = 0xC;
                            break;
                        case BusPower_Off:
                        AMS_UNREACHABLE_DEFAULT_CASE();
                    }
                }

                /* Write the drv up/down values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */
                reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, drvdn),
                                                                                APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, drvup));

                /* Read to be sure our config takes. */
                reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL);
            }
        public:
            Sdmmc1Controller() : SdmmcController(Sdmmc1RegistersPhysicalAddress) {
                m_current_bus_power = BusPower_Off;
                #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
                m_is_pcv_control = false;
                #endif
                m_power_controller = nullptr;
            }

            virtual void Initialize() override;
            virtual void Finalize() override;

            void InitializeForRegisterControl();
            void FinalizeForRegisterControl();

            #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL)
            void InitializeForPcvControl();
            void FinalizeForPcvControl();
            #endif

            virtual bool IsSupportedBusPower(BusPower bus_power) const override {
                switch (bus_power) {
                    case BusPower_Off:  return true;
                    case BusPower_1_8V: return true;
                    case BusPower_3_3V: return true;
                    AMS_UNREACHABLE_DEFAULT_CASE();
                }
            }

            virtual bool IsSupportedBusWidth(BusWidth bus_width) const override {
                switch (bus_width) {
                    case BusWidth_1Bit: return true;
                    case BusWidth_4Bit: return true;
                    case BusWidth_8Bit: return false;
                    AMS_UNREACHABLE_DEFAULT_CASE();
                }
            }
    };

    class Sdmmc2And4Controller : public SdmmcController {
        protected:
            virtual bool IsNeedPeriodicDriveStrengthCalibration() override {
                return false;
            }

            virtual Result PowerOn(BusPower bus_power) override {
                /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */
                AMS_UNUSED(bus_power);
                R_SUCCEED();
            }

            virtual void PowerOff() override {
                /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */
            }

            virtual Result LowerBusPower() override {
                AMS_ABORT("Sdmmc2And4Controller cannot lower bus power\n");
            }

            virtual void SetSchmittTrigger(BusPower bus_power) override {
                /* Do nothing. */
                AMS_UNUSED(bus_power);
            }

            virtual u8 GetOutboundTapValue() const override {
                if (IsSocMariko()) {
                    return 0xD;
                } else {
                    return 0x8;
                }
            }

            virtual u8 GetDefaultInboundTapValue() const override {
                if (IsSocMariko()) {
                    return 0xB;
                } else {
                    return 0x0;
                }
            }

            virtual u8 GetVrefSelValue() const override {
                return 0x7;
            }

            virtual void SetSlewCodes() override {
                /* Do nothing. */
            }

            virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override {
                /* Ensure that we can write the offsets. */
                AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr);
                AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr);

                /* Sdmmc2And4Controller only supports 1.8v. */
                AMS_ABORT_UNLESS(bus_power == BusPower_1_8V);

                /* Set the offsets. */
                *out_auto_cal_pd_offset = 5;
                *out_auto_cal_pu_offset = 5;
            }
        public:
            explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) {
                /* ... */
            }

            virtual bool IsSupportedBusPower(BusPower bus_power) const override {
                switch (bus_power) {
                    case BusPower_Off:  return true;
                    case BusPower_1_8V: return true;
                    case BusPower_3_3V: return false;
                    AMS_UNREACHABLE_DEFAULT_CASE();
                }
            }

            virtual bool IsSupportedBusWidth(BusWidth bus_width) const override {
                switch (bus_width) {
                    case BusWidth_1Bit: return true;
                    case BusWidth_4Bit: return true;
                    case BusWidth_8Bit: return true;
                    AMS_UNREACHABLE_DEFAULT_CASE();
                }
            }
    };

    constexpr inline dd::PhysicalAddress Sdmmc2RegistersPhysicalAddress = UINT64_C(0x700B0200);

    class Sdmmc2Controller : public Sdmmc2And4Controller {
        private:
            #if defined(AMS_SDMMC_USE_OS_EVENTS)
                static constinit inline os::InterruptEventType s_interrupt_event{};
            #endif
        protected:
            virtual void SetPad() override {
                /* Nothing is needed here. */
            }

            virtual ClockResetController::Module GetClockResetModule() const override {
                return ClockResetController::Module_Sdmmc2;
            }

            #if defined(AMS_SDMMC_USE_OS_EVENTS)
            virtual int GetInterruptNumber() const override {
                return 47;
            }

            virtual os::InterruptEventType *GetInterruptEvent() const override {
                return std::addressof(s_interrupt_event);
            }
            #endif

            virtual void ClearPadParked() override {
                if (IsSocMariko()) {
                    /* Nothing is needed here. */
                } else {
                    /* Get the apb registers address. */
                    const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                    /* Clear all MISC2PMC_EMMC2_*_PARK bits. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 0));

                    /* Read to be sure our config takes. */
                    reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL);
                }
            }

            virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override {
                /* SDMMC2 only supports 1.8v. */
                AMS_ABORT_UNLESS(bus_power == BusPower_1_8V);

                /* Ensure that we can control registers. */
                SdHostStandardController::EnsureControl();

                /* Get the apb registers address. */
                const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                if (IsSocMariko()) {
                    /* Write the drv up/down values to APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 0xA),
                                                                                    APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 0xA));

                    /* Read to be sure our config takes. */
                    reg::Read(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL);
                } else {
                    /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 0x10),
                                                                                   APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 0x10));

                    /* Read to be sure our config takes. */
                    reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL);
                }
            }
        public:
            Sdmmc2Controller() : Sdmmc2And4Controller(Sdmmc2RegistersPhysicalAddress) {
                /* ... */
            }
    };

    constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600);

    class Sdmmc4Controller : public Sdmmc2And4Controller {
        private:
            #if defined(AMS_SDMMC_USE_OS_EVENTS)
                static constinit inline os::InterruptEventType s_interrupt_event{};
            #endif
        protected:
            virtual void SetPad() override {
                if (IsSocMariko()) {
                    /* Get the apb registers address. */
                    const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                    /* Enable Schmitt Trigger in emmc4 iobrick. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, ENABLE));

                    /* Clear CMD_PULLU, CLK_PULLD, DQS_PULLD. */
                    reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 0),
                                                                                        APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 0),
                                                                                        APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 0));

                    /* Read again to be sure our config takes. */
                    reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL);
                } else {
                    /* On Erista, we can just leave the reset value intact. */
                }
            }

            virtual ClockResetController::Module GetClockResetModule() const override {
                return ClockResetController::Module_Sdmmc4;
            }

            #if defined(AMS_SDMMC_USE_OS_EVENTS)
            virtual int GetInterruptNumber() const override {
                return 63;
            }

            virtual os::InterruptEventType *GetInterruptEvent() const override {
                return std::addressof(s_interrupt_event);
            }
            #endif

            virtual void ClearPadParked() override {
                /* Get the apb registers address. */
                const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                /* Clear all MISC2PMC_EMMC4_*_PARK bits. */
                reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 0));

                /* Read to be sure our config takes. */
                reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL);
            }

            virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override {
                /* SDMMC4 only supports 1.8v. */
                AMS_ABORT_UNLESS(bus_power == BusPower_1_8V);

                /* Ensure that we can control registers. */
                SdHostStandardController::EnsureControl();

                /* Get the apb registers address. */
                const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize);

                /* Determine the drv up/down values. */
                u8 drvdn, drvup;
                if (IsSocMariko()) {
                    drvdn = 0xA;
                    drvup = 0xA;
                } else {
                    drvdn = 0x10;
                    drvup = 0x10;
                }

                /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */
                reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, drvdn),
                                                                               APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, drvup));

                /* Read to be sure our config takes. */
                reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL);
            }
        public:
            Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) {
                /* ... */
            }
    };

}
