diff --git a/exosphere/program/source/secmon_setup.cpp b/exosphere/program/source/secmon_setup.cpp index b5580c1b2..5e33002b8 100644 --- a/exosphere/program/source/secmon_setup.cpp +++ b/exosphere/program/source/secmon_setup.cpp @@ -648,7 +648,7 @@ namespace ams::secmon { reg::Read (MC + MC_SMMU_TLB_CONFIG); /* Flush the entire page table cache, and read TLB_CONFIG to ensure the flush takes. */ - reg::Write(MC + MC_SMMU_PTC_FLUSH, 0); + reg::Write(MC + MC_SMMU_PTC_FLUSH_0, 0); reg::Read (MC + MC_SMMU_TLB_CONFIG); /* Flush the entire translation lookaside buffer, and read TLB_CONFIG to ensure the flush takes. */ @@ -907,7 +907,7 @@ namespace ams::secmon { reg::Write(MC + MC_SMMU_PPCS1_ASID, MC_REG_BITS_ENUM(SMMU_PPCS1_ASID_PPCS1_SMMU_ENABLE, ENABLE), MC_REG_BITS_VALUE(SMMU_PPCS1_ASID_PPCS1_ASID, BpmpAsid)); /* Flush the entire page table cache, and read TLB_CONFIG to ensure the flush takes. */ - reg::Write(MC + MC_SMMU_PTC_FLUSH, 0); + reg::Write(MC + MC_SMMU_PTC_FLUSH_0, 0); reg::Read (MC + MC_SMMU_TLB_CONFIG); /* Flush the entire translation lookaside buffer, and read TLB_CONFIG to ensure the flush takes. */ diff --git a/exosphere/program/source/smc/secmon_mc_access_table_data.inc b/exosphere/program/source/smc/secmon_mc_access_table_data.inc index 5b97b8f3f..9d75169d7 100644 --- a/exosphere/program/source/smc/secmon_mc_access_table_data.inc +++ b/exosphere/program/source/smc/secmon_mc_access_table_data.inc @@ -22,7 +22,7 @@ SetRegisterAllowed(MC_SMMU_CONFIG); /* 0x010 */ SetRegisterAllowed(MC_SMMU_PTB_ASID); /* 0x01C */ SetRegisterAllowed(MC_SMMU_PTB_DATA); /* 0x020 */ SetRegisterAllowed(MC_SMMU_TLB_FLUSH); /* 0x030 */ -SetRegisterAllowed(MC_SMMU_PTC_FLUSH); /* 0x034 */ +SetRegisterAllowed(MC_SMMU_PTC_FLUSH_0); /* 0x034 */ SetRegisterAllowed(MC_EMEM_CFG); /* 0x050 */ SetRegisterAllowed(MC_EMEM_ADR_CFG); /* 0x054 */ SetRegisterAllowed(MC_EMEM_ARB_CFG); /* 0x090 */ @@ -53,7 +53,7 @@ SetRegisterAllowed(MC_SMMU_DCB_ASID); /* 0x244 */ SetRegisterAllowed(MC_SMMU_HC_ASID); /* 0x250 */ SetRegisterAllowed(MC_SMMU_HDA_ASID); /* 0x254 */ SetRegisterAllowed(MC_SMMU_ISP2_ASID); /* 0x258 */ -SetRegisterAllowed(MC_SMMU_NVENC_ASID); /* 0x264 */ +SetRegisterAllowed(MC_SMMU_MSENC_NVENC_ASID); /* 0x264 */ SetRegisterAllowed(MC_SMMU_NV_ASID); /* 0x268 */ SetRegisterAllowed(MC_SMMU_NV2_ASID); /* 0x26C */ SetRegisterAllowed(MC_SMMU_PPCS_ASID); /* 0x270 */ diff --git a/exosphere/sdmmc_test/Makefile b/exosphere/sdmmc_test/Makefile new file mode 100644 index 000000000..4bfed891e --- /dev/null +++ b/exosphere/sdmmc_test/Makefile @@ -0,0 +1,108 @@ +#--------------------------------------------------------------------------------- +# Define the atmosphere board and cpu +#--------------------------------------------------------------------------------- +export ATMOSPHERE_BOARD := nx-hac-001 +export ATMOSPHERE_CPU := arm7tdmi + +#--------------------------------------------------------------------------------- +# pull in common atmosphere configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/exosphere.mk + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) +export DEPSDIR := $(CURDIR)/$(BUILD) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +CFILES := $(call FIND_SOURCE_FILES,$(SOURCES),c) +CPPFILES := $(call FIND_SOURCE_FILES,$(SOURCES),cpp) +SFILES := $(call FIND_SOURCE_FILES,$(SOURCES),s) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) +export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) +export OFILES := $(OFILES_BIN) $(OFILES_SRC) +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(subst -,_,$(BINFILES)))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I. + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib -L$(dir)/$(ATMOSPHERE_LIBRARY_DIR)) + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) check_libexo + +$(BUILD): check_libexo + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +check_libexo: + @$(MAKE) --no-print-directory -C ../../libraries/libexosphere arm + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).bin $(OUTPUT).elf *.lz4 + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).bin + +$(OUTPUT).bin : $(OUTPUT).elf + $(OBJCOPY) -S -O binary --set-section-flags .bss=alloc,load,contents $< $@ + @echo built ... $(notdir $@) + +$(OUTPUT).elf : $(OFILES) ../../../libraries/libexosphere/$(ATMOSPHERE_LIBRARY_DIR)/libexosphere.a + +%.elf: + @echo linking $(notdir $@) + $(LD) $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@ + @$(NM) -CSn $@ > $(notdir $*.lst) + +$(OFILES_SRC) : $(HFILES_BIN) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h: %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/exosphere/sdmmc_test/sdmmc_test.ld b/exosphere/sdmmc_test/sdmmc_test.ld new file mode 100644 index 000000000..181d33330 --- /dev/null +++ b/exosphere/sdmmc_test/sdmmc_test.ld @@ -0,0 +1,194 @@ +OUTPUT_ARCH(arm) +ENTRY(_ZN3ams10sdmmc_test5StartEv) + +MEMORY +{ + NULL : ORIGIN = 0, LENGTH = 4K + test_fw : ORIGIN = 0x40010000, LENGTH = 32K +} + + +SECTIONS +{ + /* =========== CODE section =========== */ + PROVIDE(__start__ = ORIGIN(test_fw)); + . = __start__; + __code_start = . ; + + .crt0 : + { + KEEP (*(.crt0 .crt0.*)) + . = ALIGN(8); + } >test_fw + + .vectors : + { + KEEP (*(.vectors .vectors.*)) + . = ALIGN(8); + } >test_fw + + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(.text .stub .text.* .gnu.linkonce.t.*) + . = ALIGN(8); + } >test_fw + + .init : + { + KEEP( *(.init) ) + . = ALIGN(8); + } >test_fw + + .plt : + { + *(.plt) + *(.iplt) + . = ALIGN(8); + } >test_fw + + .fini : + { + KEEP( *(.fini) ) + . = ALIGN(8); + } >test_fw + + + /* =========== RODATA section =========== */ + . = ALIGN(8); + __rodata_start = . ; + + .rodata : + { + *(.rodata .rodata.* .gnu.linkonce.r.*) + . = ALIGN(8); + } >test_fw + + .eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >test_fw + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >test_fw + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >test_fw + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >test_fw + + .hash : { *(.hash) } >test_fw + + /* =========== DATA section =========== */ + . = ALIGN(8); + __data_start = . ; + + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >test_fw + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >test_fw + .gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >test_fw + .exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >test_fw + + .preinit_array ALIGN(8) : + { + PROVIDE (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE (__preinit_array_end = .); + } >test_fw + + .init_array ALIGN(8) : + { + PROVIDE (__init_array_start = .); + KEEP (*(SORT(.init_array.*))) + KEEP (*(.init_array)) + PROVIDE (__init_array_end = .); + } >test_fw + + .fini_array ALIGN(8) : + { + PROVIDE (__fini_array_start = .); + KEEP (*(.fini_array)) + KEEP (*(SORT(.fini_array.*))) + PROVIDE (__fini_array_end = .); + } >test_fw + + .ctors ALIGN(8) : + { + KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */ + KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } >test_fw + + .dtors ALIGN(8) : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } >test_fw + + __got_start__ = .; + + .got : { *(.got) *(.igot) } >test_fw + .got.plt : { *(.got.plt) *(.igot.plt) } >test_fw + + __got_end__ = .; + + .data ALIGN(8) : + { + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } >test_fw + + __bss_start__ = .; + .bss ALIGN(8) : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + . = ALIGN(16); + } >test_fw + __bss_end__ = .; + + __end__ = ABSOLUTE(.) ; + + __total_size__ = (__end__ - __start__); + + __stack_top__ = 0x40031000; + __stack_bottom__ = 0x40030000; + + /* ================== + ==== Metadata ==== + ================== */ + + /* Discard sections that difficult post-processing */ + /DISCARD/ : { *(.group .comment .note .interp) } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } +} \ No newline at end of file diff --git a/exosphere/sdmmc_test/sdmmc_test.specs b/exosphere/sdmmc_test/sdmmc_test.specs new file mode 100644 index 000000000..72d846e06 --- /dev/null +++ b/exosphere/sdmmc_test/sdmmc_test.specs @@ -0,0 +1,7 @@ +%rename link old_link + +*link: +%(old_link) -T %:getenv(TOPDIR /sdmmc_test.ld) --gc-sections --nmagic -nostdlib -nostartfiles + +*startfile: +crti%O%s crtbegin%O%s diff --git a/exosphere/sdmmc_test/source/sdmmc_test_main.cpp b/exosphere/sdmmc_test/source/sdmmc_test_main.cpp new file mode 100644 index 000000000..c7da95715 --- /dev/null +++ b/exosphere/sdmmc_test/source/sdmmc_test_main.cpp @@ -0,0 +1,146 @@ +/* + * 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 . + */ +#include + +namespace ams::sdmmc_test { + + namespace { + + constexpr inline const uintptr_t PMC = secmon::MemoryRegionPhysicalDevicePmc.GetAddress(); + + constexpr inline auto Port = sdmmc::Port_SdCard0; + alignas(8) constinit u8 g_sd_work_buffer[sdmmc::SdCardWorkBufferSize]; + + constexpr inline u32 SectorIndex = 0; + constexpr inline u32 SectorCount = 2; + + NORETURN void PmcMainReboot() { + /* Write enable to MAIN_RESET. */ + reg::Write(PMC + APBDEV_PMC_CNTRL, PMC_REG_BITS_ENUM(CNTRL_MAIN_RESET, ENABLE)); + + /* Wait forever until we're reset. */ + AMS_INFINITE_LOOP(); + } + + void CheckResult(const Result result) { + volatile u32 * const DEBUG = reinterpret_cast(0x4003C000); + if (R_FAILED(result)) { + DEBUG[1] = result.GetValue(); + PmcMainReboot(); + } + } + + } + + void Main() { + /* Perform butchered hwinit. */ + /* TODO: replace with simpler, non-C logic. */ + /* nx_hwinit(); */ + + /* Clear output buffer for debug. */ + std::memset((void *)0x40038000, 0xAA, 0x400); + + /* Normally, these pins get configured by boot sysmodule during initial pinmux config. */ + /* However, they're required to access the SD card. */ + { + const uintptr_t apb_misc = dd::QueryIoMapping(0x70000000, 0x4000); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_CLK, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_DOWN), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CLK_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_CMD, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_CMD_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT3, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT3_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT2, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT2_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT1, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT1_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_SDMMC1_DAT0, PINMUX_REG_BITS_ENUM(AUX_E_INPUT, ENABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_PUPD, PULL_UP), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, SDMMC1)); + + reg::ReadWrite(apb_misc + PINMUX_AUX_DMIC3_CLK, PINMUX_REG_BITS_ENUM(AUX_E_OD, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_E_INPUT, DISABLE), + PINMUX_REG_BITS_ENUM(AUX_TRISTATE, PASSTHROUGH), + PINMUX_REG_BITS_ENUM(AUX_SDMMC1_DAT0_PM, RSVD2)); + } + + /* Debug signaler. */ + volatile u32 * const DEBUG = reinterpret_cast(0x4003C000); + DEBUG[0] = 0; + DEBUG[1] = 0xAAAAAAAA; + + /* Initialize sdmmc library. */ + sdmmc::Initialize(Port); + DEBUG[0] = 1; + + sdmmc::SetSdCardWorkBuffer(Port, g_sd_work_buffer, sizeof(g_sd_work_buffer)); + DEBUG[0] = 2; + + Result result = sdmmc::Activate(Port); + DEBUG[0] = 3; + CheckResult(result); + + /* Read the first two sectors from disk. */ + void * const sector_dst = reinterpret_cast(0x40038000); + result = sdmmc::Read(sector_dst, SectorCount * sdmmc::SectorSize, Port, SectorIndex, SectorCount); + DEBUG[0] = 4; + CheckResult(result); + + /* Get the connection status. */ + sdmmc::SpeedMode speed_mode; + sdmmc::BusWidth bus_width; + result = sdmmc::CheckSdCardConnection(std::addressof(speed_mode), std::addressof(bus_width), Port); + + /* Save status for debug. */ + DEBUG[0] = 5; + DEBUG[1] = result.GetValue(); + DEBUG[2] = static_cast(speed_mode); + DEBUG[3] = static_cast(bus_width); + + /* Perform a reboot. */ + PmcMainReboot(); + } + + NORETURN void ExceptionHandler() { + PmcMainReboot(); + } + +} + +namespace ams::diag { + + void AbortImpl() { + sdmmc_test::ExceptionHandler(); + } + +} diff --git a/exosphere/sdmmc_test/source/sdmmc_test_start.s b/exosphere/sdmmc_test/source/sdmmc_test_start.s new file mode 100644 index 000000000..69c1204f7 --- /dev/null +++ b/exosphere/sdmmc_test/source/sdmmc_test_start.s @@ -0,0 +1,37 @@ +/* + * 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 . + */ + +.section .crt0.text._ZN3ams10sdmmc_test5StartEv, "ax", %progbits +.align 3 +.global _ZN3ams10sdmmc_test5StartEv +_ZN3ams10sdmmc_test5StartEv: + /* Switch to system mode, mask all interrupts, clear all flags */ + msr cpsr_cxsf, #0xDF + + /* Set the stack pointer. */ + ldr sp, =__stack_top__ + + /* Set our link register to the exception handler. */ + ldr lr, =_ZN3ams10sdmmc_test16ExceptionHandlerEv + + /* Call init array functions. */ + bl __libc_init_array + + /* Invoke main. */ + b _ZN3ams10sdmmc_test4MainEv + + /* Infinite loop. */ + 2: b 2b \ No newline at end of file diff --git a/exosphere/warmboot/source/warmboot_start.s b/exosphere/warmboot/source/warmboot_start.s index 18070e04a..dc6a56c2e 100644 --- a/exosphere/warmboot/source/warmboot_start.s +++ b/exosphere/warmboot/source/warmboot_start.s @@ -30,7 +30,7 @@ _ZN3ams8warmboot5StartEv: /* Invoke main. */ ldr r0, =_metadata - bl _ZN3ams8warmboot4MainEPKNS0_8MetadataE + b _ZN3ams8warmboot4MainEPKNS0_8MetadataE /* Infinite loop. */ 1: b 1b \ No newline at end of file diff --git a/libraries/config/common.mk b/libraries/config/common.mk index d5bf46fbb..1ca25d415 100644 --- a/libraries/config/common.mk +++ b/libraries/config/common.mk @@ -140,9 +140,9 @@ ATMOSPHERE_GCH_IDENTIFIER ?= ams_placeholder_gch_identifier # Rules for compiling pre-compiled headers #--------------------------------------------------------------------------------- %.hpp.gch/$(ATMOSPHERE_GCH_IDENTIFIER): %.hpp | %.hpp.gch - $(SILENTMSG) Precompiling $(notdir $<) for $(ATMOSPHERE_GCH_IDENTIFIER) + @echo Precompiling $(notdir $<) for $(ATMOSPHERE_GCH_IDENTIFIER) $(SILENTCMD)$(CXX) -w -x c++-header -MMD -MP -MQ$@ -MF $(DEPSDIR)/$(notdir $*).d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) %.hpp.gch: %.hpp - $(SILENTMSG) Precompiling $(notdir $<) + @echo Precompiling $(notdir $<) $(SILENTCMD)$(CXX) -w -x c++-header -MMD -MP -MQ$@ -MF $(DEPSDIR)/$(notdir $*).d $(CXXFLAGS) -c $< -o $@ $(ERROR_FILTER) diff --git a/libraries/libexosphere/arm.mk b/libraries/libexosphere/arm.mk index f82386e0f..a9b50909c 100644 --- a/libraries/libexosphere/arm.mk +++ b/libraries/libexosphere/arm.mk @@ -112,6 +112,7 @@ $(OFILES) : $(GCH_FILES) $(OFILES_SRC) : $(HFILES_BIN) libc.o: CFLAGS += -fno-builtin -fno-lto +libgcc_division.arch.arm.o: CFLAGS += -fno-builtin -fno-lto #--------------------------------------------------------------------------------- %_bin.h %.bin.o : %.bin diff --git a/libraries/libexosphere/include/exosphere.hpp b/libraries/libexosphere/include/exosphere.hpp index d7403cbf5..5fe60639c 100644 --- a/libraries/libexosphere/include/exosphere.hpp +++ b/libraries/libexosphere/include/exosphere.hpp @@ -17,7 +17,6 @@ #include #include -#include #include #include #include @@ -41,4 +40,3 @@ #include #include #include -#include \ No newline at end of file diff --git a/libraries/libexosphere/include/exosphere/tegra.hpp b/libraries/libexosphere/include/exosphere/tegra.hpp deleted file mode 100644 index 7f988871f..000000000 --- a/libraries/libexosphere/include/exosphere/tegra.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include diff --git a/libraries/libexosphere/include/exosphere/util.hpp b/libraries/libexosphere/include/exosphere/util.hpp index 088033e7c..ce31b63ea 100644 --- a/libraries/libexosphere/include/exosphere/util.hpp +++ b/libraries/libexosphere/include/exosphere/util.hpp @@ -20,14 +20,6 @@ namespace ams::util { void SetRegisterAddress(uintptr_t address); - u32 GetMicroSeconds(); - void WaitMicroSeconds(int us); - void ClearMemory(void *ptr, size_t size); - template requires std::integral && std::integral - constexpr T DivideUp(T x, U y) { - return (x + (y - 1)) / y; - } - } \ No newline at end of file diff --git a/libraries/libexosphere/source/libc/libexo_cxx.cpp b/libraries/libexosphere/source/libc/libexo_cxx.cpp new file mode 100644 index 000000000..64efc6786 --- /dev/null +++ b/libraries/libexosphere/source/libc/libexo_cxx.cpp @@ -0,0 +1,27 @@ +/* + * 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 . + */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* cxx implementation details to be stubbed here, as needed. */ +void __cxa_pure_virtual() { AMS_ABORT("pure virtual function call"); } + +#ifdef __cplusplus +} /* extern "C" */ +#endif diff --git a/libraries/libexosphere/source/libc/libgcc_division.arch.arm.c b/libraries/libexosphere/source/libc/libgcc_division.arch.arm.c new file mode 100644 index 000000000..22bc673a1 --- /dev/null +++ b/libraries/libexosphere/source/libc/libgcc_division.arch.arm.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2014, STMicroelectronics International N.V. + */ + +/* + * Form ABI specifications: + * int __aeabi_idiv(int numerator, int denominator); + * unsigned __aeabi_uidiv(unsigned numerator, unsigned denominator); + * + * typedef struct { int quot; int rem; } idiv_return; + * typedef struct { unsigned quot; unsigned rem; } uidiv_return; + * + * __value_in_regs idiv_return __aeabi_idivmod(int numerator, + * int *denominator); + * __value_in_regs uidiv_return __aeabi_uidivmod(unsigned *numerator, + * unsigned denominator); + */ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* struct qr - stores qutient/remainder to handle divmod EABI interfaces. */ +struct qr { + unsigned q; /* computed quotient */ + unsigned r; /* computed remainder */ + unsigned q_n; /* specficies if quotient shall be negative */ + unsigned r_n; /* specficies if remainder shall be negative */ +}; + +static void uint_div_qr(unsigned numerator, unsigned denominator, + struct qr *qr); + +/* returns in R0 and R1 by tail calling an asm function */ +unsigned __aeabi_uidivmod(unsigned numerator, unsigned denominator); + +unsigned __aeabi_uidiv(unsigned numerator, unsigned denominator); + +/* returns in R0 and R1 by tail calling an asm function */ +signed __aeabi_idivmod(signed numerator, signed denominator); + +signed __aeabi_idiv(signed numerator, signed denominator); + +/* + * __ste_idivmod_ret_t __aeabi_idivmod(signed numerator, signed denominator) + * Numerator and Denominator are received in R0 and R1. + * Where __ste_idivmod_ret_t is returned in R0 and R1. + * + * __ste_uidivmod_ret_t __aeabi_uidivmod(unsigned numerator, + * unsigned denominator) + * Numerator and Denominator are received in R0 and R1. + * Where __ste_uidivmod_ret_t is returned in R0 and R1. + */ +#ifdef __GNUC__ +signed ret_idivmod_values(signed quotient, signed remainder); +unsigned ret_uidivmod_values(unsigned quotient, unsigned remainder); +#else +#error "Compiler not supported" +#endif + +static void division_qr(unsigned n, unsigned p, struct qr *qr) +{ + unsigned i = 1, q = 0; + if (p == 0) { + qr->r = 0xFFFFFFFF; /* division by 0 */ + return; + } + + while ((p >> 31) == 0) { + i = i << 1; /* count the max division steps */ + p = p << 1; /* increase p until it has maximum size*/ + } + + while (i > 0) { + q = q << 1; /* write bit in q at index (size-1) */ + if (n >= p) + { + n -= p; + q++; + } + p = p >> 1; /* decrease p */ + i = i >> 1; /* decrease remaining size in q */ + } + qr->r = n; + qr->q = q; +} + +static void uint_div_qr(unsigned numerator, unsigned denominator, struct qr *qr) +{ + + division_qr(numerator, denominator, qr); + + /* negate quotient and/or remainder according to requester */ + if (qr->q_n) + qr->q = -qr->q; + if (qr->r_n) + qr->r = -qr->r; +} + +unsigned __aeabi_uidiv(unsigned numerator, unsigned denominator) +{ + struct qr qr = { .q_n = 0, .r_n = 0 }; + + uint_div_qr(numerator, denominator, &qr); + + return qr.q; +} + +unsigned __aeabi_uidivmod(unsigned numerator, unsigned denominator) +{ + struct qr qr = { .q_n = 0, .r_n = 0 }; + + uint_div_qr(numerator, denominator, &qr); + + return ret_uidivmod_values(qr.q, qr.r); +} + +signed __aeabi_idiv(signed numerator, signed denominator) +{ + struct qr qr = { .q_n = 0, .r_n = 0 }; + + if (((numerator < 0) && (denominator > 0)) || + ((numerator > 0) && (denominator < 0))) + qr.q_n = 1; /* quotient shall be negate */ + if (numerator < 0) { + numerator = -numerator; + qr.r_n = 1; /* remainder shall be negate */ + } + if (denominator < 0) + denominator = -denominator; + + uint_div_qr(numerator, denominator, &qr); + + return qr.q; +} + +signed __aeabi_idivmod(signed numerator, signed denominator) +{ + struct qr qr = { .q_n = 0, .r_n = 0 }; + + if (((numerator < 0) && (denominator > 0)) || + ((numerator > 0) && (denominator < 0))) + qr.q_n = 1; /* quotient shall be negate */ + if (numerator < 0) { + numerator = -numerator; + qr.r_n = 1; /* remainder shall be negate */ + } + if (denominator < 0) + denominator = -denominator; + + uint_div_qr(numerator, denominator, &qr); + + return ret_idivmod_values(qr.q, qr.r); +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif \ No newline at end of file diff --git a/libraries/libexosphere/source/libc/libgcc_division_asm.arch.arm.s b/libraries/libexosphere/source/libc/libgcc_division_asm.arch.arm.s new file mode 100644 index 000000000..a0acbace0 --- /dev/null +++ b/libraries/libexosphere/source/libc/libgcc_division_asm.arch.arm.s @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright (c) 2014, STMicroelectronics International N.V. + */ + +/* + * signed ret_idivmod_values(signed quot, signed rem); + * return quotient and remaining the EABI way (regs r0,r1) + */ +.section .text.ret_idivmod_values, "ax", %progbits +.globl ret_idivmod_values +.align 0 +.syntax unified +ret_idivmod_values: + bx lr +.type ret_idivmod_values, %function +.size ret_idivmod_values, .-ret_idivmod_values + +/* + * unsigned ret_uidivmod_values(unsigned quot, unsigned rem); + * return quotient and remaining the EABI way (regs r0,r1) + */ +.section .text.ret_uidivmod_values, "ax", %progbits +.globl ret_uidivmod_values +.align 0 +.syntax unified +ret_uidivmod_values: + bx lr +.type ret_uidivmod_values, %function +.size ret_uidivmod_values, .-ret_uidivmod_values diff --git a/libraries/libexosphere/source/libc/libgcc_thumb_case.arch.arm.s b/libraries/libexosphere/source/libc/libgcc_thumb_case.arch.arm.s new file mode 100644 index 000000000..2658ad61b --- /dev/null +++ b/libraries/libexosphere/source/libc/libgcc_thumb_case.arch.arm.s @@ -0,0 +1,35 @@ +/* Copyright (C) 1995-2018 Free Software Foundation, Inc. +This file is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation; either version 3, or (at your option) any +later version. +This file is distributed in the hope that 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. +Under Section 7 of GPL version 3, you are granted additional +permissions described in the GCC Runtime Library Exception, version +3.1, as published by the Free Software Foundation. +You should have received a copy of the GNU General Public License and +a copy of the GCC Runtime Library Exception along with this program; +see the files COPYING3 and COPYING.RUNTIME respectively. If not, see +. */ + + +.section .text.__gnu_thumb1_case_uqi, "ax", %progbits +.globl __gnu_thumb1_case_uqi +.align 0 +.thumb_func +.syntax unified +__gnu_thumb1_case_uqi: + push {r1} + mov r1, lr + lsrs r1, r1, #1 + lsls r1, r1, #1 + ldrb r1, [r1, r0] + lsls r1, r1, #1 + add lr, lr, r1 + pop {r1} + bx lr +.type __gnu_thumb1_case_uqi, %function +.size __gnu_thumb1_case_uqi, .-__gnu_thumb1_case_uqi diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp index aa05ae0a7..0557bb305 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_device_page_table.cpp @@ -14,7 +14,6 @@ * along with this program. If not, see . */ #include -#include "kern_mc_registers.hpp" #if defined(MESOSPHERE_BUILD_FOR_DEBUGGING) || defined(MESOSPHERE_BUILD_FOR_AUDITING) #define MESOSPHERE_ENABLE_MEMORY_CONTROLLER_INTERRUPT diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp index da7f1f6ad..913137d5f 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_k_sleep_manager.cpp @@ -78,13 +78,11 @@ namespace ams::kern::board::nintendo::nx { void WaitOtherCpuPowerOff() { constexpr u64 PmcPhysicalAddress = 0x7000E400; - constexpr u64 APBDEV_PMC_PWRGATE_STATUS = PmcPhysicalAddress + 0x38; - constexpr u32 PWRGATE_STATUS_CE123_MASK = ((1u << 3) - 1) << 9; u32 value; do { - bool res = smc::ReadWriteRegister(std::addressof(value), APBDEV_PMC_PWRGATE_STATUS, 0, 0); + bool res = smc::ReadWriteRegister(std::addressof(value), PmcPhysicalAddress + APBDEV_PMC_PWRGATE_STATUS, 0, 0); MESOSPHERE_ASSERT(res); } while ((value & PWRGATE_STATUS_CE123_MASK) != 0); } diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp index b6c232a05..e3f8755c9 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp @@ -19,11 +19,7 @@ #include "kern_bpmp_api.hpp" #include "kern_atomics_registers.hpp" -#include "kern_clkrst_registers.hpp" -#include "kern_evp_registers.hpp" -#include "kern_flow_registers.hpp" #include "kern_ictlr_registers.hpp" -#include "kern_pmc_registers.hpp" #include "kern_sema_registers.hpp" namespace ams::kern::board::nintendo::nx::lps { diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_mc_registers.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_mc_registers.hpp deleted file mode 100644 index edd925ee2..000000000 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_mc_registers.hpp +++ /dev/null @@ -1,526 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once - -#define MC_INTSTATUS 0x0 -#define MC_INTMASK 0x4 -#define MC_ERR_STATUS 0x8 -#define MC_ERR_ADR 0xc -#define MC_SMMU_CONFIG 0x10 -#define MC_SMMU_TLB_CONFIG 0x14 -#define MC_SMMU_PTC_CONFIG 0x18 -#define MC_SMMU_PTB_ASID 0x1c -#define MC_SMMU_PTB_DATA 0x20 -#define MC_SMMU_TLB_FLUSH 0x30 -#define MC_SMMU_PTC_FLUSH_0 0x34 -#define MC_SMMU_PTC_FLUSH_1 0x9b8 -#define MC_SMMU_ASID_SECURITY 0x38 -#define MC_SMMU_ASID_SECURITY_1 0x3c -#define MC_SMMU_ASID_SECURITY_2 0x9e0 -#define MC_SMMU_ASID_SECURITY_3 0x9e4 -#define MC_SMMU_ASID_SECURITY_4 0x9e8 -#define MC_SMMU_ASID_SECURITY_5 0x9ec -#define MC_SMMU_ASID_SECURITY_6 0x9f0 -#define MC_SMMU_ASID_SECURITY_7 0x9f4 -#define MC_SMMU_AFI_ASID 0x238 -#define MC_SMMU_AVPC_ASID 0x23c -#define MC_SMMU_DC_ASID 0x240 -#define MC_SMMU_DCB_ASID 0x244 -#define MC_SMMU_HC_ASID 0x250 -#define MC_SMMU_HDA_ASID 0x254 -#define MC_SMMU_ISP2_ASID 0x258 -#define MC_SMMU_MSENC_NVENC_ASID 0x264 -#define MC_SMMU_NV_ASID 0x268 -#define MC_SMMU_NV2_ASID 0x26c -#define MC_SMMU_PPCS_ASID 0x270 -#define MC_SMMU_SATA_ASID 0x274 -#define MC_SMMU_VDE_ASID 0x27c -#define MC_SMMU_VI_ASID 0x280 -#define MC_SMMU_VIC_ASID 0x284 -#define MC_SMMU_XUSB_HOST_ASID 0x288 -#define MC_SMMU_XUSB_DEV_ASID 0x28c -#define MC_SMMU_TSEC_ASID 0x294 -#define MC_SMMU_PPCS1_ASID 0x298 -#define MC_SMMU_DC1_ASID 0xa88 -#define MC_SMMU_SDMMC1A_ASID 0xa94 -#define MC_SMMU_SDMMC2A_ASID 0xa98 -#define MC_SMMU_SDMMC3A_ASID 0xa9c -#define MC_SMMU_SDMMC4A_ASID 0xaa0 -#define MC_SMMU_ISP2B_ASID 0xaa4 -#define MC_SMMU_GPU_ASID 0xaa8 -#define MC_SMMU_GPUB_ASID 0xaac -#define MC_SMMU_PPCS2_ASID 0xab0 -#define MC_SMMU_NVDEC_ASID 0xab4 -#define MC_SMMU_APE_ASID 0xab8 -#define MC_SMMU_SE_ASID 0xabc -#define MC_SMMU_NVJPG_ASID 0xac0 -#define MC_SMMU_HC1_ASID 0xac4 -#define MC_SMMU_SE1_ASID 0xac8 -#define MC_SMMU_AXIAP_ASID 0xacc -#define MC_SMMU_ETR_ASID 0xad0 -#define MC_SMMU_TSECB_ASID 0xad4 -#define MC_SMMU_TSEC1_ASID 0xad8 -#define MC_SMMU_TSECB1_ASID 0xadc -#define MC_SMMU_NVDEC1_ASID 0xae0 -#define MC_SMMU_TRANSLATION_ENABLE_0 0x228 -#define MC_SMMU_TRANSLATION_ENABLE_1 0x22c -#define MC_SMMU_TRANSLATION_ENABLE_2 0x230 -#define MC_SMMU_TRANSLATION_ENABLE_3 0x234 -#define MC_SMMU_TRANSLATION_ENABLE_4 0xb98 -#define MC_PCFIFO_CLIENT_CONFIG0 0xdd0 -#define MC_PCFIFO_CLIENT_CONFIG1 0xdd4 -#define MC_PCFIFO_CLIENT_CONFIG2 0xdd8 -#define MC_PCFIFO_CLIENT_CONFIG3 0xddc -#define MC_PCFIFO_CLIENT_CONFIG4 0xde0 -#define MC_EMEM_CFG 0x50 -#define MC_EMEM_ADR_CFG 0x54 -#define MC_EMEM_ADR_CFG_DEV0 0x58 -#define MC_EMEM_ADR_CFG_DEV1 0x5c -#define MC_EMEM_ADR_CFG_CHANNEL_MASK 0x60 -#define MC_EMEM_ADR_CFG_BANK_MASK_0 0x64 -#define MC_EMEM_ADR_CFG_BANK_MASK_1 0x68 -#define MC_EMEM_ADR_CFG_BANK_MASK_2 0x6c -#define MC_SECURITY_CFG0 0x70 -#define MC_SECURITY_CFG1 0x74 -#define MC_SECURITY_CFG3 0x9bc -#define MC_SECURITY_RSV 0x7c -#define MC_EMEM_ARB_CFG 0x90 -#define MC_EMEM_ARB_OUTSTANDING_REQ 0x94 -#define MC_EMEM_ARB_TIMING_RCD 0x98 -#define MC_EMEM_ARB_TIMING_RP 0x9c -#define MC_EMEM_ARB_TIMING_RC 0xa0 -#define MC_EMEM_ARB_TIMING_RAS 0xa4 -#define MC_EMEM_ARB_TIMING_FAW 0xa8 -#define MC_EMEM_ARB_TIMING_RRD 0xac -#define MC_EMEM_ARB_TIMING_RAP2PRE 0xb0 -#define MC_EMEM_ARB_TIMING_WAP2PRE 0xb4 -#define MC_EMEM_ARB_TIMING_R2R 0xb8 -#define MC_EMEM_ARB_TIMING_W2W 0xbc -#define MC_EMEM_ARB_TIMING_R2W 0xc0 -#define MC_EMEM_ARB_TIMING_W2R 0xc4 -#define MC_EMEM_ARB_TIMING_RFCPB 0x6c0 -#define MC_EMEM_ARB_TIMING_CCDMW 0x6c4 -#define MC_EMEM_ARB_REFPB_HP_CTRL 0x6f0 -#define MC_EMEM_ARB_REFPB_BANK_CTRL 0x6f4 -#define MC_EMEM_ARB_DA_TURNS 0xd0 -#define MC_EMEM_ARB_DA_COVERS 0xd4 -#define MC_EMEM_ARB_MISC0 0xd8 -#define MC_EMEM_ARB_MISC1 0xdc -#define MC_EMEM_ARB_MISC2 0xc8 -#define MC_EMEM_ARB_RING1_THROTTLE 0xe0 -#define MC_EMEM_ARB_RING3_THROTTLE 0xe4 -#define MC_EMEM_ARB_NISO_THROTTLE 0x6b0 -#define MC_EMEM_ARB_OVERRIDE 0xe8 -#define MC_EMEM_ARB_RSV 0xec -#define MC_CLKEN_OVERRIDE 0xf4 -#define MC_TIMING_CONTROL_DBG 0xf8 -#define MC_TIMING_CONTROL 0xfc -#define MC_STAT_CONTROL 0x100 -#define MC_STAT_STATUS 0x104 -#define MC_STAT_EMC_CLOCK_LIMIT 0x108 -#define MC_STAT_EMC_CLOCK_LIMIT_MSBS 0x10c -#define MC_STAT_EMC_CLOCKS 0x110 -#define MC_STAT_EMC_CLOCKS_MSBS 0x114 -#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_LO 0x118 -#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_LO 0x158 -#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_HI 0x11c -#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_HI 0x15c -#define MC_STAT_EMC_FILTER_SET0_ADR_LIMIT_UPPER 0xa20 -#define MC_STAT_EMC_FILTER_SET1_ADR_LIMIT_UPPER 0xa24 -#define MC_STAT_EMC_FILTER_SET0_VIRTUAL_ADR_LIMIT_LO 0x198 -#define MC_STAT_EMC_FILTER_SET1_VIRTUAL_ADR_LIMIT_LO 0x1a8 -#define MC_STAT_EMC_FILTER_SET0_VIRTUAL_ADR_LIMIT_HI 0x19c -#define MC_STAT_EMC_FILTER_SET1_VIRTUAL_ADR_LIMIT_HI 0x1ac -#define MC_STAT_EMC_FILTER_SET0_VIRTUAL_ADR_LIMIT_UPPER 0xa28 -#define MC_STAT_EMC_FILTER_SET1_VIRTUAL_ADR_LIMIT_UPPER 0xa2c -#define MC_STAT_EMC_FILTER_SET0_ASID 0x1a0 -#define MC_STAT_EMC_FILTER_SET1_ASID 0x1b0 -#define MC_STAT_EMC_FILTER_SET0_SLACK_LIMIT 0x120 -#define MC_STAT_EMC_FILTER_SET1_SLACK_LIMIT 0x160 -#define MC_STAT_EMC_FILTER_SET0_CLIENT_0 0x128 -#define MC_STAT_EMC_FILTER_SET1_CLIENT_0 0x168 -#define MC_STAT_EMC_FILTER_SET0_CLIENT_1 0x12c -#define MC_STAT_EMC_FILTER_SET1_CLIENT_1 0x16c -#define MC_STAT_EMC_FILTER_SET0_CLIENT_2 0x130 -#define MC_STAT_EMC_FILTER_SET1_CLIENT_2 0x170 -#define MC_STAT_EMC_FILTER_SET0_CLIENT_3 0x134 -#define MC_STAT_EMC_FILTER_SET0_CLIENT_4 0xb88 -#define MC_STAT_EMC_FILTER_SET1_CLIENT_3 0x174 -#define MC_STAT_EMC_FILTER_SET1_CLIENT_4 0xb8c -#define MC_STAT_EMC_SET0_COUNT 0x138 -#define MC_STAT_EMC_SET0_COUNT_MSBS 0x13c -#define MC_STAT_EMC_SET1_COUNT 0x178 -#define MC_STAT_EMC_SET1_COUNT_MSBS 0x17c -#define MC_STAT_EMC_SET0_SLACK_ACCUM 0x140 -#define MC_STAT_EMC_SET0_SLACK_ACCUM_MSBS 0x144 -#define MC_STAT_EMC_SET1_SLACK_ACCUM 0x180 -#define MC_STAT_EMC_SET1_SLACK_ACCUM_MSBS 0x184 -#define MC_STAT_EMC_SET0_HISTO_COUNT 0x148 -#define MC_STAT_EMC_SET0_HISTO_COUNT_MSBS 0x14c -#define MC_STAT_EMC_SET1_HISTO_COUNT 0x188 -#define MC_STAT_EMC_SET1_HISTO_COUNT_MSBS 0x18c -#define MC_STAT_EMC_SET0_MINIMUM_SLACK_OBSERVED 0x150 -#define MC_STAT_EMC_SET1_MINIMUM_SLACK_OBSERVED 0x190 -#define MC_STAT_EMC_SET0_IDLE_CYCLE_COUNT 0x1b8 -#define MC_STAT_EMC_SET0_IDLE_CYCL_COUNT_MSBS 0x1bc -#define MC_STAT_EMC_SET1_IDLE_CYCLE_COUNT 0x1c8 -#define MC_STAT_EMC_SET1_IDLE_CYCL_COUNT_MSBS 0x1cc -#define MC_STAT_EMC_SET0_IDLE_CYCLE_PARTITION_SELECT 0x1c0 -#define MC_STAT_EMC_SET1_IDLE_CYCLE_PARTITION_SELECT 0x1d0 -#define MC_CLIENT_HOTRESET_CTRL 0x200 -#define MC_CLIENT_HOTRESET_CTRL_1 0x970 -#define MC_CLIENT_HOTRESET_STATUS 0x204 -#define MC_CLIENT_HOTRESET_STATUS_1 0x974 -#define MC_EMEM_ARB_ISOCHRONOUS_0 0x208 -#define MC_EMEM_ARB_ISOCHRONOUS_1 0x20c -#define MC_EMEM_ARB_ISOCHRONOUS_2 0x210 -#define MC_EMEM_ARB_ISOCHRONOUS_3 0x214 -#define MC_EMEM_ARB_ISOCHRONOUS_4 0xb94 -#define MC_EMEM_ARB_HYSTERESIS_0 0x218 -#define MC_EMEM_ARB_HYSTERESIS_1 0x21c -#define MC_EMEM_ARB_HYSTERESIS_2 0x220 -#define MC_EMEM_ARB_HYSTERESIS_3 0x224 -#define MC_EMEM_ARB_HYSTERESIS_4 0xb84 -#define MC_EMEM_ARB_DHYSTERESIS_0 0xbb0 -#define MC_EMEM_ARB_DHYSTERESIS_1 0xbb4 -#define MC_EMEM_ARB_DHYSTERESIS_2 0xbb8 -#define MC_EMEM_ARB_DHYSTERESIS_3 0xbbc -#define MC_EMEM_ARB_DHYSTERESIS_4 0xbc0 -#define MC_EMEM_ARB_DHYST_CTRL 0xbcc -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_0 0xbd0 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_1 0xbd4 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_2 0xbd8 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_3 0xbdc -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_4 0xbe0 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_5 0xbe4 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_6 0xbe8 -#define MC_EMEM_ARB_DHYST_TIMEOUT_UTIL_7 0xbec -#define MC_RESERVED_RSV 0x3fc -#define MC_DISB_EXTRA_SNAP_LEVELS 0x408 -#define MC_APB_EXTRA_SNAP_LEVELS 0x2a4 -#define MC_AHB_EXTRA_SNAP_LEVELS 0x2a0 -#define MC_USBD_EXTRA_SNAP_LEVELS 0xa18 -#define MC_ISP_EXTRA_SNAP_LEVELS 0xa08 -#define MC_AUD_EXTRA_SNAP_LEVELS 0xa10 -#define MC_MSE_EXTRA_SNAP_LEVELS 0x40c -#define MC_GK2_EXTRA_SNAP_LEVELS 0xa40 -#define MC_A9AVPPC_EXTRA_SNAP_LEVELS 0x414 -#define MC_FTOP_EXTRA_SNAP_LEVELS 0x2bc -#define MC_JPG_EXTRA_SNAP_LEVELS 0xa3c -#define MC_HOST_EXTRA_SNAP_LEVELS 0xa14 -#define MC_SAX_EXTRA_SNAP_LEVELS 0x2c0 -#define MC_DIS_EXTRA_SNAP_LEVELS 0x2ac -#define MC_VICPC_EXTRA_SNAP_LEVELS 0xa1c -#define MC_HDAPC_EXTRA_SNAP_LEVELS 0xa48 -#define MC_AVP_EXTRA_SNAP_LEVELS 0x2a8 -#define MC_USBX_EXTRA_SNAP_LEVELS 0x404 -#define MC_PCX_EXTRA_SNAP_LEVELS 0x2b8 -#define MC_SD_EXTRA_SNAP_LEVELS 0xa04 -#define MC_DFD_EXTRA_SNAP_LEVELS 0xa4c -#define MC_VE_EXTRA_SNAP_LEVELS 0x2d8 -#define MC_GK_EXTRA_SNAP_LEVELS 0xa00 -#define MC_VE2_EXTRA_SNAP_LEVELS 0x410 -#define MC_SDM_EXTRA_SNAP_LEVELS 0xa44 -#define MC_VIDEO_PROTECT_BOM 0x648 -#define MC_VIDEO_PROTECT_SIZE_MB 0x64c -#define MC_VIDEO_PROTECT_BOM_ADR_HI 0x978 -#define MC_VIDEO_PROTECT_REG_CTRL 0x650 -#define MC_ERR_VPR_STATUS 0x654 -#define MC_ERR_VPR_ADR 0x658 -#define MC_VIDEO_PROTECT_VPR_OVERRIDE 0x418 -#define MC_VIDEO_PROTECT_VPR_OVERRIDE1 0x590 -#define MC_IRAM_BOM 0x65c -#define MC_IRAM_TOM 0x660 -#define MC_IRAM_ADR_HI 0x980 -#define MC_IRAM_REG_CTRL 0x964 -#define MC_EMEM_CFG_ACCESS_CTRL 0x664 -#define MC_TZ_SECURITY_CTRL 0x668 -#define MC_EMEM_ARB_OUTSTANDING_REQ_RING3 0x66c -#define MC_EMEM_ARB_OUTSTANDING_REQ_NISO 0x6b4 -#define MC_EMEM_ARB_RING0_THROTTLE_MASK 0x6bc -#define MC_EMEM_ARB_NISO_THROTTLE_MASK 0x6b8 -#define MC_EMEM_ARB_NISO_THROTTLE_MASK_1 0xb80 -#define MC_SEC_CARVEOUT_BOM 0x670 -#define MC_SEC_CARVEOUT_SIZE_MB 0x674 -#define MC_SEC_CARVEOUT_ADR_HI 0x9d4 -#define MC_SEC_CARVEOUT_REG_CTRL 0x678 -#define MC_ERR_SEC_STATUS 0x67c -#define MC_ERR_SEC_ADR 0x680 -#define MC_PC_IDLE_CLOCK_GATE_CONFIG 0x684 -#define MC_STUTTER_CONTROL 0x688 -#define MC_RESERVED_RSV_1 0x958 -#define MC_DVFS_PIPE_SELECT 0x95c -#define MC_AHB_PTSA_MIN 0x4e0 -#define MC_AUD_PTSA_MIN 0x54c -#define MC_MLL_MPCORER_PTSA_RATE 0x44c -#define MC_RING2_PTSA_RATE 0x440 -#define MC_USBD_PTSA_RATE 0x530 -#define MC_USBX_PTSA_MIN 0x528 -#define MC_USBD_PTSA_MIN 0x534 -#define MC_APB_PTSA_MAX 0x4f0 -#define MC_JPG_PTSA_RATE 0x584 -#define MC_DIS_PTSA_MIN 0x420 -#define MC_AVP_PTSA_MAX 0x4fc -#define MC_AVP_PTSA_RATE 0x4f4 -#define MC_RING1_PTSA_MIN 0x480 -#define MC_DIS_PTSA_MAX 0x424 -#define MC_SD_PTSA_MAX 0x4d8 -#define MC_MSE_PTSA_RATE 0x4c4 -#define MC_VICPC_PTSA_MIN 0x558 -#define MC_PCX_PTSA_MAX 0x4b4 -#define MC_ISP_PTSA_RATE 0x4a0 -#define MC_A9AVPPC_PTSA_MIN 0x48c -#define MC_RING2_PTSA_MAX 0x448 -#define MC_AUD_PTSA_RATE 0x548 -#define MC_HOST_PTSA_MIN 0x51c -#define MC_MLL_MPCORER_PTSA_MAX 0x454 -#define MC_SD_PTSA_MIN 0x4d4 -#define MC_RING1_PTSA_RATE 0x47c -#define MC_JPG_PTSA_MIN 0x588 -#define MC_HDAPC_PTSA_MIN 0x62c -#define MC_AVP_PTSA_MIN 0x4f8 -#define MC_JPG_PTSA_MAX 0x58c -#define MC_VE_PTSA_MAX 0x43c -#define MC_DFD_PTSA_MAX 0x63c -#define MC_VICPC_PTSA_RATE 0x554 -#define MC_GK_PTSA_MAX 0x544 -#define MC_VICPC_PTSA_MAX 0x55c -#define MC_SDM_PTSA_MAX 0x624 -#define MC_SAX_PTSA_RATE 0x4b8 -#define MC_PCX_PTSA_MIN 0x4b0 -#define MC_APB_PTSA_MIN 0x4ec -#define MC_GK2_PTSA_MIN 0x614 -#define MC_PCX_PTSA_RATE 0x4ac -#define MC_RING1_PTSA_MAX 0x484 -#define MC_HDAPC_PTSA_RATE 0x628 -#define MC_MLL_MPCORER_PTSA_MIN 0x450 -#define MC_GK2_PTSA_MAX 0x618 -#define MC_AUD_PTSA_MAX 0x550 -#define MC_GK2_PTSA_RATE 0x610 -#define MC_ISP_PTSA_MAX 0x4a8 -#define MC_DISB_PTSA_RATE 0x428 -#define MC_VE2_PTSA_MAX 0x49c -#define MC_DFD_PTSA_MIN 0x638 -#define MC_FTOP_PTSA_RATE 0x50c -#define MC_A9AVPPC_PTSA_RATE 0x488 -#define MC_VE2_PTSA_MIN 0x498 -#define MC_USBX_PTSA_MAX 0x52c -#define MC_DIS_PTSA_RATE 0x41c -#define MC_USBD_PTSA_MAX 0x538 -#define MC_A9AVPPC_PTSA_MAX 0x490 -#define MC_USBX_PTSA_RATE 0x524 -#define MC_FTOP_PTSA_MAX 0x514 -#define MC_HDAPC_PTSA_MAX 0x630 -#define MC_SD_PTSA_RATE 0x4d0 -#define MC_DFD_PTSA_RATE 0x634 -#define MC_FTOP_PTSA_MIN 0x510 -#define MC_SDM_PTSA_RATE 0x61c -#define MC_AHB_PTSA_RATE 0x4dc -#define MC_SMMU_SMMU_PTSA_MAX 0x460 -#define MC_RING2_PTSA_MIN 0x444 -#define MC_SDM_PTSA_MIN 0x620 -#define MC_APB_PTSA_RATE 0x4e8 -#define MC_MSE_PTSA_MIN 0x4c8 -#define MC_HOST_PTSA_RATE 0x518 -#define MC_VE_PTSA_RATE 0x434 -#define MC_AHB_PTSA_MAX 0x4e4 -#define MC_SAX_PTSA_MIN 0x4bc -#define MC_SMMU_SMMU_PTSA_MIN 0x45c -#define MC_ISP_PTSA_MIN 0x4a4 -#define MC_HOST_PTSA_MAX 0x520 -#define MC_SAX_PTSA_MAX 0x4c0 -#define MC_VE_PTSA_MIN 0x438 -#define MC_GK_PTSA_MIN 0x540 -#define MC_MSE_PTSA_MAX 0x4cc -#define MC_DISB_PTSA_MAX 0x430 -#define MC_DISB_PTSA_MIN 0x42c -#define MC_SMMU_SMMU_PTSA_RATE 0x458 -#define MC_VE2_PTSA_RATE 0x494 -#define MC_GK_PTSA_RATE 0x53c -#define MC_PTSA_GRANT_DECREMENT 0x960 -#define MC_LATENCY_ALLOWANCE_AVPC_0 0x2e4 -#define MC_LATENCY_ALLOWANCE_AXIAP_0 0x3a0 -#define MC_LATENCY_ALLOWANCE_XUSB_1 0x380 -#define MC_LATENCY_ALLOWANCE_ISP2B_0 0x384 -#define MC_LATENCY_ALLOWANCE_SDMMCAA_0 0x3bc -#define MC_LATENCY_ALLOWANCE_SDMMCA_0 0x3b8 -#define MC_LATENCY_ALLOWANCE_ISP2_0 0x370 -#define MC_LATENCY_ALLOWANCE_SE_0 0x3e0 -#define MC_LATENCY_ALLOWANCE_ISP2_1 0x374 -#define MC_LATENCY_ALLOWANCE_DC_0 0x2e8 -#define MC_LATENCY_ALLOWANCE_VIC_0 0x394 -#define MC_LATENCY_ALLOWANCE_DCB_1 0x2f8 -#define MC_LATENCY_ALLOWANCE_NVDEC_0 0x3d8 -#define MC_LATENCY_ALLOWANCE_DCB_2 0x2fc -#define MC_LATENCY_ALLOWANCE_TSEC_0 0x390 -#define MC_LATENCY_ALLOWANCE_DC_2 0x2f0 -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0AB 0x694 -#define MC_LATENCY_ALLOWANCE_PPCS_1 0x348 -#define MC_LATENCY_ALLOWANCE_XUSB_0 0x37c -#define MC_LATENCY_ALLOWANCE_PPCS_0 0x344 -#define MC_LATENCY_ALLOWANCE_TSECB_0 0x3f0 -#define MC_LATENCY_ALLOWANCE_AFI_0 0x2e0 -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0B 0x698 -#define MC_LATENCY_ALLOWANCE_DC_1 0x2ec -#define MC_LATENCY_ALLOWANCE_APE_0 0x3dc -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0C 0x6a0 -#define MC_LATENCY_ALLOWANCE_A9AVP_0 0x3a4 -#define MC_LATENCY_ALLOWANCE_GPU2_0 0x3e8 -#define MC_LATENCY_ALLOWANCE_DCB_0 0x2f4 -#define MC_LATENCY_ALLOWANCE_HC_1 0x314 -#define MC_LATENCY_ALLOWANCE_SDMMC_0 0x3c0 -#define MC_LATENCY_ALLOWANCE_NVJPG_0 0x3e4 -#define MC_LATENCY_ALLOWANCE_PTC_0 0x34c -#define MC_LATENCY_ALLOWANCE_ETR_0 0x3ec -#define MC_LATENCY_ALLOWANCE_MPCORE_0 0x320 -#define MC_LATENCY_ALLOWANCE_VI2_0 0x398 -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0BB 0x69c -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0CB 0x6a4 -#define MC_LATENCY_ALLOWANCE_SATA_0 0x350 -#define MC_SCALED_LATENCY_ALLOWANCE_DISPLAY0A 0x690 -#define MC_LATENCY_ALLOWANCE_HC_0 0x310 -#define MC_LATENCY_ALLOWANCE_DC_3 0x3c8 -#define MC_LATENCY_ALLOWANCE_GPU_0 0x3ac -#define MC_LATENCY_ALLOWANCE_SDMMCAB_0 0x3c4 -#define MC_LATENCY_ALLOWANCE_ISP2B_1 0x388 -#define MC_LATENCY_ALLOWANCE_NVENC_0 0x328 -#define MC_LATENCY_ALLOWANCE_HDA_0 0x318 -#define MC_MIN_LENGTH_APE_0 0xb34 -#define MC_MIN_LENGTH_DCB_2 0x8a8 -#define MC_MIN_LENGTH_A9AVP_0 0x950 -#define MC_MIN_LENGTH_TSEC_0 0x93c -#define MC_MIN_LENGTH_DC_1 0x898 -#define MC_MIN_LENGTH_AXIAP_0 0x94c -#define MC_MIN_LENGTH_ISP2B_0 0x930 -#define MC_MIN_LENGTH_VI2_0 0x944 -#define MC_MIN_LENGTH_DCB_0 0x8a0 -#define MC_MIN_LENGTH_DCB_1 0x8a4 -#define MC_MIN_LENGTH_PPCS_1 0x8f4 -#define MC_MIN_LENGTH_NVJPG_0 0xb3c -#define MC_MIN_LENGTH_HDA_0 0x8c4 -#define MC_MIN_LENGTH_NVENC_0 0x8d4 -#define MC_MIN_LENGTH_SDMMC_0 0xb18 -#define MC_MIN_LENGTH_ISP2B_1 0x934 -#define MC_MIN_LENGTH_HC_1 0x8c0 -#define MC_MIN_LENGTH_DC_3 0xb20 -#define MC_MIN_LENGTH_AVPC_0 0x890 -#define MC_MIN_LENGTH_VIC_0 0x940 -#define MC_MIN_LENGTH_ISP2_0 0x91c -#define MC_MIN_LENGTH_HC_0 0x8bc -#define MC_MIN_LENGTH_SE_0 0xb38 -#define MC_MIN_LENGTH_NVDEC_0 0xb30 -#define MC_MIN_LENGTH_SATA_0 0x8fc -#define MC_MIN_LENGTH_DC_0 0x894 -#define MC_MIN_LENGTH_XUSB_1 0x92c -#define MC_MIN_LENGTH_DC_2 0x89c -#define MC_MIN_LENGTH_SDMMCAA_0 0xb14 -#define MC_MIN_LENGTH_GPU_0 0xb04 -#define MC_MIN_LENGTH_ETR_0 0xb44 -#define MC_MIN_LENGTH_AFI_0 0x88c -#define MC_MIN_LENGTH_PPCS_0 0x8f0 -#define MC_MIN_LENGTH_ISP2_1 0x920 -#define MC_MIN_LENGTH_XUSB_0 0x928 -#define MC_MIN_LENGTH_MPCORE_0 0x8cc -#define MC_MIN_LENGTH_TSECB_0 0xb48 -#define MC_MIN_LENGTH_SDMMCA_0 0xb10 -#define MC_MIN_LENGTH_GPU2_0 0xb40 -#define MC_MIN_LENGTH_SDMMCAB_0 0xb1c -#define MC_MIN_LENGTH_PTC_0 0x8f8 -#define MC_EMEM_ARB_OVERRIDE_1 0x968 -#define MC_VIDEO_PROTECT_GPU_OVERRIDE_0 0x984 -#define MC_VIDEO_PROTECT_GPU_OVERRIDE_1 0x988 -#define MC_EMEM_ARB_STATS_0 0x990 -#define MC_EMEM_ARB_STATS_1 0x994 -#define MC_MTS_CARVEOUT_BOM 0x9a0 -#define MC_MTS_CARVEOUT_SIZE_MB 0x9a4 -#define MC_MTS_CARVEOUT_ADR_HI 0x9a8 -#define MC_MTS_CARVEOUT_REG_CTRL 0x9ac -#define MC_ERR_MTS_STATUS 0x9b0 -#define MC_ERR_MTS_ADR 0x9b4 -#define MC_ERR_GENERALIZED_CARVEOUT_STATUS 0xc00 -#define MC_ERR_GENERALIZED_CARVEOUT_ADR 0xc04 -#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS2 0xd74 -#define MC_SECURITY_CARVEOUT4_CFG0 0xcf8 -#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS2 0xd10 -#define MC_SECURITY_CARVEOUT4_SIZE_128KB 0xd04 -#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS4 0xc28 -#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS1 0xc30 -#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS4 0xc8c -#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS0 0xd1c -#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS1 0xd70 -#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS0 0xc2c -#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS4 0xd7c -#define MC_SECURITY_CARVEOUT3_SIZE_128KB 0xcb4 -#define MC_SECURITY_CARVEOUT2_CFG0 0xc58 -#define MC_SECURITY_CARVEOUT1_CFG0 0xc08 -#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS2 0xc84 -#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS0 0xc68 -#define MC_SECURITY_CARVEOUT3_BOM 0xcac -#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS2 0xc70 -#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS3 0xd78 -#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS0 0xc7c -#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS4 0xd18 -#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS1 0xcbc -#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS3 0xc38 -#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS2 0xc34 -#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS2 0xcc0 -#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS2 0xd60 -#define MC_SECURITY_CARVEOUT3_CFG0 0xca8 -#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS0 0xcb8 -#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS3 0xc88 -#define MC_SECURITY_CARVEOUT2_SIZE_128KB 0xc64 -#define MC_SECURITY_CARVEOUT5_BOM_HI 0xd50 -#define MC_SECURITY_CARVEOUT1_SIZE_128KB 0xc14 -#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS3 0xd14 -#define MC_SECURITY_CARVEOUT1_BOM 0xc0c -#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS4 0xd2c -#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS4 0xd68 -#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS4 0xcc8 -#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS0 0xd58 -#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS2 0xd24 -#define MC_SECURITY_CARVEOUT3_CLIENT_ACCESS3 0xcc4 -#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS4 0xc78 -#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS1 0xc1c -#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS0 0xc18 -#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS3 0xd28 -#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS1 0xd5c -#define MC_SECURITY_CARVEOUT3_BOM_HI 0xcb0 -#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS3 0xcd8 -#define MC_SECURITY_CARVEOUT2_BOM_HI 0xc60 -#define MC_SECURITY_CARVEOUT4_BOM_HI 0xd00 -#define MC_SECURITY_CARVEOUT5_CLIENT_ACCESS3 0xd64 -#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS4 0xcdc -#define MC_SECURITY_CARVEOUT2_CLIENT_FORCE_INTERNAL_ACCESS1 0xc80 -#define MC_SECURITY_CARVEOUT5_SIZE_128KB 0xd54 -#define MC_SECURITY_CARVEOUT4_CLIENT_FORCE_INTERNAL_ACCESS1 0xd20 -#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS2 0xcd4 -#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS1 0xd0c -#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS3 0xc74 -#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS0 0xccc -#define MC_SECURITY_CARVEOUT4_BOM 0xcfc -#define MC_SECURITY_CARVEOUT5_CFG0 0xd48 -#define MC_SECURITY_CARVEOUT2_BOM 0xc5c -#define MC_SECURITY_CARVEOUT5_BOM 0xd4c -#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS3 0xc24 -#define MC_SECURITY_CARVEOUT5_CLIENT_FORCE_INTERNAL_ACCESS0 0xd6c -#define MC_SECURITY_CARVEOUT3_CLIENT_FORCE_INTERNAL_ACCESS1 0xcd0 -#define MC_SECURITY_CARVEOUT1_BOM_HI 0xc10 -#define MC_SECURITY_CARVEOUT1_CLIENT_ACCESS2 0xc20 -#define MC_SECURITY_CARVEOUT1_CLIENT_FORCE_INTERNAL_ACCESS4 0xc3c -#define MC_SECURITY_CARVEOUT2_CLIENT_ACCESS1 0xc6c -#define MC_SECURITY_CARVEOUT4_CLIENT_ACCESS0 0xd08 -#define MC_ERR_APB_ASID_UPDATE_STATUS 0x9d0 -#define MC_DA_CONFIG0 0x9dc diff --git a/libraries/libmesosphere/source/kern_debug_log.cpp b/libraries/libmesosphere/source/kern_debug_log.cpp index 861859b8f..9a14405bd 100644 --- a/libraries/libmesosphere/source/kern_debug_log.cpp +++ b/libraries/libmesosphere/source/kern_debug_log.cpp @@ -17,386 +17,9 @@ #include "kern_debug_log_impl.hpp" namespace ams::kern { - #pragma GCC push_options - #pragma GCC optimize ("-Os") namespace { - /* Useful definitions for our VSNPrintf implementation. */ - enum FormatSpecifierFlag : u32 { - FormatSpecifierFlag_None = 0, - FormatSpecifierFlag_EmptySign = (1 << 0), - FormatSpecifierFlag_ForceSign = (1 << 1), - FormatSpecifierFlag_Hash = (1 << 2), - FormatSpecifierFlag_LeftJustify = (1 << 3), - FormatSpecifierFlag_ZeroPad = (1 << 4), - FormatSpecifierFlag_Char = (1 << 5), - FormatSpecifierFlag_Short = (1 << 6), - FormatSpecifierFlag_Long = (1 << 7), - FormatSpecifierFlag_LongLong = (1 << 8), - FormatSpecifierFlag_Uppercase = (1 << 9), - FormatSpecifierFlag_HasPrecision = (1 << 10), - }; - - using FormatSpecifierFlagStorage = std::underlying_type::type; - - constexpr ALWAYS_INLINE bool IsDigit(char c) { - return '0' <= c && c <= '9'; - } - - constexpr ALWAYS_INLINE u32 ParseU32(const char *&str) { - u32 value = 0; - do { - value = (value * 10) + static_cast(*(str++) - '0'); - } while (IsDigit(*str)); - return value; - } - - constexpr ALWAYS_INLINE size_t Strnlen(const char *str, size_t max) { - const char *cur = str; - while (*cur && max--) { - cur++; - } - return static_cast(cur - str); - } - - ALWAYS_INLINE void VSNPrintfImpl(char * const dst, const size_t dst_size, const char *format, ::std::va_list vl) { - size_t dst_index = 0; - - auto WriteCharacter = [dst, dst_size, &dst_index](char c) ALWAYS_INLINE_LAMBDA { - if (dst_index < dst_size) { - dst[dst_index++] = c; - } - }; - - /* Loop over every character in the string, looking for format specifiers. */ - while (*format) { - if (const char c = *(format++); c != '%') { - WriteCharacter(c); - continue; - } - - /* We have to parse a format specifier. */ - /* Start by parsing flags. */ - FormatSpecifierFlagStorage flags = FormatSpecifierFlag_None; - auto SetFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags |= f; }; - auto ClearFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags &= ~f; }; - auto HasFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { return (flags & f) != 0; }; - { - bool parsed_flags = false; - while (!parsed_flags) { - switch (*format) { - case ' ': SetFlag(FormatSpecifierFlag_EmptySign); format++; break; - case '+': SetFlag(FormatSpecifierFlag_ForceSign); format++; break; - case '#': SetFlag(FormatSpecifierFlag_Hash); format++; break; - case '-': SetFlag(FormatSpecifierFlag_LeftJustify); format++; break; - case '0': SetFlag(FormatSpecifierFlag_ZeroPad); format++; break; - default: - parsed_flags = true; - break; - } - } - } - - /* Next, parse width. */ - u32 width = 0; - if (IsDigit(*format)) { - /* Integer width. */ - width = ParseU32(format); - } else if (*format == '*') { - /* Dynamic width. */ - const int _width = va_arg(vl, int); - if (_width >= 0) { - width = static_cast(_width); - } else { - SetFlag(FormatSpecifierFlag_LeftJustify); - width = static_cast(-_width); - } - format++; - } - - /* Next, parse precision if present. */ - u32 precision = 0; - if (*format == '.') { - SetFlag(FormatSpecifierFlag_HasPrecision); - format++; - - if (IsDigit(*format)) { - /* Integer precision. */ - precision = ParseU32(format); - } else if (*format == '*') { - /* Dynamic precision. */ - const int _precision = va_arg(vl, int); - if (_precision > 0) { - precision = static_cast(_precision); - } - format++; - } - } - - /* Parse length. */ - constexpr bool SizeIsLong = sizeof(size_t) == sizeof(long); - constexpr bool IntMaxIsLong = sizeof(intmax_t) == sizeof(long); - constexpr bool PtrDiffIsLong = sizeof(ptrdiff_t) == sizeof(long); - switch (*format) { - case 'z': - SetFlag(SizeIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); - format++; - break; - case 'j': - SetFlag(IntMaxIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); - format++; - break; - case 't': - SetFlag(PtrDiffIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); - format++; - break; - case 'h': - SetFlag(FormatSpecifierFlag_Short); - format++; - if (*format == 'h') { - SetFlag(FormatSpecifierFlag_Char); - format++; - } - break; - case 'l': - SetFlag(FormatSpecifierFlag_Long); - format++; - if (*format == 'l') { - SetFlag(FormatSpecifierFlag_LongLong); - format++; - } - break; - default: - break; - } - - const char specifier = *(format++); - switch (specifier) { - case 'p': - if constexpr (sizeof(uintptr_t) == sizeof(long long)) { - SetFlag(FormatSpecifierFlag_LongLong); - } else { - SetFlag(FormatSpecifierFlag_Long); - } - SetFlag(FormatSpecifierFlag_Hash); - [[fallthrough]]; - case 'd': - case 'i': - case 'u': - case 'b': - case 'o': - case 'x': - case 'X': - { - /* Determine the base to print with. */ - u32 base; - switch (specifier) { - case 'b': - base = 2; - break; - case 'o': - base = 8; - break; - case 'X': - SetFlag(FormatSpecifierFlag_Uppercase); - [[fallthrough]]; - case 'p': - case 'x': - base = 16; - break; - default: - base = 10; - ClearFlag(FormatSpecifierFlag_Hash); - break; - } - - /* Precision implies no zero-padding. */ - if (HasFlag(FormatSpecifierFlag_HasPrecision)) { - ClearFlag(FormatSpecifierFlag_ZeroPad); - } - - /* Unsigned types don't get signs. */ - const bool is_unsigned = base != 10 || specifier == 'u'; - if (is_unsigned) { - ClearFlag(FormatSpecifierFlag_EmptySign); - ClearFlag(FormatSpecifierFlag_ForceSign); - } - - auto PrintInteger = [&](bool negative, uintmax_t value) { - constexpr size_t BufferSize = 64; /* Binary digits for 64-bit numbers may use 64 digits. */ - char buf[BufferSize]; - size_t len = 0; - - /* No hash flag for zero. */ - if (value == 0) { - ClearFlag(FormatSpecifierFlag_Hash); - } - - if (!HasFlag(FormatSpecifierFlag_HasPrecision) || value != 0) { - do { - const char digit = static_cast(value % base); - buf[len++] = (digit < 10) ? ('0' + digit) : ((HasFlag(FormatSpecifierFlag_Uppercase) ? 'A' : 'a') + digit - 10); - value /= base; - } while (value); - } - - /* Determine our prefix length. */ - size_t prefix_len = 0; - const bool has_sign = negative || HasFlag(FormatSpecifierFlag_ForceSign) || HasFlag(FormatSpecifierFlag_EmptySign); - if (has_sign) { - prefix_len++; - } - if (HasFlag(FormatSpecifierFlag_Hash)) { - prefix_len += (base != 8) ? 2 : 1; - } - - /* Determine zero-padding count. */ - size_t num_zeroes = (len < precision) ? precision - len : 0; - if (!HasFlag(FormatSpecifierFlag_LeftJustify) && HasFlag(FormatSpecifierFlag_ZeroPad)) { - num_zeroes = (len + prefix_len < width) ? width - len - prefix_len : 0; - } - - /* Print out left padding. */ - if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { - for (size_t i = len + prefix_len + num_zeroes; i < static_cast(width); i++) { - WriteCharacter(' '); - } - } - - /* Print out sign. */ - if (negative) { - WriteCharacter('-'); - } else if (HasFlag(FormatSpecifierFlag_ForceSign)) { - WriteCharacter('+'); - } else if (HasFlag(FormatSpecifierFlag_EmptySign)) { - WriteCharacter(' '); - } - - /* Print out base prefix. */ - if (HasFlag(FormatSpecifierFlag_Hash)) { - WriteCharacter('0'); - if (base == 2) { - WriteCharacter('b'); - } else if (base == 16) { - WriteCharacter('x'); - } - } - - /* Print out zeroes. */ - for (size_t i = 0; i < num_zeroes; i++) { - WriteCharacter('0'); - } - - /* Print out digits. */ - for (size_t i = 0; i < len; i++) { - WriteCharacter(buf[len - 1 - i]); - } - - /* Print out right padding. */ - if (HasFlag(FormatSpecifierFlag_LeftJustify)) { - for (size_t i = len + prefix_len + num_zeroes; i < static_cast(width); i++) { - WriteCharacter(' '); - } - } - }; - - /* Output the integer. */ - if (is_unsigned) { - uintmax_t n = 0; - if (HasFlag(FormatSpecifierFlag_LongLong)) { - n = static_cast(va_arg(vl, unsigned long long)); - } else if (HasFlag(FormatSpecifierFlag_Long)) { - n = static_cast(va_arg(vl, unsigned long)); - } else if (HasFlag(FormatSpecifierFlag_Char)) { - n = static_cast(va_arg(vl, unsigned int)); - } else if (HasFlag(FormatSpecifierFlag_Short)) { - n = static_cast(va_arg(vl, unsigned int)); - } else { - n = static_cast(va_arg(vl, unsigned int)); - } - if (specifier == 'p' && n == 0) { - WriteCharacter('('); - WriteCharacter('n'); - WriteCharacter('i'); - WriteCharacter('l'); - WriteCharacter(')'); - } else { - PrintInteger(false, n); - } - } else { - intmax_t n = 0; - if (HasFlag(FormatSpecifierFlag_LongLong)) { - n = static_cast(va_arg(vl, signed long long)); - } else if (HasFlag(FormatSpecifierFlag_Long)) { - n = static_cast(va_arg(vl, signed long)); - } else if (HasFlag(FormatSpecifierFlag_Char)) { - n = static_cast(va_arg(vl, signed int)); - } else if (HasFlag(FormatSpecifierFlag_Short)) { - n = static_cast(va_arg(vl, signed int)); - } else { - n = static_cast(va_arg(vl, signed int)); - } - const bool negative = n < 0; - const uintmax_t u = (negative) ? static_cast(-n) : static_cast(n); - PrintInteger(negative, u); - } - } - break; - case 'c': - { - size_t len = 1; - if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { - while (len++ < width) { - WriteCharacter(' '); - } - } - WriteCharacter(static_cast(va_arg(vl, int))); - if (HasFlag(FormatSpecifierFlag_LeftJustify)) { - while (len++ < width) { - WriteCharacter(' '); - } - } - } - break; - case 's': - { - const char *str = va_arg(vl, char *); - if (str == nullptr) { - str = "(null)"; - } - - size_t len = Strnlen(str, precision > 0 ? precision : std::numeric_limits::max()); - if (HasFlag(FormatSpecifierFlag_HasPrecision)) { - len = (len < precision) ? len : precision; - } - if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { - while (len++ < width) { - WriteCharacter(' '); - } - } - while (*str && (!HasFlag(FormatSpecifierFlag_HasPrecision) || (precision--) != 0)) { - WriteCharacter(*(str++)); - } - if (HasFlag(FormatSpecifierFlag_LeftJustify)) { - while (len++ < width) { - WriteCharacter(' '); - } - } - } - break; - case '%': - default: - WriteCharacter(specifier); - break; - } - } - - /* Ensure null termination. */ - WriteCharacter('\0'); - dst[dst_size - 1] = '\0'; - } - KSpinLock g_debug_log_lock; bool g_initialized_impl; @@ -452,9 +75,6 @@ namespace ams::kern { } - - #pragma GCC pop_options - void KDebugLog::Initialize() { if (KTargetSystem::IsDebugLoggingEnabled()) { KScopedInterruptDisable di; @@ -486,7 +106,7 @@ namespace ams::kern { } void KDebugLog::VSNPrintf(char *dst, const size_t dst_size, const char *format, ::std::va_list vl) { - VSNPrintfImpl(dst, dst_size, format, vl); + ::ams::util::TVSNPrintf(dst, dst_size, format, vl); } Result KDebugLog::PrintUserString(ams::kern::svc::KUserPointer user_str, size_t len) { diff --git a/libraries/libstratosphere/include/stratosphere.hpp b/libraries/libstratosphere/include/stratosphere.hpp index 9fffefa9f..6096ca240 100644 --- a/libraries/libstratosphere/include/stratosphere.hpp +++ b/libraries/libstratosphere/include/stratosphere.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -60,7 +61,6 @@ #include #include #include -#include #include #include #include diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index 7d60ab72d..837598518 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -39,19 +39,21 @@ namespace ams::impl { AMS_DEFINE_SYSTEM_THREAD(21, pm, Main); AMS_DEFINE_SYSTEM_THREAD(21, pm, ProcessTrack); + /* NCM. */ AMS_DEFINE_SYSTEM_THREAD(21, ncm, MainWaitThreads); AMS_DEFINE_SYSTEM_THREAD(21, ncm, ContentManagerServerIpcSession); AMS_DEFINE_SYSTEM_THREAD(21, ncm, LocationResolverServerIpcSession); /* FS. */ - AMS_DEFINE_SYSTEM_THREAD(16, fs, WorkerThreadPool); - AMS_DEFINE_SYSTEM_THREAD(17, fs, Main); - AMS_DEFINE_SYSTEM_THREAD(17, fs, WorkerRealTimeAccess); - AMS_DEFINE_SYSTEM_THREAD(18, fs, WorkerNormalPriorityAccess); - AMS_DEFINE_SYSTEM_THREAD(19, fs, WorkerLowPriorityAccess); - AMS_DEFINE_SYSTEM_THREAD(30, fs, WorkerBackgroundAccess); - AMS_DEFINE_SYSTEM_THREAD(30, fs, PatrolReader); + AMS_DEFINE_SYSTEM_THREAD(11, sdmmc, DeviceDetector); + AMS_DEFINE_SYSTEM_THREAD(16, fs, WorkerThreadPool); + AMS_DEFINE_SYSTEM_THREAD(17, fs, Main); + AMS_DEFINE_SYSTEM_THREAD(17, fs, WorkerRealTimeAccess); + AMS_DEFINE_SYSTEM_THREAD(18, fs, WorkerNormalPriorityAccess); + AMS_DEFINE_SYSTEM_THREAD(19, fs, WorkerLowPriorityAccess); + AMS_DEFINE_SYSTEM_THREAD(30, fs, WorkerBackgroundAccess); + AMS_DEFINE_SYSTEM_THREAD(30, fs, PatrolReader); /* Boot. */ AMS_DEFINE_SYSTEM_THREAD(-1, boot, Main); diff --git a/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp b/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp index 4cd493b28..c0f3ac992 100644 --- a/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp +++ b/libraries/libstratosphere/include/stratosphere/dd/dd_io_mappings.hpp @@ -19,16 +19,13 @@ namespace ams::dd { - uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size); - - u32 ReadRegister(uintptr_t phys_addr); - void WriteRegister(uintptr_t phys_addr, u32 value); - u32 ReadWriteRegister(uintptr_t phys_addr, u32 value, u32 mask); + u32 ReadRegister(dd::PhysicalAddress phys_addr); + void WriteRegister(dd::PhysicalAddress phys_addr, u32 value); + u32 ReadWriteRegister(dd::PhysicalAddress phys_addr, u32 value, u32 mask); /* Convenience Helper. */ - - inline uintptr_t GetIoMapping(uintptr_t phys_addr, size_t size) { - const uintptr_t io_mapping = QueryIoMapping(phys_addr, size); + inline uintptr_t GetIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + const uintptr_t io_mapping = dd::QueryIoMapping(phys_addr, size); AMS_ABORT_UNLESS(io_mapping); return io_mapping; } diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_evp_registers.hpp b/libraries/libstratosphere/include/stratosphere/ddsf.hpp similarity index 92% rename from libraries/libmesosphere/source/board/nintendo/nx/kern_evp_registers.hpp rename to libraries/libstratosphere/include/stratosphere/ddsf.hpp index 5f7bdf084..000a4aedd 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_evp_registers.hpp +++ b/libraries/libstratosphere/include/stratosphere/ddsf.hpp @@ -2,7 +2,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ + #pragma once -#define EVP_COP_RESET_VECTOR 0x200 +#include diff --git a/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp b/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp new file mode 100644 index 000000000..467c48dbe --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/ddsf/ddsf_types.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +namespace ams::ddsf { + + enum AccessMode { + AccessMode_None = (0u << 0), + AccessMode_Read = (1u << 0), + AccessMode_Write = (1u << 1), + + AccessMode_ReadWrite = AccessMode_Read | AccessMode_Write, + }; + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio.hpp b/libraries/libstratosphere/include/stratosphere/gpio.hpp new file mode 100644 index 000000000..02175345f --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio.hpp @@ -0,0 +1,22 @@ +/* + * 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 . + */ + +#pragma once +#include +#include +#include +#include +#include diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_pmc_registers.hpp b/libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp similarity index 81% rename from libraries/libmesosphere/source/board/nintendo/nx/kern_pmc_registers.hpp rename to libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp index 2ed4d670f..a58c8b21c 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_pmc_registers.hpp +++ b/libraries/libstratosphere/include/stratosphere/gpio/gpio_api.hpp @@ -2,7 +2,7 @@ * 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 + * 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 @@ -14,8 +14,12 @@ * along with this program. If not, see . */ #pragma once +#include +#include -#define APBDEV_PMC_DPD_ENABLE 0x024 -#define APBDEV_PMC_SCRATCH0 0x050 -#define APBDEV_PMC_SCRATCH4 0x060 -#define APBDEV_PMC_SCRATCH39 0x138 +namespace ams::gpio { + + void Initialize(); + void Finalize(); + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp b/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp new file mode 100644 index 000000000..a02d34884 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_api.hpp @@ -0,0 +1,58 @@ +/* + * 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 . + */ +#pragma once +#include +#include +#include +#include + +namespace ams::gpio { + + struct GpioPadSession { + void *_session; + os::SystemEventType *_event; + }; + + Result OpenSession(GpioPadSession *out_session, ams::DeviceCode device_code); + void CloseSession(GpioPadSession *session); + + Direction GetDirection(GpioPadSession *session); + void SetDirection(GpioPadSession *session, Direction direction); + + GpioValue GetValue(GpioPadSession *session); + void SetValue(GpioPadSession *session, GpioValue value); + + InterruptMode GetInterruptMode(GpioPadSession *session); + void SetInterruptMode(GpioPadSession *session, InterruptMode mode); + + bool GetInterruptEnable(GpioPadSession *session); + void SetInterruptEnable(GpioPadSession *session, bool en); + + InterruptStatus GetInterruptStatus(GpioPadSession *session); + void ClearInterruptStatus(GpioPadSession *session); + + int GetDebounceTime(GpioPadSession *session); + void SetDebounceTime(GpioPadSession *session, int ms); + + bool GetDebounceEnabled(GpioPadSession *session); + void SetDebounceEnabled(GpioPadSession *session, bool en); + + Result BindInterrupt(os::SystemEventType *event, GpioPadSession *session); + void UnbindInterrupt(GpioPadSession *session); + + Result IsWakeEventActive(bool *out_is_active, ams::DeviceCode device_code); + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp b/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp new file mode 100644 index 000000000..c1c816c95 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/gpio_pad_name.board.nintendo_nx.hpp @@ -0,0 +1,55 @@ +/* + * 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 . + */ +#pragma once +#include +#include + +namespace ams::gpio { + + enum GpioPadName : u32 { + GpioPadName_CodecLdoEnTemp = 1, + GpioPadName_ButtonVolUp = 25, + GpioPadName_ButtonVolDn = 26, + GpioPadName_SdCd = 56, + }; + + /* TODO: Better place for this? */ + constexpr inline const DeviceCode DeviceCode_CodecLdoEnTemp = 0x33000002; + constexpr inline const DeviceCode DeviceCode_ButtonVolUp = 0x35000002; + constexpr inline const DeviceCode DeviceCode_ButtonVolDn = 0x35000003; + constexpr inline const DeviceCode DeviceCode_SdCd = 0x3C000002; + + constexpr inline GpioPadName ConvertToGpioPadName(DeviceCode dc) { + switch (dc.GetInternalValue()) { + case DeviceCode_CodecLdoEnTemp.GetInternalValue(): return GpioPadName_CodecLdoEnTemp; + case DeviceCode_ButtonVolUp .GetInternalValue(): return GpioPadName_ButtonVolUp; + case DeviceCode_ButtonVolDn .GetInternalValue(): return GpioPadName_ButtonVolDn; + case DeviceCode_SdCd .GetInternalValue(): return GpioPadName_SdCd; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + constexpr inline DeviceCode ConvertToDeviceCode(GpioPadName gpn) { + switch (gpn) { + case GpioPadName_CodecLdoEnTemp: return DeviceCode_CodecLdoEnTemp; + case GpioPadName_ButtonVolUp: return DeviceCode_ButtonVolUp; + case GpioPadName_ButtonVolDn: return DeviceCode_ButtonVolDn; + case GpioPadName_SdCd: return DeviceCode_SdCd; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.hpp b/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.hpp new file mode 100644 index 000000000..25e665375 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/gpio_select_pad_name.hpp @@ -0,0 +1,24 @@ +/* + * 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 . + */ +#pragma once +#include +#include + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + #include +#else + /* Error? */ +#endif diff --git a/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp b/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp new file mode 100644 index 000000000..8d456c547 --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/gpio_types.hpp @@ -0,0 +1,46 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::gpio { + + enum InterruptMode : u32 { + InterruptMode_LowLevel = 0, + InterruptMode_HighLevel = 1, + InterruptMode_RisingEdge = 2, + InterruptMode_FallingEdge = 3, + InterruptMode_AnyEdge = 4, + }; + + enum Direction : u32 { + Direction_Input = 0, + Direction_Output = 1, + }; + + enum GpioValue : u32 { + GpioValue_Low = 0, + GpioValue_High = 1, + }; + + enum InterruptStatus : u32 { + InterruptStatus_Inactive = 0, + InterruptStatus_Active = 1, + }; + + using WakeBitFlag = util::BitFlagSet<128>; + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp b/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp new file mode 100644 index 000000000..e54dc7b1b --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_manager.hpp @@ -0,0 +1,41 @@ +/* + * 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 . + */ + +#pragma once +#include +#include +#include +#include +#include + +namespace ams::gpio::sf { + + #define AMS_GPIO_I_MANAGER_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, OpenSessionForDev, (ams::sf::Out> out, s32 pad_descriptor) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, OpenSession, (ams::sf::Out> out, gpio::GpioPadName pad_name) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, OpenSessionForTest, (ams::sf::Out> out, gpio::GpioPadName pad_name) ) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, IsWakeEventActive, (ams::sf::Out out, gpio::GpioPadName pad_name), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, GetWakeEventActiveFlagSet, (ams::sf::Out out), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, SetWakeEventActiveFlagSetForDebug, (gpio::GpioPadName pad_name, bool is_enabled), hos::Version_Min, hos::Version_6_2_0) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, SetWakePinDebugMode, (s32 mode) ) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, OpenSession2, (ams::sf::Out> out, DeviceCode device_code, ddsf::AccessMode access_mode), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, IsWakeEventActive2, (ams::sf::Out out, DeviceCode device_code), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, SetWakeEventActiveFlagSetForDebug2, (DeviceCode device_code, bool is_enabled), hos::Version_5_0_0 ) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, SetRetryValues, (u32 arg0, u32 arg1), hos::Version_6_0_0 ) + + AMS_SF_DEFINE_INTERFACE(IManager, AMS_GPIO_I_MANAGER_INTERFACE_INFO) + +} diff --git a/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp b/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp new file mode 100644 index 000000000..ec4791b1b --- /dev/null +++ b/libraries/libstratosphere/include/stratosphere/gpio/sf/gpio_sf_i_pad_session.hpp @@ -0,0 +1,45 @@ +/* + * 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 . + */ + +#pragma once +#include +#include + +namespace ams::gpio::sf { + + #define AMS_GPIO_I_PAD_SESSION_INTERFACE_INFO(C, H) \ + AMS_SF_METHOD_INFO(C, H, 0, Result, SetDirection, (gpio::Direction direction) ) \ + AMS_SF_METHOD_INFO(C, H, 1, Result, GetDirection, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 2, Result, SetInterruptMode, (gpio::InterruptMode mode) ) \ + AMS_SF_METHOD_INFO(C, H, 3, Result, GetInterruptMode, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 4, Result, SetInterruptEnable, (bool enable) ) \ + AMS_SF_METHOD_INFO(C, H, 5, Result, GetInterruptEnable, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 6, Result, GetInterruptStatus, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 7, Result, ClearInterruptStatus, () ) \ + AMS_SF_METHOD_INFO(C, H, 8, Result, SetValue, (gpio::GpioValue value) ) \ + AMS_SF_METHOD_INFO(C, H, 9, Result, GetValue, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 10, Result, BindInterrupt, (ams::sf::OutCopyHandle out) ) \ + AMS_SF_METHOD_INFO(C, H, 11, Result, UnbindInterrupt, () ) \ + AMS_SF_METHOD_INFO(C, H, 12, Result, SetDebounceEnabled, (bool enable) ) \ + AMS_SF_METHOD_INFO(C, H, 13, Result, GetDebounceEnabled, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 14, Result, SetDebounceTime, (s32 ms) ) \ + AMS_SF_METHOD_INFO(C, H, 15, Result, GetDebounceTime, (ams::sf::Out out) ) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, SetValueForSleepState, (gpio::GpioValue value), hos::Version_4_0_0) \ + AMS_SF_METHOD_INFO(C, H, 16, Result, GetValueForSleepState, (ams::sf::Out out), hos::Version_6_0_0) + + AMS_SF_DEFINE_INTERFACE(IPadSession, AMS_GPIO_I_PAD_SESSION_INTERFACE_INFO) + +} diff --git a/libraries/libstratosphere/include/stratosphere/reg.hpp b/libraries/libstratosphere/include/stratosphere/reg.hpp deleted file mode 100644 index 6b5076f93..000000000 --- a/libraries/libstratosphere/include/stratosphere/reg.hpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#pragma once -#include - -namespace ams::reg { - - inline void Write(volatile u32 *reg, u32 val) { - *reg = val; - } - - inline void Write(uintptr_t reg, u32 val) { - Write(reinterpret_cast(reg), val); - } - - inline u32 Read(volatile u32 *reg) { - return *reg; - } - - inline u32 Read(uintptr_t reg) { - return Read(reinterpret_cast(reg)); - } - - inline void SetBits(volatile u32 *reg, u32 mask) { - *reg = *reg | mask; - } - - inline void SetBits(uintptr_t reg, u32 mask) { - SetBits(reinterpret_cast(reg), mask); - } - - inline void ClearBits(volatile u32 *reg, u32 mask) { - *reg = *reg & ~mask; - } - - inline void ClearBits(uintptr_t reg, u32 mask) { - ClearBits(reinterpret_cast(reg), mask); - } - - inline void MaskBits(volatile u32 *reg, u32 mask) { - *reg = *reg & mask; - } - - inline void MaskBits(uintptr_t reg, u32 mask) { - MaskBits(reinterpret_cast(reg), mask); - } - - inline void ReadWrite(volatile u32 *reg, u32 val, u32 mask) { - *reg = (*reg & (~mask)) | (val & mask); - } - - inline void ReadWrite(uintptr_t reg, u32 val, u32 mask) { - ReadWrite(reinterpret_cast(reg), val, mask); - } - -} \ No newline at end of file diff --git a/libraries/libstratosphere/source/boot2/boot2_api.cpp b/libraries/libstratosphere/source/boot2/boot2_api.cpp index c2c050f39..4f4cda01a 100644 --- a/libraries/libstratosphere/source/boot2/boot2_api.cpp +++ b/libraries/libstratosphere/source/boot2/boot2_api.cpp @@ -167,20 +167,19 @@ namespace ams::boot2 { } } - bool GetGpioPadLow(GpioPadName pad) { - GpioPadSession button; - if (R_FAILED(gpioOpenSession(&button, pad))) { + bool GetGpioPadLow(DeviceCode device_code) { + gpio::GpioPadSession button; + if (R_FAILED(gpio::OpenSession(std::addressof(button), device_code))) { return false; } /* Ensure we close even on early return. */ - ON_SCOPE_EXIT { gpioPadClose(&button); }; + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(button)); }; /* Set direction input. */ - gpioPadSetDirection(&button, GpioDirection_Input); + gpio::SetDirection(std::addressof(button), gpio::Direction_Input); - GpioValue val; - return R_SUCCEEDED(gpioPadGetValue(&button, &val)) && val == GpioValue_Low; + return gpio::GetValue(std::addressof(button)) == gpio::GpioValue_Low; } bool IsForceMaintenance() { @@ -197,7 +196,7 @@ namespace ams::boot2 { /* Contact GPIO, read plus/minus buttons. */ { - return GetGpioPadLow(GpioPadName_ButtonVolUp) && GetGpioPadLow(GpioPadName_ButtonVolDown); + return GetGpioPadLow(gpio::DeviceCode_ButtonVolUp) && GetGpioPadLow(gpio::DeviceCode_ButtonVolDn); } } diff --git a/libraries/libstratosphere/source/dd/dd_io_mappings.cpp b/libraries/libstratosphere/source/dd/dd_io_mappings.cpp deleted file mode 100644 index 8bc84442c..000000000 --- a/libraries/libstratosphere/source/dd/dd_io_mappings.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#include - -namespace ams::dd { - - uintptr_t QueryIoMapping(uintptr_t phys_addr, size_t size) { - u64 virtual_addr; - const u64 aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize); - const size_t offset = phys_addr - aligned_addr; - const u64 aligned_size = size + offset; - if (hos::GetVersion() >= hos::Version_10_0_0) { - u64 region_size; - R_TRY_CATCH(svcQueryIoMapping(&virtual_addr, ®ion_size, aligned_addr, aligned_size)) { - /* Official software handles this by returning 0. */ - R_CATCH(svc::ResultNotFound) { return 0; } - } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - AMS_ASSERT(region_size >= aligned_size); - } else { - R_TRY_CATCH(svcLegacyQueryIoMapping(&virtual_addr, aligned_addr, aligned_size)) { - /* Official software handles this by returning 0. */ - R_CATCH(svc::ResultNotFound) { return 0; } - } R_END_TRY_CATCH_WITH_ABORT_UNLESS; - } - - return static_cast(virtual_addr + offset); - } - - namespace { - - inline u32 ReadWriteRegisterImpl(uintptr_t phys_addr, u32 value, u32 mask) { - u32 out_value; - R_ABORT_UNLESS(svcReadWriteRegister(&out_value, phys_addr, mask, value)); - return out_value; - } - - } - - u32 ReadRegister(uintptr_t phys_addr) { - return ReadWriteRegisterImpl(phys_addr, 0, 0); - } - - void WriteRegister(uintptr_t phys_addr, u32 value) { - ReadWriteRegisterImpl(phys_addr, value, ~u32()); - } - - u32 ReadWriteRegister(uintptr_t phys_addr, u32 value, u32 mask) { - return ReadWriteRegisterImpl(phys_addr, value, mask); - } - -} diff --git a/libraries/libstratosphere/source/gpio/gpio_client_api.cpp b/libraries/libstratosphere/source/gpio/gpio_client_api.cpp new file mode 100644 index 000000000..c5d88023b --- /dev/null +++ b/libraries/libstratosphere/source/gpio/gpio_client_api.cpp @@ -0,0 +1,197 @@ +/* + * 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 . + */ +#include +#include "gpio_remote_manager_impl.hpp" + +namespace ams::gpio { + + namespace { + + /* TODO: Manager object. */ + constinit os::SdkMutex g_init_mutex; + constinit int g_initialize_count = 0; + std::shared_ptr g_manager; + + using InternalSession = std::shared_ptr; + + InternalSession &GetInterface(GpioPadSession *session) { + AMS_ASSERT(session->_session != nullptr); + return *static_cast(session->_session); + } + + } + + void Initialize() { + std::scoped_lock lk(g_init_mutex); + + if ((g_initialize_count++) == 0) { + R_ABORT_UNLESS(::gpioInitialize()); + g_manager = ams::sf::MakeShared(); + } + } + + void Finalize() { + std::scoped_lock lk(g_init_mutex); + + AMS_ASSERT(g_initialize_count > 0); + + if ((--g_initialize_count) == 0) { + g_manager.reset(); + ::gpioExit(); + } + } + + Result OpenSession(GpioPadSession *out_session, ams::DeviceCode device_code) { + /* Allocate the session. */ + InternalSession *internal_session = new (std::nothrow) InternalSession; + AMS_ABORT_UNLESS(internal_session != nullptr); + auto session_guard = SCOPE_GUARD { delete internal_session; }; + + /* Get the session. */ + { + ams::sf::cmif::ServiceObjectHolder object_holder; + if (hos::GetVersion() >= hos::Version_7_0_0) { + R_TRY(g_manager->OpenSession2(std::addressof(object_holder), device_code, ddsf::AccessMode_ReadWrite)); + } else { + R_TRY(g_manager->OpenSession(std::addressof(object_holder), ConvertToGpioPadName(device_code))); + } + *internal_session = object_holder.GetServiceObject(); + } + + /* Set output. */ + out_session->_session = internal_session; + out_session->_event = nullptr; + + /* We succeeded. */ + session_guard.Cancel(); + return ResultSuccess(); + } + + void CloseSession(GpioPadSession *session) { + AMS_ASSERT(session != nullptr); + + /* Unbind the interrupt, if it's still bound. */ + if (session->_event != nullptr) { + gpio::UnbindInterrupt(session); + } + + /* Close the session. */ + delete std::addressof(GetInterface(session)); + session->_session = nullptr; + } + + Result IsWakeEventActive(bool *out_is_active, ams::DeviceCode device_code) { + if (hos::GetVersion() >= hos::Version_7_0_0) { + R_TRY(g_manager->IsWakeEventActive2(out_is_active, device_code)); + } else { + R_TRY(g_manager->IsWakeEventActive(out_is_active, ConvertToGpioPadName(device_code))); + } + + return ResultSuccess(); + } + + Direction GetDirection(GpioPadSession *session) { + Direction out; + R_ABORT_UNLESS(GetInterface(session)->GetDirection(std::addressof(out))); + return out; + } + + void SetDirection(GpioPadSession *session, Direction direction) { + R_ABORT_UNLESS(GetInterface(session)->SetDirection(direction)); + } + + GpioValue GetValue(GpioPadSession *session) { + GpioValue out; + R_ABORT_UNLESS(GetInterface(session)->GetValue(std::addressof(out))); + return out; + } + + void SetValue(GpioPadSession *session, GpioValue value) { + R_ABORT_UNLESS(GetInterface(session)->SetValue(value)); + } + + InterruptMode GetInterruptMode(GpioPadSession *session) { + InterruptMode out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptMode(std::addressof(out))); + return out; + } + + void SetInterruptMode(GpioPadSession *session, InterruptMode mode) { + R_ABORT_UNLESS(GetInterface(session)->SetInterruptMode(mode)); + } + + bool GetInterruptEnable(GpioPadSession *session) { + bool out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptEnable(std::addressof(out))); + return out; + } + + void SetInterruptEnable(GpioPadSession *session, bool en) { + R_ABORT_UNLESS(GetInterface(session)->SetInterruptEnable(en)); + } + + InterruptStatus GetInterruptStatus(GpioPadSession *session) { + InterruptStatus out; + R_ABORT_UNLESS(GetInterface(session)->GetInterruptStatus(std::addressof(out))); + return out; + } + + void ClearInterruptStatus(GpioPadSession *session) { + R_ABORT_UNLESS(GetInterface(session)->ClearInterruptStatus()); + } + + int GetDebounceTime(GpioPadSession *session) { + int out; + R_ABORT_UNLESS(GetInterface(session)->GetDebounceTime(std::addressof(out))); + return out; + } + + void SetDebounceTime(GpioPadSession *session, int ms) { + R_ABORT_UNLESS(GetInterface(session)->SetDebounceTime(ms)); + } + + bool GetDebounceEnabled(GpioPadSession *session) { + bool out; + R_ABORT_UNLESS(GetInterface(session)->GetDebounceEnabled(std::addressof(out))); + return out; + } + + void SetDebounceEnabled(GpioPadSession *session, bool en) { + R_ABORT_UNLESS(GetInterface(session)->SetDebounceEnabled(en)); + } + + Result BindInterrupt(os::SystemEventType *event, GpioPadSession *session) { + AMS_ASSERT(session->_event == nullptr); + + ams::sf::CopyHandle handle; + R_TRY(GetInterface(session)->BindInterrupt(std::addressof(handle))); + + os::AttachReadableHandleToSystemEvent(event, handle.GetValue(), true, os::EventClearMode_ManualClear); + + session->_event = event; + return ResultSuccess(); + } + + void UnbindInterrupt(GpioPadSession *session) { + AMS_ASSERT(session->_event != nullptr); + + R_ABORT_UNLESS(GetInterface(session)->UnbindInterrupt()); + + os::DestroySystemEvent(session->_event); + session->_event = nullptr; + } + +} diff --git a/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp b/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp new file mode 100644 index 000000000..bb439b79f --- /dev/null +++ b/libraries/libstratosphere/source/gpio/gpio_remote_manager_impl.hpp @@ -0,0 +1,91 @@ +/* + * 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 . + */ +#pragma once +#include +#include "gpio_remote_pad_session_impl.hpp" + +namespace ams::gpio { + + class RemoteManagerImpl { + public: + RemoteManagerImpl() { /* ... */ } + + ~RemoteManagerImpl() { /* ... */ } + public: + /* Actual commands. */ + Result OpenSessionForDev(ams::sf::Out> out, s32 pad_descriptor) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result OpenSession(ams::sf::Out> out, gpio::GpioPadName pad_name) { + ::GpioPadSession p; + R_TRY(::gpioOpenSession(std::addressof(p), static_cast<::GpioPadName>(static_cast(pad_name)))); + + out.SetValue(ams::sf::MakeShared(p)); + return ResultSuccess(); + } + + Result OpenSessionForTest(ams::sf::Out> out, gpio::GpioPadName pad_name) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result IsWakeEventActive(ams::sf::Out out, gpio::GpioPadName pad_name) { + return ::gpioIsWakeEventActive2(out.GetPointer(), static_cast<::GpioPadName>(static_cast(pad_name))); + } + + Result GetWakeEventActiveFlagSet(ams::sf::Out out) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result SetWakeEventActiveFlagSetForDebug(gpio::GpioPadName pad_name, bool is_enabled) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result SetWakePinDebugMode(s32 mode) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result OpenSession2(ams::sf::Out> out, DeviceCode device_code, ddsf::AccessMode access_mode) { + ::GpioPadSession p; + R_TRY(::gpioOpenSession2(std::addressof(p), device_code.GetInternalValue(), access_mode)); + + out.SetValue(ams::sf::MakeShared(p)); + return ResultSuccess(); + } + + Result IsWakeEventActive2(ams::sf::Out out, DeviceCode device_code) { + return ::gpioIsWakeEventActive2(out.GetPointer(), device_code.GetInternalValue()); + } + + Result SetWakeEventActiveFlagSetForDebug2(DeviceCode device_code, bool is_enabled) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + Result SetRetryValues(u32 arg0, u32 arg1) { + /* TODO: libnx bindings */ + AMS_ABORT(); + } + + }; + static_assert(gpio::sf::IsIManager); + +} diff --git a/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp b/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp new file mode 100644 index 000000000..297132f08 --- /dev/null +++ b/libraries/libstratosphere/source/gpio/gpio_remote_pad_session_impl.hpp @@ -0,0 +1,113 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::gpio { + + class RemotePadSessionImpl { + private: + ::GpioPadSession srv; + public: + RemotePadSessionImpl(::GpioPadSession &p) : srv(p) { /* ... */ } + + ~RemotePadSessionImpl() { ::gpioPadClose(std::addressof(this->srv)); } + public: + /* Actual commands. */ + Result SetDirection(gpio::Direction direction) { + return ::gpioPadSetDirection(std::addressof(this->srv), static_cast<::GpioDirection>(static_cast(direction))); + } + + Result GetDirection(ams::sf::Out out) { + static_assert(sizeof(gpio::Direction) == sizeof(::GpioDirection)); + return ::gpioPadGetDirection(std::addressof(this->srv), reinterpret_cast<::GpioDirection *>(out.GetPointer())); + } + + Result SetInterruptMode(gpio::InterruptMode mode) { + return ::gpioPadSetInterruptMode(std::addressof(this->srv), static_cast<::GpioInterruptMode>(static_cast(mode))); + } + + Result GetInterruptMode(ams::sf::Out out) { + static_assert(sizeof(gpio::InterruptMode) == sizeof(::GpioInterruptMode)); + return ::gpioPadGetInterruptMode(std::addressof(this->srv), reinterpret_cast<::GpioInterruptMode *>(out.GetPointer())); + } + + Result SetInterruptEnable(bool enable) { + return ::gpioPadSetInterruptEnable(std::addressof(this->srv), enable); + } + + Result GetInterruptEnable(ams::sf::Out out) { + return ::gpioPadGetInterruptEnable(std::addressof(this->srv), out.GetPointer()); + } + + Result GetInterruptStatus(ams::sf::Out out) { + static_assert(sizeof(gpio::InterruptStatus) == sizeof(::GpioInterruptStatus)); + return ::gpioPadGetInterruptStatus(std::addressof(this->srv), reinterpret_cast<::GpioInterruptStatus *>(out.GetPointer())); + } + + Result ClearInterruptStatus() { + return ::gpioPadClearInterruptStatus(std::addressof(this->srv)); + } + + Result SetValue(gpio::GpioValue value) { + return ::gpioPadSetValue(std::addressof(this->srv), static_cast<::GpioValue>(static_cast(value))); + } + + Result GetValue(ams::sf::Out out) { + static_assert(sizeof(gpio::GpioValue) == sizeof(::GpioValue)); + return ::gpioPadGetValue(std::addressof(this->srv), reinterpret_cast<::GpioValue *>(out.GetPointer())); + } + + Result BindInterrupt(ams::sf::OutCopyHandle out) { + ::Event ev; + R_TRY(::gpioPadBindInterrupt(std::addressof(this->srv), std::addressof(ev))); + out.SetValue(ev.revent); + return ResultSuccess(); + } + + Result UnbindInterrupt() { + return ::gpioPadUnbindInterrupt(std::addressof(this->srv)); + } + + Result SetDebounceEnabled(bool enable) { + return ::gpioPadSetDebounceEnabled(std::addressof(this->srv), enable); + } + + Result GetDebounceEnabled(ams::sf::Out out) { + return ::gpioPadGetDebounceEnabled(std::addressof(this->srv), out.GetPointer()); + } + + Result SetDebounceTime(s32 ms) { + return ::gpioPadSetDebounceTime(std::addressof(this->srv), ms); + } + + Result GetDebounceTime(ams::sf::Out out) { + return ::gpioPadGetDebounceTime(std::addressof(this->srv), out.GetPointer()); + } + + Result SetValueForSleepState(gpio::GpioValue value) { + /* TODO: libnx bindings. */ + AMS_ABORT(); + } + + Result GetValueForSleepState(ams::sf::Out out) { + /* TODO: libnx bindings. */ + AMS_ABORT(); + } + }; + static_assert(gpio::sf::IsIPadSession); + +} diff --git a/libraries/libvapours/include/vapours.hpp b/libraries/libvapours/include/vapours.hpp index 84820cb2b..a47ec53bc 100644 --- a/libraries/libvapours/include/vapours.hpp +++ b/libraries/libvapours/include/vapours.hpp @@ -20,12 +20,22 @@ #include #include +#include #include #include #include #include +#include + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) +#include +#endif + #include #include #include + +#include +#include \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/dd.hpp b/libraries/libvapours/include/vapours/dd.hpp new file mode 100644 index 000000000..52d3e9cd0 --- /dev/null +++ b/libraries/libvapours/include/vapours/dd.hpp @@ -0,0 +1,24 @@ +/* + * 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 . + */ + +#pragma once +#include +#include +#include + +#include +#include +#include diff --git a/libraries/libvapours/include/vapours/dd/dd_cache.hpp b/libraries/libvapours/include/vapours/dd/dd_cache.hpp new file mode 100644 index 000000000..8ea0e200a --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_cache.hpp @@ -0,0 +1,25 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size); + void StoreDataCache(void *addr, size_t size); + void FlushDataCache(void *addr, size_t size); + +} diff --git a/libraries/libvapours/include/vapours/dd/dd_common_types.hpp b/libraries/libvapours/include/vapours/dd/dd_common_types.hpp new file mode 100644 index 000000000..25a533b0f --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_common_types.hpp @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +#pragma once +#include +#include +#include + +namespace ams::dd { + + using PhysicalAddress = u64; + using DeviceVirtualAddress = u64; + + static_assert(std::same_as); + +} diff --git a/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp b/libraries/libvapours/include/vapours/dd/dd_io_mapping.hpp new file mode 100644 index 000000000..f82552bb0 --- /dev/null +++ b/libraries/libvapours/include/vapours/dd/dd_io_mapping.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 . + */ +#pragma once +#include + +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/device_code.hpp b/libraries/libvapours/include/vapours/device_code.hpp new file mode 100644 index 000000000..c39abcaf2 --- /dev/null +++ b/libraries/libvapours/include/vapours/device_code.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include + +namespace ams { + + namespace impl { + + using DeviceCodeType = u32; + + } + + /* TODO: Better understand device code components. */ + class DeviceCode { + private: + impl::DeviceCodeType inner_value; + public: + constexpr DeviceCode(impl::DeviceCodeType v) : inner_value(v) { /* ... */ } + + constexpr impl::DeviceCodeType GetInternalValue() const { return this->inner_value; } + + constexpr bool operator==(const DeviceCode &rhs) const { + return this->GetInternalValue() == rhs.GetInternalValue(); + } + + constexpr bool operator!=(const DeviceCode &rhs) const { + return !(*this == rhs); + } + }; + + constexpr inline const DeviceCode InvalidDeviceCode(0); + +} diff --git a/libraries/libexosphere/include/exosphere/reg.hpp b/libraries/libvapours/include/vapours/reg.hpp similarity index 58% rename from libraries/libexosphere/include/exosphere/reg.hpp rename to libraries/libvapours/include/vapours/reg.hpp index 1ac314110..7cf770312 100644 --- a/libraries/libexosphere/include/exosphere/reg.hpp +++ b/libraries/libvapours/include/vapours/reg.hpp @@ -18,6 +18,9 @@ namespace ams::reg { + template + concept UnsignedNonConstIntegral = std::unsigned_integral && !std::is_const::value; + using BitsValue = std::tuple; using BitsMask = std::tuple; @@ -48,100 +51,137 @@ namespace ams::reg { return (EncodeValue(values) | ...); } - ALWAYS_INLINE void Write(volatile u32 *reg, u32 val) { *reg = val; } - ALWAYS_INLINE void Write(volatile u32 ®, u32 val) { reg = val; } + template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + constexpr ALWAYS_INLINE u32 EncodeMask(const Masks... masks) { + return (EncodeMask(masks) | ...); + } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void Write(volatile IntType *reg, std::type_identity_t val) { *reg = val; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void Write(volatile IntType ®, std::type_identity_t val) { reg = val; } + ALWAYS_INLINE void Write(uintptr_t reg, u32 val) { Write(reinterpret_cast(reg), val); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void Write(volatile u32 *reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void Write(volatile IntType *reg, const Values... values) { return Write(reg, static_cast((EncodeValue(values) | ...))); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void Write(volatile u32 ®, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void Write(volatile IntType ®, const Values... values) { return Write(reg, static_cast((EncodeValue(values) | ...))); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) ALWAYS_INLINE void Write(uintptr_t reg, const Values... values) { return Write(reg, (EncodeValue(values) | ...)); } - ALWAYS_INLINE u32 Read(volatile u32 *reg) { return *reg; } - ALWAYS_INLINE u32 Read(volatile u32 ®) { return reg; } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType Read(volatile IntType *reg) { return *reg; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType Read(volatile IntType ®) { return reg; } + ALWAYS_INLINE u32 Read(uintptr_t reg) { return Read(reinterpret_cast(reg)); } - ALWAYS_INLINE u32 Read(volatile u32 *reg, u32 mask) { return *reg & mask; } - ALWAYS_INLINE u32 Read(volatile u32 ®, u32 mask) { return reg & mask; } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType Read(volatile IntType *reg, std::type_identity_t mask) { return *reg & mask; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType Read(volatile IntType ®, std::type_identity_t mask) { return reg & mask; } + ALWAYS_INLINE u32 Read(uintptr_t reg, u32 mask) { return Read(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(volatile u32 *reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType *reg, const Masks... masks) { return Read(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE IntType Read(volatile IntType ®, const Masks... masks) { return Read(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(volatile u32 ®, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE u32 Read(uintptr_t reg, const Masks... masks) { return Read(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType *reg, const Values... values) { return Read(reg, static_cast((EncodeMask(values) | ...))) == static_cast(Encode(values...)); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE bool HasValue(volatile u32 *reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } - - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE bool HasValue(volatile u32 ®, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE bool HasValue(volatile IntType ®, const Values... values) { return Read(reg, static_cast((EncodeMask(values) | ...))) == static_cast(Encode(values...)); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) ALWAYS_INLINE bool HasValue(uintptr_t reg, const Values... values) { return Read(reg, (EncodeMask(values) | ...)) == Encode(values...); } - ALWAYS_INLINE u32 GetValue(volatile u32 *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } - ALWAYS_INLINE u32 GetValue(volatile u32 ®, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } - ALWAYS_INLINE u32 GetValue(uintptr_t reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType GetValue(volatile IntType *reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE IntType GetValue(volatile IntType ®, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + ALWAYS_INLINE u32 GetValue(uintptr_t reg, const BitsMask mask) { return Read(reg, mask) >> GetOffset(mask); } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, std::type_identity_t val, std::type_identity_t mask) { *reg = (*reg & (~mask)) | (val & mask); } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void ReadWrite(volatile IntType ®, std::type_identity_t val, std::type_identity_t mask) { reg = ( reg & (~mask)) | (val & mask); } - ALWAYS_INLINE void ReadWrite(volatile u32 *reg, u32 val, u32 mask) { *reg = (*reg & (~mask)) | (val & mask); } - ALWAYS_INLINE void ReadWrite(volatile u32 ®, u32 val, u32 mask) { reg = ( reg & (~mask)) | (val & mask); } ALWAYS_INLINE void ReadWrite(uintptr_t reg, u32 val, u32 mask) { ReadWrite(reinterpret_cast(reg), val, mask); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(volatile u32 *reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType *reg, const Values... values) { return ReadWrite(reg, static_cast((EncodeValue(values) | ...)), static_cast((EncodeMask(values) | ...))); } + + template requires UnsignedNonConstIntegral && ((sizeof...(Values) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ReadWrite(volatile IntType ®, const Values... values) { return ReadWrite(reg, static_cast((EncodeValue(values) | ...)), static_cast((EncodeMask(values) | ...))); } template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(volatile u32 ®, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } - template requires ((sizeof...(Values) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ReadWrite(uintptr_t reg, const Values... values) { return ReadWrite(reg, (EncodeValue(values) | ...), (EncodeMask(values) | ...)); } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void SetBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg | mask; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void SetBits(volatile IntType ®, std::type_identity_t mask) { reg = reg | mask; } - ALWAYS_INLINE void SetBits(volatile u32 *reg, u32 mask) { *reg = *reg | mask; } - ALWAYS_INLINE void SetBits(volatile u32 ®, u32 mask) { reg = reg | mask; } ALWAYS_INLINE void SetBits(uintptr_t reg, u32 mask) { SetBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(volatile u32 *reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType *reg, const Masks... masks) { return SetBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void SetBits(volatile IntType ®, const Masks... masks) { return SetBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(volatile u32 ®, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void SetBits(uintptr_t reg, const Masks... masks) { return SetBits(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void ClearBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg & ~mask; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void ClearBits(volatile IntType ®, std::type_identity_t mask) { reg = reg & ~mask; } - ALWAYS_INLINE void ClearBits(volatile u32 *reg, u32 mask) { *reg = *reg & ~mask; } - ALWAYS_INLINE void ClearBits(volatile u32 ®, u32 mask) { reg = reg & ~mask; } ALWAYS_INLINE void ClearBits(uintptr_t reg, u32 mask) { ClearBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(volatile u32 *reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType *reg, const Masks... masks) { return ClearBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void ClearBits(volatile IntType ®, const Masks... masks) { return ClearBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(volatile u32 ®, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void ClearBits(uintptr_t reg, const Masks... masks) { return ClearBits(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void MaskBits(volatile IntType *reg, std::type_identity_t mask) { *reg = *reg & mask; } + + template requires UnsignedNonConstIntegral + ALWAYS_INLINE void MaskBits(volatile IntType ®, std::type_identity_t mask) { reg = reg & mask; } - ALWAYS_INLINE void MaskBits(volatile u32 *reg, u32 mask) { *reg = *reg & mask; } - ALWAYS_INLINE void MaskBits(volatile u32 ®, u32 mask) { reg = reg & mask; } ALWAYS_INLINE void MaskBits(uintptr_t reg, u32 mask) { MaskBits(reinterpret_cast(reg), mask); } - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(volatile u32 *reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType *reg, const Masks... masks) { return MaskBits(reg, static_cast((EncodeMask(masks) | ...))); } + + template requires UnsignedNonConstIntegral && ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) + ALWAYS_INLINE void MaskBits(volatile IntType ®, const Masks... masks) { return MaskBits(reg, static_cast((EncodeMask(masks) | ...))); } template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(volatile u32 ®, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } - - template requires ((sizeof...(Masks) > 0) && (std::is_same::value && ...)) - ALWAYS_INLINE void MaskBits(uintptr_t reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } + ALWAYS_INLINE void MaskBits(uintptr_t reg, const Masks... masks) { return MaskBits(reg, (EncodeMask(masks) | ...)); } #define REG_BITS_MASK(OFFSET, WIDTH) ::ams::reg::BitsMask{OFFSET, WIDTH} #define REG_BITS_VALUE(OFFSET, WIDTH, VALUE) ::ams::reg::BitsValue{OFFSET, WIDTH, VALUE} diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index 4d4c41a56..596114b95 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -41,10 +41,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp b/libraries/libvapours/include/vapours/results/pcv_results.hpp similarity index 77% rename from libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp rename to libraries/libvapours/include/vapours/results/pcv_results.hpp index 42e329887..ec0aa6d46 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp +++ b/libraries/libvapours/include/vapours/results/pcv_results.hpp @@ -2,7 +2,7 @@ * 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 + * 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 @@ -13,9 +13,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#pragma once -#define FLOW_CTLR_HALT_COP_EVENTS 0x004 -#define FLOW_CTLR_CC4_HVC_CONTROL 0x060 -#define FLOW_CTLR_CC4_RETENTION_CONTROL 0x064 -#define FLOW_CTLR_CC4_HVC_RETRY 0x08C +#pragma once +#include + +namespace ams::pcv { + + R_DEFINE_NAMESPACE_RESULT_MODULE(133); + + R_DEFINE_ERROR_RESULT(IllegalRequest, 16); + +} diff --git a/libraries/libvapours/include/vapours/results/sdmmc_results.hpp b/libraries/libvapours/include/vapours/results/sdmmc_results.hpp new file mode 100644 index 000000000..a6d305cc6 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/sdmmc_results.hpp @@ -0,0 +1,105 @@ +/* + * 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 . + */ + +#pragma once +#include + +namespace ams::sdmmc { + + R_DEFINE_NAMESPACE_RESULT_MODULE(24); + + R_DEFINE_ERROR_RESULT(NoDevice, 1); + R_DEFINE_ERROR_RESULT(NotActivated, 2); + R_DEFINE_ERROR_RESULT(DeviceRemoved, 3); + R_DEFINE_ERROR_RESULT(NotAwakened, 4); + + R_DEFINE_ERROR_RANGE(CommunicationError, 32, 126); + R_DEFINE_ERROR_RANGE(CommunicationNotAttained, 33, 46); + R_DEFINE_ERROR_RESULT(ResponseIndexError, 34); + R_DEFINE_ERROR_RESULT(ResponseEndBitError, 35); + R_DEFINE_ERROR_RESULT(ResponseCrcError, 36); + R_DEFINE_ERROR_RESULT(ResponseTimeoutError, 37); + R_DEFINE_ERROR_RESULT(DataEndBitError, 38); + R_DEFINE_ERROR_RESULT(DataCrcError, 39); + R_DEFINE_ERROR_RESULT(DataTimeoutError, 40); + R_DEFINE_ERROR_RESULT(AutoCommandResponseIndexError, 41); + R_DEFINE_ERROR_RESULT(AutoCommandResponseEndBitError, 42); + R_DEFINE_ERROR_RESULT(AutoCommandResponseCrcError, 43); + R_DEFINE_ERROR_RESULT(AutoCommandResponseTimeoutError, 44); + R_DEFINE_ERROR_RESULT(CommandCompleteSoftwareTimeout, 45); + R_DEFINE_ERROR_RESULT(TransferCompleteSoftwareTimeout, 46); + R_DEFINE_ERROR_RANGE(DeviceStatusHasError, 48, 70); + R_DEFINE_ERROR_RESULT(DeviceStatusAddressOutOfRange, 49); + R_DEFINE_ERROR_RESULT(DeviceStatusAddressMisaligned, 50); + R_DEFINE_ERROR_RESULT(DeviceStatusBlockLenError, 51); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseSeqError, 52); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseParam, 53); + R_DEFINE_ERROR_RESULT(DeviceStatusWpViolation, 54); + R_DEFINE_ERROR_RESULT(DeviceStatusLockUnlockFailed, 55); + R_DEFINE_ERROR_RESULT(DeviceStatusComCrcError, 56); + R_DEFINE_ERROR_RESULT(DeviceStatusIllegalCommand, 57); + R_DEFINE_ERROR_RESULT(DeviceStatusDeviceEccFailed, 58); + R_DEFINE_ERROR_RESULT(DeviceStatusCcError, 59); + R_DEFINE_ERROR_RESULT(DeviceStatusError, 60); + R_DEFINE_ERROR_RESULT(DeviceStatusCidCsdOverwrite, 61); + R_DEFINE_ERROR_RESULT(DeviceStatusWpEraseSkip, 62); + R_DEFINE_ERROR_RESULT(DeviceStatusEraseReset, 63); + R_DEFINE_ERROR_RESULT(DeviceStatusSwitchError, 64); + R_DEFINE_ERROR_RESULT(UnexpectedDeviceState, 72); + R_DEFINE_ERROR_RESULT(UnexpectedDeviceCsdValue, 73); + R_DEFINE_ERROR_RESULT(AbortTransactionSoftwareTimeout, 74); + R_DEFINE_ERROR_RESULT(CommandInhibitCmdSoftwareTimeout, 75); + R_DEFINE_ERROR_RESULT(CommandInhibitDatSoftwareTimeout, 76); + R_DEFINE_ERROR_RESULT(BusySoftwareTimeout, 77); + R_DEFINE_ERROR_RESULT(IssueTuningCommandSoftwareTimeout, 78); + R_DEFINE_ERROR_RESULT(TuningFailed, 79); + R_DEFINE_ERROR_RESULT(MmcInitializationSoftwareTimeout, 80); + R_DEFINE_ERROR_RESULT(MmcNotSupportExtendedCsd, 81); + R_DEFINE_ERROR_RESULT(UnexpectedMmcExtendedCsdValue, 82); + R_DEFINE_ERROR_RESULT(MmcEraseSoftwareTimeout, 83); + R_DEFINE_ERROR_RESULT(SdCardValidationError, 84); + R_DEFINE_ERROR_RESULT(SdCardInitializationSoftwareTimeout, 85); + R_DEFINE_ERROR_RESULT(SdCardGetValidRcaSoftwareTimeout, 86); + R_DEFINE_ERROR_RESULT(UnexpectedSdCardAcmdDisabled, 87); + R_DEFINE_ERROR_RESULT(SdCardNotSupportSwitchFunctionStatus, 88); + R_DEFINE_ERROR_RESULT(UnexpectedSdCardSwitchFunctionStatus, 89); + R_DEFINE_ERROR_RESULT(SdCardNotSupportAccessMode, 90); + R_DEFINE_ERROR_RESULT(SdCardNot4BitBusWidthAtUhsIMode, 91); + R_DEFINE_ERROR_RESULT(SdCardNotSupportSdr104AndSdr50, 92); + R_DEFINE_ERROR_RESULT(SdCardCannotSwitchAccessMode, 93); + R_DEFINE_ERROR_RESULT(SdCardFailedSwitchAccessMode, 94); + R_DEFINE_ERROR_RESULT(SdCardUnacceptableCurrentConsumption, 95); + R_DEFINE_ERROR_RESULT(SdCardNotReadyToVoltageSwitch, 96); + R_DEFINE_ERROR_RESULT(SdCardNotCompleteVoltageSwitch, 97); + + R_DEFINE_ERROR_RANGE(HostControllerUnexpected, 128, 158); + R_DEFINE_ERROR_RESULT(InternalClockStableSoftwareTimeout, 129); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownAutoCmdError, 130); + R_DEFINE_ERROR_RESULT(SdHostStandardUnknownError, 131); + R_DEFINE_ERROR_RESULT(SdmmcDllCalibrationSoftwareTimeout, 132); + R_DEFINE_ERROR_RESULT(SdmmcDllApplicationSoftwareTimeout, 133); + R_DEFINE_ERROR_RESULT(SdHostStandardFailSwitchTo1_8V, 134); + R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationNotCompleted, 135); + R_DEFINE_ERROR_RESULT(DriveStrengthCalibrationSoftwareTimeout, 136); + R_DEFINE_ERROR_RESULT(SdmmcCompShortToGnd, 137); + R_DEFINE_ERROR_RESULT(SdmmcCompOpen, 138); + + R_DEFINE_ERROR_RANGE(InternalError, 160, 190); + R_DEFINE_ERROR_RESULT(NoWaitedInterrupt, 161); + R_DEFINE_ERROR_RESULT(WaitInterruptSoftwareTimeout, 162); + + R_DEFINE_ERROR_RESULT(NotImplemented, 201); +} diff --git a/libraries/libvapours/include/vapours/sdmmc.hpp b/libraries/libvapours/include/vapours/sdmmc.hpp new file mode 100644 index 000000000..444071a17 --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp new file mode 100644 index 000000000..97d6560c1 --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_build_config.hpp @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#if defined(ATMOSPHERE_IS_EXOSPHERE) + + //#define AMS_SDMMC_THREAD_SAFE + //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + //#define AMS_SDMMC_USE_DEVICE_DETECTOR + //#define AMS_SDMMC_USE_LOGGER + //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER + //#define AMS_SDMMC_ENABLE_MMC_HS400 + //#define AMS_SDMMC_ENABLE_SD_UHS_I + //#define AMS_SDMMC_SET_PLLC4_BASE + //#define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#elif defined(ATMOSPHERE_IS_MESOSPHERE) + + //#define AMS_SDMMC_THREAD_SAFE + //#define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + //#define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + //#define AMS_SDMMC_USE_DEVICE_DETECTOR + //#define AMS_SDMMC_USE_LOGGER + //#define AMS_SDMMC_USE_OS_EVENTS + //#define AMS_SDMMC_USE_OS_TIMER + #define AMS_SDMMC_USE_UTIL_TIMER + //#define AMS_SDMMC_ENABLE_MMC_HS400 + //#define AMS_SDMMC_ENABLE_SD_UHS_I + //#define AMS_SDMMC_SET_PLLC4_BASE + //#define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + #define AMS_SDMMC_THREAD_SAFE + #define AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS + #define AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL + #define AMS_SDMMC_USE_DEVICE_DETECTOR + #define AMS_SDMMC_USE_LOGGER + #define AMS_SDMMC_USE_OS_EVENTS + #define AMS_SDMMC_USE_OS_TIMER + //#define AMS_SDMMC_USE_UTIL_TIMER + #define AMS_SDMMC_ENABLE_MMC_HS400 + #define AMS_SDMMC_ENABLE_SD_UHS_I + #define AMS_SDMMC_SET_PLLC4_BASE + #define AMS_SDMMC_USE_SD_CARD_DETECTOR + +#else + #error "Unknown execution context for ams::sdmmc!" +#endif diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp new file mode 100644 index 000000000..e06976fce --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_common.hpp @@ -0,0 +1,114 @@ +/* + * 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 . + */ + +#pragma once +#include + +namespace ams::sdmmc { + + enum BusPower { + BusPower_Off = 0, + BusPower_1_8V = 1, + BusPower_3_3V = 2, + }; + + enum BusWidth { + BusWidth_1Bit = 0, + BusWidth_4Bit = 1, + BusWidth_8Bit = 2, + }; + + enum SpeedMode { + SpeedMode_MmcIdentification = 0, + SpeedMode_MmcLegacySpeed = 1, + SpeedMode_MmcHighSpeed = 2, + SpeedMode_MmcHs200 = 3, + SpeedMode_MmcHs400 = 4, + SpeedMode_SdCardIdentification = 5, + SpeedMode_SdCardDefaultSpeed = 6, + SpeedMode_SdCardHighSpeed = 7, + SpeedMode_SdCardSdr12 = 8, + SpeedMode_SdCardSdr25 = 9, + SpeedMode_SdCardSdr50 = 10, + SpeedMode_SdCardSdr104 = 11, + SpeedMode_SdCardDdr50 = 12, + SpeedMode_GcAsicFpgaSpeed = 13, + SpeedMode_GcAsicSpeed = 14, + }; + + enum Port { + Port_Mmc0 = 0, + Port_SdCard0 = 1, + Port_GcAsic0 = 2, + }; + + struct ErrorInfo { + u32 num_activation_failures; + u32 num_activation_error_corrections; + u32 num_read_write_failures; + u32 num_read_write_error_corrections; + }; + + struct DataTransfer { + void *buffer; + size_t buffer_size; + size_t block_size; + u32 num_blocks; + bool is_read; + }; + + using DeviceDetectionEventCallback = void (*)(void *); + + constexpr inline size_t SectorSize = 0x200; + + constexpr inline size_t DeviceCidSize = 0x10; + constexpr inline size_t DeviceCsdSize = 0x10; + + constexpr inline size_t BufferDeviceVirtualAddressAlignment = alignof(ams::dd::DeviceVirtualAddress); + static_assert(BufferDeviceVirtualAddressAlignment >= 8); + + void Initialize(Port port); + void Finalize(Port port); + +#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvClockResetControl(); +#endif + +#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address); + void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address); +#endif + + void ChangeCheckTransferInterval(Port port, u32 ms); + void SetDefaultCheckTransferInterval(Port port); + + Result Activate(Port port); + void Deactivate(Port port); + + Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors); + Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size); + + Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + + Result GetDeviceSpeedMode(SpeedMode *out, Port port); + Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port); + Result GetDeviceStatus(u32 *out_device_status, Port port); + Result GetDeviceCid(void *out, size_t out_size, Port port); + Result GetDeviceCsd(void *out, size_t out_size, Port port); + + void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port); + +} \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp new file mode 100644 index 000000000..a1355de55 --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_gc_asic.hpp @@ -0,0 +1,34 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::sdmmc { + + constexpr inline size_t GcAsicOperationSize = 0x40; + + void PutGcAsicToSleep(Port port); + Result AwakenGcAsic(Port port); + Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size); + Result FinishGcAsicOperation(Port port); + Result AbortGcAsicOperation(Port port); + Result SleepGcAsic(Port port); + Result UpdateGcAsicKey(Port port); + + void SignalGcRemovedEvent(Port port); + void ClearGcRemovedEvent(Port port); + +} diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp new file mode 100644 index 000000000..39d9e7390 --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_mmc.hpp @@ -0,0 +1,43 @@ +/* + * 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 . + */ + +#pragma once +#include + + +namespace ams::sdmmc { + + enum MmcPartition { + MmcPartition_UserData = 0, + MmcPartition_BootPartition1 = 1, + MmcPartition_BootPartition2 = 2, + MmcPartition_Unknown = 3, + }; + + constexpr inline size_t MmcExtendedCsdSize = 0x200; + constexpr inline size_t MmcWorkBufferSize = MmcExtendedCsdSize; + + void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size); + void PutMmcToSleep(Port port); + void AwakenMmc(Port port); + Result SelectMmcPartition(Port port, MmcPartition mmc_partition); + Result EraseMmc(Port port); + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port); + Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port); + + Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + +} \ No newline at end of file diff --git a/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp b/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp new file mode 100644 index 000000000..1c1eff371 --- /dev/null +++ b/libraries/libvapours/include/vapours/sdmmc/sdmmc_sd_card.hpp @@ -0,0 +1,53 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::sdmmc { + + enum SdCardSwitchFunction { + SdCardSwitchFunction_CheckSupportedFunction = 0, + SdCardSwitchFunction_CheckDefault = 1, + SdCardSwitchFunction_CheckHighSpeed = 2, + SdCardSwitchFunction_CheckSdr50 = 3, + SdCardSwitchFunction_CheckSdr104 = 4, + SdCardSwitchFunction_CheckDdr50 = 5, + }; + + constexpr inline size_t SdCardScrSize = 0x08; + constexpr inline size_t SdCardSwitchFunctionStatusSize = 0x40; + constexpr inline size_t SdCardSdStatusSize = 0x40; + + constexpr inline size_t SdCardWorkBufferSize = SdCardSdStatusSize; + + void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size); + void PutSdCardToSleep(Port port); + void AwakenSdCard(Port port); + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port); + Result GetSdCardScr(void *dst, size_t dst_size, Port port); + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function); + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode); + Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port); + + Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port); + + bool IsSdCardInserted(Port port); + bool IsSdCardRemoved(Port port); + + void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg); + void UnregisterSdCardDetectionEventCallback(Port port); + +} diff --git a/libraries/libvapours/include/vapours/tegra.hpp b/libraries/libvapours/include/vapours/tegra.hpp new file mode 100644 index 000000000..7d70c4a79 --- /dev/null +++ b/libraries/libvapours/include/vapours/tegra.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_ahb_arbc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp similarity index 94% rename from libraries/libexosphere/include/exosphere/tegra/tegra_ahb_arbc.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp index b130ada97..37066da05 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_ahb_arbc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_ahb_arbc.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define AHB_ARBC(x) (0x6000c000 + x) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_apb_misc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp similarity index 72% rename from libraries/libexosphere/include/exosphere/tegra/tegra_apb_misc.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp index 4ad9de8ba..1ceabe5b2 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_apb_misc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_apb_misc.hpp @@ -14,11 +14,24 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include -#define APB_MISC_PP_CONFIG_CTL (0x024) +#define APB_MISC_PP_CONFIG_CTL (0x024) -#define APB_MISC_GP_ASDBGREG (0x810) +#define APB_MISC_GP_ASDBGREG (0x810) + +#define APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL (0xA98) + +#define APB_MISC_GP_EMMC2_PAD_CFGPADCTRL (0xA9C) +#define APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL (0xA9C) + +#define APB_MISC_GP_EMMC4_PAD_CFGPADCTRL (0xAB4) +#define APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL (0xABC) #define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00) #define APB_MISC_SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG0_0 (0xc00) @@ -41,6 +54,29 @@ DEFINE_APB_MISC_REG_BIT_ENUM(PP_CONFIG_CTL_TBE, 7, DISABLE, ENABLE); DEFINE_APB_MISC_REG(GP_ASDBGREG_CFG2TMC_RAM_SVOP_PDP, 24, 2); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, 12, 7); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, 20, 7); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 28, 2); +DEFINE_APB_MISC_REG (GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 30, 2); + +DEFINE_APB_MISC_REG_BIT_ENUM(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_E_SCH, 0, DISABLE, ENABLE); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 2, 6); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 8, 6); +DEFINE_APB_MISC_REG (GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 14, 13); + +DEFINE_APB_MISC_REG (GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 12, 7); +DEFINE_APB_MISC_REG (GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 20, 7); + + +DEFINE_APB_MISC_REG_BIT_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, 0, DISABLE, ENABLE); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, 2, 6); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, 8, 6); +DEFINE_APB_MISC_REG (GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 14, 13); + +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 1, 1); +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 2, 1); +DEFINE_APB_MISC_REG(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 22, 1); + #define DEFINE_SLAVE_SECURITY_REG(RINDEX, INDEX, NAME) DEFINE_APB_MISC_REG_BIT_ENUM(SECURE_REGS_APB_SLAVE_SECURITY_ENABLE_REG##RINDEX##_##NAME##_SECURITY_EN, INDEX, DISABLE, ENABLE) DEFINE_SLAVE_SECURITY_REG(0, 29, STM); diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_avp_cache.hpp b/libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp similarity index 93% rename from libraries/libexosphere/include/exosphere/tegra/tegra_avp_cache.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp index 52e4c7748..d0d97738a 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_avp_cache.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_avp_cache.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define AVP_CACHE_ADDRESS(x) (0x50040000 + x) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_clkrst.hpp b/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp similarity index 80% rename from libraries/libexosphere/include/exosphere/tegra/tegra_clkrst.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp index 395a006ea..a2d9d3613 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_clkrst.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_clkrst.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include /* Clock source enums. */ #define CLK_RST_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (CLK_RST_CONTROLLER, NAME) @@ -33,13 +38,18 @@ #define CLK_RST_CONTROLLER_MISC_CLK_ENB (0x048) #define CLK_RST_CONTROLLER_OSC_CTRL (0x050) +#define CLK_RST_CONTROLLER_PLLD_BASE (0x0D0) +#define CLK_RST_CONTROLLER_PLLD_MISC1 (0x0D8) +#define CLK_RST_CONTROLLER_PLLD_MISC (0x0DC) #define CLK_RST_CONTROLLER_PLLX_BASE (0x0E0) +#define CLK_RST_CONTROLLER_PLLX_MISC (0x0E4) #define CLK_RST_CONTROLLER_CCLKG_BURST_POLICY (0x368) #define CLK_RST_CONTROLLER_SUPER_CCLKG_DIVIDER (0x36C) #define CLK_RST_CONTROLLER_CCLKLP_BURST_POLICY (0x370) #define CLK_RST_CONTROLLER_SUPER_CCLKLP_DIVIDER (0x374) #define CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2 (0x388) #define CLK_RST_CONTROLLER_SPARE_REG0 (0x55C) +#define CLK_RST_CONTROLLER_PLLC4_BASE (0x5A4) #define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA (0x0F8) #define CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB (0x0FC) @@ -74,6 +84,10 @@ DEFINE_CLK_RST_REG(CPU_SOFTRST_CTRL2_CAR2PMC_CPU_ACK_WIDTH, 0, 12); DEFINE_CLK_RST_REG_TWO_BIT_ENUM(SPARE_REG0_CLK_M_DIVISOR, 2, CLK_M_DIVISOR1, CLK_M_DIVISOR2, CLK_M_DIVISOR3, CLK_M_DIVISOR4); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_IDDQ, 18, OFF, ON); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_LOCK, 27, NOT_LOCK, LOCK_FEQ_AND_PHASE); +DEFINE_CLK_RST_REG_BIT_ENUM(PLLC4_BASE_PLLC4_ENABLE, 30, DISABLE, ENABLE); + /* RST_DEVICES */ #define CLK_RST_CONTROLLER_RST_DEVICES_L (0x004) #define CLK_RST_CONTROLLER_RST_DEVICES_H (0x008) @@ -93,15 +107,24 @@ DEFINE_CLK_RST_REG_TWO_BIT_ENUM(SPARE_REG0_CLK_M_DIVISOR, 2, CLK_M_DIVISOR1, CLK #define CLK_RST_CONTROLLER_CLK_OUT_ENB_W (0x364) /* CLK_SOURCE */ -#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 (0x124) -#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 (0x128) -#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTA (0x178) -#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTB (0x17C) -#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTC (0x1A0) -#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT (0x3B4) -#define CLK_RST_CONTROLLER_CLK_SOURCE_ACTMON (0x3E8) -#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF (0x62C) -#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC (0x630) +#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 (0x124) +#define CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 (0x128) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DISP1 (0x138) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1 (0x150) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2 (0x154) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4 (0x164) +#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTA (0x178) +#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTB (0x17C) +#define CLK_RST_CONTROLLER_CLK_SOURCE_UARTC (0x1A0) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3 (0x1BC) +#define CLK_RST_CONTROLLER_CLK_SOURCE_CSITE (0x1D4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT (0x3B4) +#define CLK_RST_CONTROLLER_CLK_SOURCE_ACTMON (0x3E8) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP (0x620) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_REF (0x62C) +#define CLK_RST_CONTROLLER_CLK_SOURCE_DVFS_SOC (0x630) +#define CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL (0x66C) +#define CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM (0x694) /* RST_DEV_*_SET */ #define CLK_RST_CONTROLLER_RST_DEV_L_SET (0x300) @@ -163,6 +186,19 @@ DEFINE_CLK_RST_REG(CLK_SOURCE_I2C1_I2C1_CLK_DIVISOR, 0, 8); DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_I2C5_I2C5_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); DEFINE_CLK_RST_REG(CLK_SOURCE_I2C5_I2C5_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC1_SDMMC1_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLM_OUT0, PLLE_OUT0, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC2_SDMMC2_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC4_SDMMC4_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC3_SDMMC3_CLK_SRC, 29, PLLP_OUT0, PLLA_OUT, PLLC_OUT0, PLLC4_OUT2, PLLC4_OUT1, PLLE_OUT0, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, 29, PLLP_OUT0, _RSVD1_, _RSVD2_, PLLC4_OUT2, _RSVD4_, _RSVD5_, CLK_M, PLLC4_OUT0); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, 29, PLLP_OUT0, PLLC4_OUT2_LJ, PLLC4_OUT0_LJ, PLLC4_OUT2, PLLC4_OUT1, PLLC4_OUT1_LJ, CLK_M, PLLC4_OUT0); + +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC1_SDMMC1_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC2_SDMMC2_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC4_SDMMC4_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG(CLK_SOURCE_SDMMC3_SDMMC3_CLK_DIVISOR, 0, 8); + DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTA_UARTA_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTB_UARTB_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_UARTC_UARTC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); @@ -178,6 +214,9 @@ DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_REF_DVFS_REF_CLK_SRC, 29, PLLP DEFINE_CLK_RST_REG(CLK_SOURCE_DVFS_SOC_DVFS_SOC_DIVISOR, 0, 8); DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_DVFS_SOC_DVFS_SOC_CLK_SRC, 29, PLLP_OUT0, PLLC2_OUT0, PLLC_OUT0, PLLC4_OUT0, RESERVED4, PLLC4_OUT1, CLK_M, PLLC4_OUT2); +DEFINE_CLK_RST_REG(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 0, 8); +DEFINE_CLK_RST_REG_THREE_BIT_ENUM(CLK_SOURCE_LEGACY_TM_CLK_SRC, 29, PLLP_OUT3, PLLC_OUT0, PLLC2_OUT0, CLK_M, PLLP_OUT0, PLLC4_OUT0, PLLC4_OUT1, PLLC4_OUT2); + DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_SET_SET_COP_RST, 1, DISABLE, ENABLE); DEFINE_CLK_RST_REG_BIT_ENUM(RST_DEV_L_CLR_CLR_COP_RST, 1, DISABLE, ENABLE); @@ -198,6 +237,9 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA HANDLER(L, RTC, 0, 4) \ HANDLER(L, TMR, 0, 5) \ HANDLER(L, GPIO, 0, 8) \ + HANDLER(L, SDMMC2, 0, 9) \ + HANDLER(L, SDMMC1, 0, 14) \ + HANDLER(L, SDMMC4, 0, 15) \ HANDLER(L, USBD, 0, 22) \ HANDLER(L, CACHE2, 0, 31) \ HANDLER(H, MEM, 1, 0) \ @@ -209,6 +251,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA HANDLER(H, I2C5, 1, 15) \ HANDLER(H, EMC, 1, 25) \ HANDLER(H, USB2, 1, 26) \ + HANDLER(U, SDMMC3, 2, 5) \ HANDLER(U, CSITE, 2, 9) \ HANDLER(U, IRAMA, 2, 20) \ HANDLER(U, IRAMB, 2, 21) \ @@ -238,6 +281,7 @@ DEFINE_CLK_RST_REG_BIT_ENUM(RST_CPUG_CMPLX_CLR_CLR_NONCPURESET, 29, DISABLE, ENA HANDLER(X, GPU, 5, 24) \ HANDLER(X, DBGAPB, 5, 25) \ HANDLER(X, PLLG_REF, 5, 29) \ + HANDLER(Y, LEGACY_TM, 6, 1) \ HANDLER(Y, MC_CCPA, 6, 8) \ HANDLER(Y, MC_CDPA, 6, 9) \ HANDLER(Y, PLLP_OUT_CPU, 6, 31) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_emc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp similarity index 97% rename from libraries/libexosphere/include/exosphere/tegra/tegra_emc.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_emc.hpp index 7078ea199..1e1560d4c 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_emc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_emc.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define EMC_ADDRESS(x) (0x7001B000 + x) #define EMC0_ADDRESS(x) (0x7001E000 + x) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_evp.hpp b/libraries/libvapours/include/vapours/tegra/tegra_evp.hpp similarity index 85% rename from libraries/libexosphere/include/exosphere/tegra/tegra_evp.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_evp.hpp index 24135720c..16f194021 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_evp.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_evp.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define EVP_CPU_RESET_VECTOR (0x100) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_flow_ctlr.hpp b/libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp similarity index 90% rename from libraries/libexosphere/include/exosphere/tegra/tegra_flow_ctlr.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp index ef74c7f78..aa199b9d2 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_flow_ctlr.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_flow_ctlr.hpp @@ -14,12 +14,21 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include + +#define FLOW_CTLR_RAM_REPAIR (0x040) +#define FLOW_CTLR_FLOW_DBG_QUAL (0x050) +#define FLOW_CTLR_CC4_HVC_CONTROL (0x060) +#define FLOW_CTLR_CC4_RETENTION_CONTROL (0x064) +#define FLOW_CTLR_CC4_HVC_RETRY (0x08C) +#define FLOW_CTLR_L2FLUSH_CONTROL (0x094) +#define FLOW_CTLR_BPMP_CLUSTER_CONTROL (0x098) -#define FLOW_CTLR_RAM_REPAIR (0x040) -#define FLOW_CTLR_FLOW_DBG_QUAL (0x050) -#define FLOW_CTLR_L2FLUSH_CONTROL (0x094) -#define FLOW_CTLR_BPMP_CLUSTER_CONTROL (0x098) #define FLOW_CTLR_CPU0_CSR (0x008) #define FLOW_CTLR_CPU1_CSR (0x018) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_ictlr.hpp b/libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp similarity index 84% rename from libraries/libexosphere/include/exosphere/tegra/tegra_ictlr.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp index 6f11ee918..773fccb02 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_ictlr.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_ictlr.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define PRI_ICTLR(n) (0x60004000 + n) #define SEC_ICTLR(n) (0x60004100 + n) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_mc.hpp similarity index 99% rename from libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_mc.hpp index 107bf548d..093842554 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_mc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_mc.hpp @@ -14,8 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include #define MC_INTSTATUS (0x000) #define MC_INTMASK (0x004) @@ -25,7 +29,7 @@ #define MC_SMMU_PTB_ASID (0x01C) #define MC_SMMU_PTB_DATA (0x020) #define MC_SMMU_TLB_FLUSH (0x030) -#define MC_SMMU_PTC_FLUSH (0x034) +#define MC_SMMU_PTC_FLUSH_0 (0x034) #define MC_EMEM_CFG (0x050) #define MC_EMEM_ADR_CFG (0x054) #define MC_EMEM_ARB_CFG (0x090) @@ -56,7 +60,7 @@ #define MC_SMMU_HC_ASID (0x250) #define MC_SMMU_HDA_ASID (0x254) #define MC_SMMU_ISP2_ASID (0x258) -#define MC_SMMU_NVENC_ASID (0x264) +#define MC_SMMU_MSENC_NVENC_ASID (0x264) #define MC_SMMU_NV_ASID (0x268) #define MC_SMMU_NV2_ASID (0x26C) #define MC_SMMU_PPCS_ASID (0x270) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_mselect.hpp b/libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp similarity index 94% rename from libraries/libexosphere/include/exosphere/tegra/tegra_mselect.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp index 6f0c5137b..eda67c8da 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_mselect.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_mselect.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define MSELECT(x) (0x50060000 + x) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_pg_up.hpp b/libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp similarity index 81% rename from libraries/libexosphere/include/exosphere/tegra/tegra_pg_up.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp index a461d28d2..aa8c192fb 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_pg_up.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_pg_up.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define PG_UP(x) (0x60000000 + x) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_pinmux.hpp b/libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp similarity index 77% rename from libraries/libexosphere/include/exosphere/tegra/tegra_pinmux.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp index 8e5424880..137a8eaec 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_pinmux.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_pinmux.hpp @@ -14,7 +14,21 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include + +#define PINMUX_AUX_SDMMC1_CLK (0x3000) +#define PINMUX_AUX_SDMMC1_CMD (0x3004) +#define PINMUX_AUX_SDMMC1_DAT3 (0x3008) +#define PINMUX_AUX_SDMMC1_DAT2 (0x300C) +#define PINMUX_AUX_SDMMC1_DAT1 (0x3010) +#define PINMUX_AUX_SDMMC1_DAT0 (0x3014) + +#define PINMUX_AUX_DMIC3_CLK (0x30B4) #define PINMUX_AUX_GEN1_I2C_SCL (0x30BC) #define PINMUX_AUX_GEN1_I2C_SDA (0x30C0) @@ -34,6 +48,11 @@ #define PINMUX_AUX_UART3_RTS (0x310C) #define PINMUX_AUX_UART3_CTS (0x3110) #define PINMUX_AUX_DVFS_PWM (0x3184) +#define PINMUX_AUX_NFC_EN (0x31D0) +#define PINMUX_AUX_NFC_INT (0x31D4) +#define PINMUX_AUX_LCD_BL_PWM (0x31FC) +#define PINMUX_AUX_LCD_BL_EN (0x3200) +#define PINMUX_AUX_LCD_RST (0x3204) #define PINMUX_AUX_GPIO_PA6 (0x3244) #define PINMUX_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (PINMUX, NAME) @@ -56,6 +75,16 @@ DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_LPDR, 8, DISABLE, ENABLE); DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_OD, 11, DISABLE, ENABLE); DEFINE_PINMUX_REG_BIT_ENUM(AUX_E_SCHMT, 12, DISABLE, ENABLE); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_CLK_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_CMD_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT3_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT2_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT1_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_SDMMC1_DAT0_PM, 0, SDMMC1, RSVD1, RSVD2, RSVD3); + +DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_DMIC3_CLK_PM, 0, DMIC3, I2S5A, RSVD2, RSVD3); + DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_GEN1_I2C_PM, 0, I2C1, RSVD1, RSVD2, RSVD3); DEFINE_PINMUX_REG_TWO_BIT_ENUM(AUX_PWR_I2C_PM, 0, I2CPMU, RSVD1, RSVD2, RSVD3); diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp b/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp similarity index 96% rename from libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp index ecd8d1860..e518c6684 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_pmc.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_pmc.hpp @@ -14,8 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include -#include +#include +#include +#include +#include +#include +#include #define APBDEV_PMC_CNTRL (0x000) #define APBDEV_PMC_WAKE_MASK (0x00C) @@ -34,6 +38,7 @@ #define APBDEV_PMC_PWR_DET (0x048) #define APBDEV_PMC_SCRATCH0 (0x050) #define APBDEV_PMC_SCRATCH1 (0x054) +#define APBDEV_PMC_SCRATCH4 (0x060) #define APBDEV_PMC_SCRATCH12 (0x080) #define APBDEV_PMC_SCRATCH13 (0x084) #define APBDEV_PMC_SCRATCH18 (0x098) @@ -45,6 +50,7 @@ #define APBDEV_PMC_SCRATCH31 (0x118) #define APBDEV_PMC_SCRATCH32 (0x11C) #define APBDEV_PMC_SCRATCH33 (0x120) +#define APBDEV_PMC_SCRATCH39 (0x138) #define APBDEV_PMC_SCRATCH40 (0x13C) #define APBDEV_PMC_WAKE2_MASK (0x160) #define APBDEV_PMC_WAKE2_LVL (0x164) @@ -198,6 +204,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/libexosphere/include/exosphere/tegra/tegra_sb.hpp b/libraries/libvapours/include/vapours/tegra/tegra_sb.hpp similarity index 94% rename from libraries/libexosphere/include/exosphere/tegra/tegra_sb.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_sb.hpp index 83ea3f677..7e484eea6 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_sb.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_sb.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define SB_CSR (0x200) #define SB_PFCFG (0x208) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_sysctr0.hpp b/libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp similarity index 93% rename from libraries/libexosphere/include/exosphere/tegra/tegra_sysctr0.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp index 650067eb6..b0393e23b 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_sysctr0.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_sysctr0.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define SYSCTR0_CNTCR (0x000) #define SYSCTR0_CNTCV0 (0x008) diff --git a/libraries/libexosphere/include/exosphere/tegra/tegra_timer.hpp b/libraries/libvapours/include/vapours/tegra/tegra_timer.hpp similarity index 94% rename from libraries/libexosphere/include/exosphere/tegra/tegra_timer.hpp rename to libraries/libvapours/include/vapours/tegra/tegra_timer.hpp index ee3fe3dd1..f68241014 100644 --- a/libraries/libexosphere/include/exosphere/tegra/tegra_timer.hpp +++ b/libraries/libvapours/include/vapours/tegra/tegra_timer.hpp @@ -14,7 +14,12 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include +#include +#include +#include +#include #define TIMERUS_USEC_CFG (0x014) #define TIMER_SHARED_TIMER_SECURE_CFG (0x1A4) diff --git a/libraries/libvapours/include/vapours/util.hpp b/libraries/libvapours/include/vapours/util.hpp index 0d60a3ea7..d55f5db64 100644 --- a/libraries/libvapours/include/vapours/util.hpp +++ b/libraries/libvapours/include/vapours/util.hpp @@ -34,8 +34,10 @@ #include #include #include +#include #include #include #include #include #include +#include diff --git a/libraries/libvapours/include/vapours/util/util_alignment.hpp b/libraries/libvapours/include/vapours/util/util_alignment.hpp index 849e141c0..3fa951504 100644 --- a/libraries/libvapours/include/vapours/util/util_alignment.hpp +++ b/libraries/libvapours/include/vapours/util/util_alignment.hpp @@ -73,4 +73,9 @@ namespace ams::util { return IsAligned(reinterpret_cast(value), alignment); } -} \ No newline at end of file + template requires std::integral && std::integral + constexpr ALWAYS_INLINE T DivideUp(T x, U y) { + return (x + (y - 1)) / y; + } + +} diff --git a/libraries/libvapours/include/vapours/util/util_format_string.hpp b/libraries/libvapours/include/vapours/util/util_format_string.hpp new file mode 100644 index 000000000..0dd2faf45 --- /dev/null +++ b/libraries/libvapours/include/vapours/util/util_format_string.hpp @@ -0,0 +1,29 @@ +/* + * 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 . + */ + +#pragma once +#include +#include + +namespace ams::util { + + int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...); + int VSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); + + int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...); + int TVSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl); + +} \ No newline at end of file diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_clkrst_registers.hpp b/libraries/libvapours/include/vapours/util/util_timer.hpp similarity index 80% rename from libraries/libmesosphere/source/board/nintendo/nx/kern_clkrst_registers.hpp rename to libraries/libvapours/include/vapours/util/util_timer.hpp index 308bb435c..2d2236163 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_clkrst_registers.hpp +++ b/libraries/libvapours/include/vapours/util/util_timer.hpp @@ -2,7 +2,7 @@ * 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 + * 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 @@ -13,8 +13,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#pragma once -#define CLK_RST_CONTROLLER_CLK_SOURCE_CSITE 0x1D4 -#define CLK_RST_CONTROLLER_RST_DEV_L_SET 0x300 -#define CLK_RST_CONTROLLER_RST_DEV_L_CLR 0x304 +#pragma once +#include +#include + +namespace ams::util { + + u32 GetMicroSeconds(); + void WaitMicroSeconds(int us); + +} diff --git a/libraries/libvapours/source/dd/dd_cache.cpp b/libraries/libvapours/source/dd/dd_cache.cpp new file mode 100644 index 000000000..1cab96001 --- /dev/null +++ b/libraries/libvapours/source/dd/dd_cache.cpp @@ -0,0 +1,41 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/dd_select_cache_impl.hpp" + +namespace ams::dd { + + void InvalidateDataCache(void *addr, size_t size) { + return impl::InvalidateDataCacheImpl(addr, size); + } + + void StoreDataCache(void *addr, size_t size) { + return impl::StoreDataCacheImpl(addr, size); + } + + void FlushDataCache(void *addr, size_t size) { + return impl::FlushDataCacheImpl(addr, size); + } + +} diff --git a/libraries/libvapours/source/dd/dd_io_mapping.cpp b/libraries/libvapours/source/dd/dd_io_mapping.cpp new file mode 100644 index 000000000..91358726c --- /dev/null +++ b/libraries/libvapours/source/dd/dd_io_mapping.cpp @@ -0,0 +1,101 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif + +namespace ams::dd { + + uintptr_t QueryIoMapping(dd::PhysicalAddress phys_addr, size_t size) { + #if defined(ATMOSPHERE_IS_EXOSPHERE) + #if defined(ATMOSPHERE_ARCH_ARM64) + /* TODO: Secure Monitor translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #elif defined(ATMOSPHERE_ARCH_ARM) + /* TODO: BPMP translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #else + #error "Unknown architecture for ams::dd::QueryIoMapping (EXOSPHERE)!" + #endif + #elif defined(ATMOSPHERE_IS_MESOSPHERE) + /* TODO: Kernel address translation? */ + AMS_UNUSED(size); + return static_cast(phys_addr); + #elif defined(ATMOSPHERE_IS_STRATOSPHERE) + + svc::Address virt_addr = 0; + const dd::PhysicalAddress aligned_addr = util::AlignDown(phys_addr, os::MemoryPageSize); + const size_t offset = phys_addr - aligned_addr; + const size_t aligned_size = size + offset; + + if (hos::GetVersion() >= hos::Version_10_0_0) { + svc::Size region_size = 0; + R_TRY_CATCH(svc::QueryIoMapping(&virt_addr, ®ion_size, aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + AMS_ASSERT(region_size >= aligned_size); + } else { + R_TRY_CATCH(svc::LegacyQueryIoMapping(&virt_addr, aligned_addr, aligned_size)) { + /* Official software handles this by returning 0. */ + R_CATCH(svc::ResultNotFound) { return 0; } + } R_END_TRY_CATCH_WITH_ABORT_UNLESS; + } + + return static_cast(virt_addr) + offset; + + #else + #error "Unknown execution context for ams::dd::QueryIoMapping!" + #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(out_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/dd/impl/dd_cache_impl.os.horizon.hpp b/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp new file mode 100644 index 000000000..53ccf6473 --- /dev/null +++ b/libraries/libvapours/source/dd/impl/dd_cache_impl.os.horizon.hpp @@ -0,0 +1,94 @@ +/* + * 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 . + */ +#pragma once +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#else +#include +#endif + +namespace ams::dd::impl { + + void StoreDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc cvac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::StoreProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + /* Do nothing. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::StoreDataCacheImpl" + #endif + #endif + } + + void FlushDataCacheImpl(void *addr, size_t size) { + #if defined(ATMOSPHERE_ARCH_ARM64) + /* On aarch64, we can use cache maintenance instructions. */ + + /* Get cache line size. */ + uintptr_t ctr_el0 = 0; + __asm__ __volatile__("mrs %[ctr_el0], ctr_el0" : [ctr_el0]"=r"(ctr_el0)); + const uintptr_t cache_line_size = 4 << ((ctr_el0 >> 16) & 0xF); + + /* Invalidate the cache. */ + const uintptr_t start_addr = reinterpret_cast(addr) & ~(cache_line_size - 1); + const uintptr_t end_addr = reinterpret_cast(addr) + size; + for (uintptr_t cur = start_addr; cur < end_addr; cur += cache_line_size) { + __asm__ __volatile__("dc civac, %[cur]" : : [cur]"r"(cur)); + } + + /* Add a memory barrier. */ + __asm__ __volatile__("dsb sy" ::: "memory"); + #else + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + /* Invoke the relevant svc. */ + const auto result = svc::FlushProcessDataCache(svc::PseudoHandle::CurrentProcess, reinterpret_cast(addr), size); + R_ASSERT(result); + #elif defined(ATMOSPHERE_IS_EXOSPHERE) && defined(__BPMP__) + /* Do nothing. */ + AMS_UNUSED(addr, size); + #else + #error "Unknown execution context for ams::dd::impl::FlushDataCacheImpl" + #endif + #endif + } + + void InvalidateDataCacheImpl(void *addr, size_t size) { + /* Just perform a flush, which is clean + invalidate. */ + return FlushDataCacheImpl(addr, size); + } + +} diff --git a/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp b/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp new file mode 100644 index 000000000..18e021496 --- /dev/null +++ b/libraries/libvapours/source/dd/impl/dd_select_cache_impl.hpp @@ -0,0 +1,22 @@ +/* + * 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 . + */ +#pragma once + +#if defined(ATMOSPHERE_OS_HORIZON) + #include "dd_cache_impl.os.horizon.hpp" +#else + #error "Unknown OS for ams::dd::CacheImpl" +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp new file mode 100644 index 000000000..c9234e994 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.cpp @@ -0,0 +1,581 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() std::scoped_lock lk(this->base_device->device_mutex) + + #else + + #define AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX() + + #endif + + void BaseDevice::GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const { + AMS_ABORT_UNLESS(out_c_size_mult != nullptr); + AMS_ABORT_UNLESS(out_read_bl_len != nullptr); + + /* Extract C_SIZE_MULT and READ_BL_LEN from the CSD. */ + *out_c_size_mult = static_cast((this->csd[2] >> 7) & 0x7); + *out_read_bl_len = static_cast((this->csd[4] >> 8) & 0xF); + } + + Result BaseDevice::SetLegacyMemoryCapacity() { + /* Get csize from the csd. */ + const u32 c_size = ((this->csd[3] >> 6) & 0x3FF) | ((this->csd[4] & 0x3) << 10); + + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + this->GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Set memory capacity. */ + this->memory_capacity = (c_size + 1) << ((read_bl_len + c_size_mult + 2) - 9); + this->is_valid_memory_capacity = true; + + return ResultSuccess(); + } + + Result BaseDevice::CheckDeviceStatus(u32 r1_resp) const { + /* Check if there are any errors at all. */ + R_SUCCEED_IF((r1_resp & DeviceStatus_ErrorMask) == 0); + + /* Check errors individually. */ + #define AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(__ERROR__) R_UNLESS((r1_resp & DeviceStatus_##__ERROR__) == 0, sdmmc::ResultDeviceStatus##__ERROR__()) + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(ComCrcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(DeviceEccFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CcError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(Error); + + if (this->GetDeviceType() == DeviceType_Mmc) { + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(SwitchError); + } + + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressMisaligned); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(BlockLenError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseSeqError); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseParam); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpViolation); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(LockUnlockFailed); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(CidCsdOverwrite); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(WpEraseSkip); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(EraseReset); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(IllegalCommand); + AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR(AddressOutOfRange); + + #undef AMS_SDMMC_CHECK_DEVICE_STATUS_ERROR + + return ResultSuccess(); + } + + DeviceState BaseDevice::GetDeviceState(u32 r1_resp) const { + return static_cast((r1_resp & DeviceStatus_CurrentStateMask) >> DeviceStatus_CurrentStateShift); + } + + Result BaseDeviceAccessor::IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, command_arg, CommandResponseType, is_busy); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + this->host_controller->GetLastResponse(out_response, sizeof(u32), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (status_ignore_mask != 0) { + *out_response &= ~status_ignore_mask; + } + + /* Check the r1 response for errors. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + R_TRY(this->base_device->CheckDeviceStatus(*out_response)); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(this->base_device->GetDeviceState(*out_response) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandGoIdleState() const { + /* Issue the command. */ + Command command(CommandIndex_GoIdleState, 0, ResponseType_R0, false); + return this->host_controller->IssueCommand(std::addressof(command)); + } + + Result BaseDeviceAccessor::IssueCommandAllSendCid(void *dst, size_t dst_size) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_AllSendCid, 0, CommandResponseType, false); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + this->host_controller->GetLastResponse(static_cast(dst), DeviceCidSize, CommandResponseType); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandSelectCard() const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + return this->IssueCommandAndCheckR1(CommandIndex_SelectCard, arg, true, DeviceState_Unknown); + } + + Result BaseDeviceAccessor::IssueCommandSendCsd(void *dst, size_t dst_size) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R2; + Command command(CommandIndex_SendCsd, arg, CommandResponseType, false); + R_TRY(this->host_controller->IssueCommand(std::addressof(command))); + + /* Copy the data out. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(dst), alignof(u32))); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + this->host_controller->GetLastResponse(static_cast(dst), DeviceCsdSize, CommandResponseType); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const { + /* Get the command argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = static_cast(this->base_device->GetRca()) << 16; + + /* Issue the command. */ + return this->IssueCommandAndCheckR1(out_device_status, CommandIndex_SendStatus, arg, false, DeviceState_Tran, status_ignore_mask); + } + + Result BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize() const { + /* Issue the command. */ + return this->IssueCommandAndCheckR1(CommandIndex_SetBlockLen, SectorSize, false, DeviceState_Tran); + } + + Result BaseDeviceAccessor::IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Get the argument. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + const u32 arg = this->base_device->IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Get the command index and transfer direction. */ + const u32 command_index = is_read ? CommandIndex_ReadMultipleBlock : CommandIndex_WriteMultipleBlock; + const auto xfer_direction = is_read ? TransferDirection_ReadFromDevice : TransferDirection_WriteToDevice; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(command_index, arg, CommandResponseType, false); + TransferData xfer_data(buf, SectorSize, num_sectors, xfer_direction, true, true); + Result result = this->host_controller->IssueCommand(std::addressof(command), std::addressof(xfer_data), out_num_transferred_blocks); + + /* Handle the failure case. */ + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* By default, we'll want to return the result we just got. */ + Result result_to_return = result; + + /* Stop transmission. */ + u32 resp = 0; + result = this->host_controller->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + result = this->base_device->CheckDeviceStatus(resp & (~DeviceStatus_IllegalCommand)); + if (R_FAILED(result)) { + result_to_return = result; + } + } + + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Get the device status. */ + u32 device_status = 0; + result = this->IssueCommandSendStatus(std::addressof(device_status), DeviceStatus_IllegalCommand); + + /* If there's a device status error we don't already have, we prefer to return it. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(result_to_return) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + result_to_return = result; + } + + /* Return the result we chose. */ + return result_to_return; + } + + /* Get the responses. */ + u32 resp, st_resp; + this->host_controller->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + this->host_controller->GetLastStopTransmissionResponse(std::addressof(st_resp), sizeof(st_resp)); + + /* Check the device status. */ + R_TRY(this->base_device->CheckDeviceStatus(resp)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Check the device status. */ + R_TRY(this->base_device->CheckDeviceStatus(st_resp & ~status_ignore_mask)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const { + /* Issue the read/write command. */ + AMS_ABORT_UNLESS(out_num_transferred_blocks != nullptr); + R_TRY(this->IssueCommandMultipleBlock(out_num_transferred_blocks, sector_index, num_sectors, buf, is_read)); + + /* Decide on what errors to ignore. */ + u32 status_ignore_mask = 0; + if (is_read) { + AMS_ABORT_UNLESS(this->base_device != nullptr); + if ((*out_num_transferred_blocks + sector_index) == this->base_device->GetMemoryCapacity()) { + status_ignore_mask = DeviceStatus_AddressOutOfRange; + } + } + + /* Get and check the status. */ + u32 device_status; + R_TRY(this->IssueCommandSendStatus(std::addressof(device_status), status_ignore_mask)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read) { + /* Verify that we can send the command. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + + /* If we want to read zero sectors, there's no work for us to do. */ + R_SUCCEED_IF(num_sectors == 0); + + /* Check that the buffer is big enough for the sectors we're reading. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Read sectors repeatedly until we've read all the ones we want. */ + u32 cur_sector_index = sector_index; + u32 remaining_sectors = num_sectors; + u8 *cur_buf = static_cast(buf); + while (remaining_sectors > 0) { + /* Determine how many sectors we can read in this iteration. */ + u32 cur_sectors = remaining_sectors; + if (sector_index_alignment > 0) { + AMS_ABORT_UNLESS((cur_sector_index % sector_index_alignment) == 0); + + const u32 max_sectors = this->host_controller->GetMaxTransferNumBlocks(); + if (remaining_sectors > max_sectors) { + cur_sectors = max_sectors - (max_sectors % sector_index_alignment); + } + } + + /* Try to perform the read/write. */ + u32 num_transferred_blocks = 0; + Result result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Retry the read/write. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Check if we were removed. */ + R_TRY(this->CheckRemoved()); + + /* Log that we failed to read/write. */ + this->PushErrorLog(false, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + + /* Re-startup the connection, to see if that helps. */ + R_TRY(this->ReStartup()); + + /* Retry the read/write a third time. */ + num_transferred_blocks = 0; + result = this->ReadWriteSingle(std::addressof(num_transferred_blocks), cur_sector_index, cur_sectors, cur_buf, is_read); + if (R_FAILED(result)) { + /* Log that we failed after a re-startup. */ + this->PushErrorLog(true, "%s %X %X:%X", is_read ? "R" : "W", cur_sector_index, cur_sectors, result.GetValue()); + return result; + } + + /* Log that we succeeded after a retry. */ + this->PushErrorLog(true, "%s %X %X:0", is_read ? "R" : "W", cur_sector_index, cur_sectors); + + /* Increment the number of error corrections we've done. */ + ++this->num_read_write_error_corrections; + } + } + + /* Update our tracking variables. */ + AMS_ABORT_UNLESS(remaining_sectors >= num_transferred_blocks); + remaining_sectors -= num_transferred_blocks; + cur_sector_index += num_transferred_blocks; + cur_buf += num_transferred_blocks * SectorSize; + } + + return ResultSuccess(); + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void BaseDeviceAccessor::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return this->host_controller->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void BaseDeviceAccessor::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Register the address. */ + return this->host_controller->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + #endif + + Result BaseDeviceAccessor::Activate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is awake. */ + R_UNLESS(this->base_device->IsAwake(), sdmmc::ResultNotAwakened()); + + /* If the device is already active, we don't need to do anything. */ + R_SUCCEED_IF(this->base_device->IsActive()); + + /* Activate the base device. */ + auto activate_guard = SCOPE_GUARD { ++this->num_activation_failures; }; + R_TRY(this->OnActivate()); + + /* We successfully activated the device. */ + activate_guard.Cancel(); + this->base_device->SetActive(); + + return ResultSuccess(); + } + + void BaseDeviceAccessor::Deactivate() { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Deactivate the base device. */ + if (this->base_device->IsActive()) { + this->host_controller->Shutdown(); + this->base_device->Deactivate(); + } + } + + Result BaseDeviceAccessor::ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Perform the read/write. */ + auto rw_guard = SCOPE_GUARD { ++this->num_read_write_failures; }; + R_TRY(this->OnReadWrite(sector_index, num_sectors, buffer, buffer_size, is_read)); + + /* We successfully performed the read/write. */ + rw_guard.Cancel(); + return ResultSuccess(); + } + + Result BaseDeviceAccessor::CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the current speed mode/bus width. */ + *out_speed_mode = this->host_controller->GetSpeedMode(); + *out_bus_width = this->host_controller->GetBusWidth(); + + /* Verify that we can get the status. */ + R_TRY(this->host_controller->GetInternalStatus()); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetMemoryCapacity(u32 *out_sectors) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the capacity. */ + AMS_ABORT_UNLESS(out_sectors != nullptr); + *out_sectors = this->base_device->GetMemoryCapacity(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetDeviceStatus(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the status. */ + R_TRY(this->IssueCommandSendStatus(out, 0)); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetOcr(u32 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the ocr. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = this->base_device->GetOcr(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetRca(u16 *out) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the rca. */ + AMS_ABORT_UNLESS(out != nullptr); + *out = this->base_device->GetRca(); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetCid(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the cid. */ + this->base_device->GetCid(out, size); + + return ResultSuccess(); + } + + Result BaseDeviceAccessor::GetCsd(void *out, size_t size) const { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Check that the device is accessible. */ + R_TRY(this->base_device->CheckAccessible()); + + /* Get the csd. */ + this->base_device->GetCsd(out, size); + + return ResultSuccess(); + } + + void BaseDeviceAccessor::GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) { + /* Lock exclusive access of the base device. */ + AMS_ABORT_UNLESS(this->base_device != nullptr); + AMS_SDMMC_LOCK_BASE_DEVICE_MUTEX(); + + /* Set the output error info. */ + AMS_ABORT_UNLESS(out_error_info != nullptr); + out_error_info->num_activation_failures = this->num_activation_failures; + out_error_info->num_activation_error_corrections = this->num_activation_error_corrections; + out_error_info->num_read_write_failures = this->num_read_write_failures; + out_error_info->num_read_write_error_corrections = this->num_read_write_error_corrections; + this->ClearErrorInfo(); + + /* Check if we should write logs. */ + if (out_log_size == nullptr) { + return; + } + + /* Check if we can write logs. */ + if (out_log_buffer == nullptr || log_buffer_size == 0) { + *out_log_size = 0; + return; + } + + /* Get and clear our logs. */ + #if defined(AMS_SDMMC_USE_LOGGER) + { + if (this->error_logger.HasLog()) { + this->PushErrorTimeStamp(); + + *out_log_size = this->error_logger.GetAndClearLogs(out_log_buffer, log_buffer_size); + } else { + *out_log_size = 0; + } + } + #else + { + *out_log_size = 0; + } + #endif + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp new file mode 100644 index 000000000..8cb0490e8 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_base_device_accessor.hpp @@ -0,0 +1,512 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_USE_LOGGER) + class Logger { + private: + static constexpr size_t LogLengthMax = 0x20; + static constexpr size_t LogCountMax = 0x10; + private: + char logs[LogCountMax][LogLengthMax]; + int log_index; + private: + void Clear() { + for (size_t i = 0; i < LogCountMax; ++i) { + this->logs[i][0] = '\0'; + } + this->log_index = 0; + } + + size_t Pop(char *dst, size_t dst_size) { + /* Decrease log index. */ + if ((--this->log_index) < 0) { + this->log_index = LogCountMax - 1; + } + + /* Check if we have a log. */ + if (this->logs[this->log_index][0] == '\0') { + return 0; + } + + /* Copy log to output. */ + const int len = ::ams::util::Strlcpy(dst, this->logs[this->log_index], dst_size); + + /* Clear the log we copied. */ + this->logs[this->log_index][0] = '\0'; + + return static_cast(len); + } + + public: + Logger() { + this->Clear(); + } + + void Push(const char *fmt, std::va_list vl) { + /* Format the log into the current buffer. */ + ::ams::util::TVSNPrintf(this->logs[this->log_index], LogLengthMax, fmt, vl); + + /* Update our log index. */ + if ((++this->log_index) >= static_cast(LogCountMax)) { + this->log_index = 0; + } + } + + void Push(const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + this->Push(fmt, vl); + va_end(vl); + } + + bool HasLog() const { + const int index = this->log_index > 0 ? this->log_index - 1 : static_cast(LogCountMax - 1); + return this->logs[index][0] != '\0'; + } + + size_t GetAndClearLogs(char *dst, size_t dst_size) { + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size > 0); + + /* Pop logs until we run out of them. */ + size_t total_len = 0; + while (true) { + /* Pop the current log. */ + const size_t cur_len = this->Pop(dst + total_len, dst_size - total_len); + if (cur_len == 0) { + break; + } + + /* Check if the log exceeded the buffer size. */ + if (total_len + cur_len + 1 >= dst_size) { + break; + } + + /* Advance the total length. */ + total_len += cur_len; + + /* Check if there's space for our separator. */ + if (total_len + 3 >= dst_size) { + break; + } + + dst[total_len + 0] = ','; + dst[total_len + 1] = ' '; + total_len += 2; + } + + /* Ensure that the complete log fits in the buffer. */ + if (total_len >= dst_size) { + total_len = dst_size - 1; + } + + /* Ensure null termination. */ + dst[total_len] = '\0'; + + /* Clear any remaining logs. */ + this->Clear(); + + /* Return the length of the logs, including null terminator. */ + return total_len + 1; + } + }; + #endif + + enum DeviceType { + DeviceType_Mmc = 0, + DeviceType_SdCard = 1, + DeviceType_GcAsic = 2, + }; + + enum DeviceState { + DeviceState_Idle = 0, + DeviceState_Ready = 1, + DeviceState_Ident = 2, + DeviceState_Stby = 3, + DeviceState_Tran = 4, + DeviceState_Data = 5, + DeviceState_Rcv = 6, + DeviceState_Prg = 7, + DeviceState_Dis = 8, + DeviceState_Rsvd0 = 9, + DeviceState_Rsvd1 = 10, + DeviceState_Rsvd2 = 11, + DeviceState_Rsvd3 = 12, + DeviceState_Rsvd4 = 13, + DeviceState_Rsvd5 = 14, + DeviceState_RsvdIoMode = 15, + DeviceState_Unknown = 16, + }; + + enum DeviceStatus : u32 { + DeviceStatus_AkeSeqError = (1u << 3), + DeviceStatus_AppCmd = (1u << 5), + DeviceStatus_SwitchError = (1u << 7), + DeviceStatus_EraseReset = (1u << 13), + DeviceStatus_WpEraseSkip = (1u << 15), + DeviceStatus_CidCsdOverwrite = (1u << 16), + DeviceStatus_Error = (1u << 19), + DeviceStatus_CcError = (1u << 20), + DeviceStatus_DeviceEccFailed = (1u << 21), + DeviceStatus_IllegalCommand = (1u << 22), + DeviceStatus_ComCrcError = (1u << 23), + DeviceStatus_LockUnlockFailed = (1u << 24), + DeviceStatus_WpViolation = (1u << 26), + DeviceStatus_EraseParam = (1u << 27), + DeviceStatus_EraseSeqError = (1u << 28), + DeviceStatus_BlockLenError = (1u << 29), + DeviceStatus_AddressMisaligned = (1u << 30), + DeviceStatus_AddressOutOfRange = (1u << 31), + + DeviceStatus_CurrentStateShift = 9, + DeviceStatus_CurrentStateMask = (0b1111u << DeviceStatus_CurrentStateShift), + + DeviceStatus_ErrorMask = (DeviceStatus_SwitchError | + DeviceStatus_EraseReset | + DeviceStatus_WpEraseSkip | + DeviceStatus_CidCsdOverwrite | + DeviceStatus_Error | + DeviceStatus_CcError | + DeviceStatus_DeviceEccFailed | + DeviceStatus_IllegalCommand | + DeviceStatus_ComCrcError | + DeviceStatus_LockUnlockFailed | + DeviceStatus_WpViolation | + DeviceStatus_EraseParam | + DeviceStatus_EraseSeqError | + DeviceStatus_BlockLenError | + DeviceStatus_AddressMisaligned | + DeviceStatus_AddressOutOfRange), + }; + + class BaseDevice { + private: + u32 ocr; + u8 cid[DeviceCidSize]; + u16 csd[DeviceCsdSize / sizeof(u16)]; + u32 memory_capacity; + bool is_high_capacity; + bool is_valid_ocr; + bool is_valid_cid; + bool is_valid_csd; + bool is_valid_high_capacity; + bool is_valid_memory_capacity; + bool is_active; + bool is_awake; + public: + #if defined(AMS_SDMMC_THREAD_SAFE) + mutable os::Mutex device_mutex; + public: + BaseDevice() : device_mutex(true) + #else + BaseDevice() + #endif + { + this->is_awake = true; + this->ocr = 0; + this->memory_capacity = 0; + this->is_high_capacity = false; + this->OnDeactivate(); + } + + void OnDeactivate() { + this->is_active = false; + this->is_valid_ocr = false; + this->is_valid_cid = false; + this->is_valid_csd = false; + this->is_valid_high_capacity = false; + this->is_valid_memory_capacity = false; + } + + bool IsAwake() const { + return this->is_awake; + } + + void Awaken() { + this->is_awake = true; + } + + void PutToSleep() { + this->is_awake = false; + } + + bool IsActive() const { + return this->is_active; + } + + void SetActive() { + this->is_active = true; + } + + virtual void Deactivate() { + this->OnDeactivate(); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const = 0; + #endif + + virtual DeviceType GetDeviceType() const = 0; + virtual u16 GetRca() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void InitializeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::InitializeEvent(removed_event, false, os::EventClearMode_ManualClear); + } + } + + void FinalizeRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::FinalizeEvent(removed_event); + } + } + + void SignalRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::SignalEvent(removed_event); + } + } + + void ClearRemovedEvent() { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + os::ClearEvent(removed_event); + } + } + + bool IsRemoved() const { + if (os::EventType *removed_event = this->GetRemovedEvent(); removed_event != nullptr) { + return os::TryWaitEvent(removed_event); + } + return false; + } + #endif + + Result CheckRemoved() const { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + return ResultSuccess(); + } + + Result CheckAccessible() const { + /* Check awake. */ + R_UNLESS(this->IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check active. */ + R_UNLESS(this->IsActive(), sdmmc::ResultNotActivated()); + + /* Check removed. */ + R_TRY(this->CheckRemoved()); + + return ResultSuccess(); + } + + void SetHighCapacity(bool en) { + this->is_high_capacity = en; + this->is_valid_high_capacity = true; + } + + bool IsHighCapacity() const { + AMS_ABORT_UNLESS(this->is_valid_high_capacity); + return this->is_high_capacity; + } + + void SetOcr(u32 o) { + this->ocr = o; + this->is_valid_ocr = true; + } + + u32 GetOcr() const { + AMS_ABORT_UNLESS(this->is_valid_ocr); + return this->ocr; + } + + void SetCid(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCidSize); + std::memcpy(this->cid, src, DeviceCidSize); + this->is_valid_cid = true; + } + + void GetCid(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(this->is_valid_cid); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCidSize); + std::memcpy(dst, this->cid, DeviceCidSize); + } + + void SetCsd(const void *src, size_t src_size) { + AMS_ABORT_UNLESS(src != nullptr); + AMS_ABORT_UNLESS(src_size >= DeviceCsdSize); + std::memcpy(this->csd, src, DeviceCsdSize); + this->is_valid_csd = true; + } + + void GetCsd(void *dst, size_t dst_size) const { + AMS_ABORT_UNLESS(this->is_valid_csd); + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= DeviceCsdSize); + std::memcpy(dst, this->csd, DeviceCsdSize); + } + + void SetMemoryCapacity(u32 num_sectors) { + this->memory_capacity = num_sectors; + this->is_valid_memory_capacity = true; + } + + u32 GetMemoryCapacity() const { + AMS_ABORT_UNLESS(this->is_valid_memory_capacity); + return this->memory_capacity; + } + + void GetLegacyCapacityParameters(u8 *out_c_size_mult, u8 *out_read_bl_len) const; + Result SetLegacyMemoryCapacity(); + + Result CheckDeviceStatus(u32 r1_resp) const; + DeviceState GetDeviceState(u32 r1_resp) const; + }; + + class BaseDeviceAccessor : public IDeviceAccessor { + private: + IHostController *host_controller; + BaseDevice *base_device; + u32 num_activation_failures; + u32 num_activation_error_corrections; + u32 num_read_write_failures; + u32 num_read_write_error_corrections; + #if defined(AMS_SDMMC_USE_LOGGER) + Logger error_logger; + #endif + private: + void ClearErrorInfo() { + this->num_activation_failures = 0; + this->num_activation_error_corrections = 0; + this->num_read_write_failures = 0; + this->num_read_write_error_corrections = 0; + } + protected: + explicit BaseDeviceAccessor(IHostController *hc) : host_controller(hc), base_device(nullptr) { + this->ClearErrorInfo(); + } + + IHostController *GetHostController() const { + return this->host_controller; + } + + void SetDevice(BaseDevice *bd) { + this->base_device = bd; + } + + Result CheckRemoved() const { + return this->base_device->CheckRemoved(); + } + + Result IssueCommandAndCheckR1(u32 *out_response, u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const; + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state, u32 status_ignore_mask) const { + u32 dummy; + return this->IssueCommandAndCheckR1(std::addressof(dummy), command_index, command_arg, is_busy, expected_state, status_ignore_mask); + } + + Result IssueCommandAndCheckR1(u32 command_index, u32 command_arg, bool is_busy, DeviceState expected_state) const { + return this->IssueCommandAndCheckR1(command_index, command_arg, is_busy, expected_state, 0); + } + + Result IssueCommandGoIdleState() const; + Result IssueCommandAllSendCid(void *dst, size_t dst_size) const; + Result IssueCommandSelectCard() const; + Result IssueCommandSendCsd(void *dst, size_t dst_size) const; + Result IssueCommandSendStatus(u32 *out_device_status, u32 status_ignore_mask) const; + + Result IssueCommandSendStatus(u32 status_ignore_mask) const { + u32 dummy; + return this->IssueCommandSendStatus(std::addressof(dummy), status_ignore_mask); + } + + Result IssueCommandSendStatus() const { + return this->IssueCommandSendStatus(0); + } + + Result IssueCommandSetBlockLenToSectorSize() const; + Result IssueCommandMultipleBlock(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteSingle(u32 *out_num_transferred_blocks, u32 sector_index, u32 num_sectors, void *buf, bool is_read) const; + Result ReadWriteMultiple(u32 sector_index, u32 num_sectors, u32 sector_index_alignment, void *buf, size_t buf_size, bool is_read); + + void IncrementNumActivationErrorCorrections() { + ++this->num_activation_error_corrections; + } + + void PushErrorTimeStamp() { + #if defined(AMS_SDMMC_USE_LOGGER) + { + this->error_logger.Push("%u", static_cast(os::ConvertToTimeSpan(os::GetSystemTick()).GetSeconds())); + } + #endif + } + + void PushErrorLog(bool with_timestamp, const char *fmt, ...) { + #if defined(AMS_SDMMC_USE_LOGGER) + { + std::va_list vl; + va_start(vl, fmt); + this->error_logger.Push(fmt, vl); + va_end(vl); + + if (with_timestamp) { + this->PushErrorTimeStamp(); + } + } + #else + { + AMS_UNUSED(with_timestamp, fmt); + } + #endif + } + + virtual Result OnActivate() = 0; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) = 0; + virtual Result ReStartup() = 0; + public: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual Result Activate() override; + virtual void Deactivate() override; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) override; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) override; + + virtual Result GetMemoryCapacity(u32 *out_sectors) const override; + virtual Result GetDeviceStatus(u32 *out) const override; + virtual Result GetOcr(u32 *out) const override; + virtual Result GetRca(u16 *out) const override; + virtual Result GetCid(void *out, size_t size) const override; + virtual Result GetCsd(void *out, size_t size) const override; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) override; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp new file mode 100644 index 000000000..17903800b --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.board.nintendo_nx.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.hpp" +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController { + + namespace { + + constinit bool g_is_module_initialized[Module_Count] = {}; + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_module_mutex; + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() std::scoped_lock lk(g_module_mutex) + + #else + + #define AMS_SDMMC_LOCK_MODULE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + constinit bool g_is_pcv_control = false; + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if (g_is_pcv_control) + + #else + + #define AMS_SDMMC_IF_IS_PCV_CONTROL() if constexpr (false) + + #endif + + } + + void Initialize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Mark the module as initialized. */ + g_is_module_initialized[module] = true; + + /* Initialize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Initialize(module); + } else { + ClockResetController::reg::Initialize(module); + } + } + + void Finalize(Module module) { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* Finalize the module. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + ClockResetController::pcv::Finalize(module); + } else { + ClockResetController::reg::Finalize(module); + } + + /* Mark the module as finalized. */ + g_is_module_initialized[module] = false; + } + + bool IsAvailable(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::IsAvailable(module); + } else { + return ClockResetController::reg::IsAvailable(module); + } + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl() { + /* Acquire exclusive access to module state. */ + AMS_SDMMC_LOCK_MODULE_MUTEX(); + + /* If we're already using pcv control, we don't need to do anything. */ + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return; + } + + /* Finalize all modules. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::reg::Finalize(static_cast(i)); + } + } + + /* Mark that we've switched to pcv control. */ + + /* Initialize modules using pcv control. */ + for (int i = 0; i < Module_Count; ++i) { + if (g_is_module_initialized[i]) { + ClockResetController::pcv::Initialize(static_cast(i)); + } + } + } + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } else { + return ClockResetController::reg::SetClockFrequencyKHz(out_actual_frequency, module, target_frequency); + } + } + + void AssertReset(Module module) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::AssertReset(module); + } else { + return ClockResetController::reg::AssertReset(module); + } + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_SDMMC_IF_IS_PCV_CONTROL() { + return ClockResetController::pcv::ReleaseReset(module, target_frequency_khz); + } else { + return ClockResetController::reg::ReleaseReset(module, target_frequency_khz); + } + } + + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp new file mode 100644 index 000000000..29ac27601 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.hpp @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::sdmmc::impl::ClockResetController { + + enum Module { + #if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + Module_Sdmmc1 = 0, + Module_Sdmmc2 = 1, + Module_Sdmmc3 = 2, + Module_Sdmmc4 = 3, + #endif + + Module_Count, + }; + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvControl(); + #endif + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp new file mode 100644 index 000000000..8d8c39deb --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not implemented"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not implemented"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not implemented"); + } + + #else + + void Initialize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void Finalize(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + bool IsAvailable(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency) { + AMS_UNUSED(out_actual_frequency, module, target_frequency); + AMS_ABORT("PCV Control not supported"); + } + + void AssertReset(Module module) { + AMS_UNUSED(module); + AMS_ABORT("PCV Control not supported"); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + AMS_UNUSED(module, target_frequency_khz); + AMS_ABORT("PCV Control not supported"); + } + + #endif + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp new file mode 100644 index 000000000..85144bff3 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.pcv.board.nintendo_nx.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::pcv { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp new file mode 100644 index 000000000..3a494281e --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + constinit os::SdkMutex g_init_mutex; + + #define AMS_SDMMC_LOCK_INIT_MUTEX() std::scoped_lock lk(g_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_INIT_MUTEX() + + #endif + + constinit bool g_is_initialized = false; + + struct ModuleInfo { + u32 target_frequency_khz; + u32 actual_frequency_khz; + }; + + constinit ModuleInfo g_module_infos[Module_Count] = {}; + + constexpr inline dd::PhysicalAddress ClockResetControllerRegistersPhysicalAddress = UINT64_C(0x60006000); + constexpr inline size_t ClockResetControllerRegistersSize = 4_KB; + + constinit uintptr_t g_clkrst_registers_address = 0; + + [[maybe_unused]] void InitializePllc4() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Check if PLLC4_BASE has the expected value; if it does, we have nothing to do. */ + constexpr u32 ExpectedPllc4Base = 0x58006804; + if (ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE) == ExpectedPllc4Base) { + return; + } + + /* Disable PLLC4_ENABLE, if it's currently set. */ + if (ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE))) { + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, DISABLE)); + } + + /* Operate on the register with PLLC4_ENABLE cleared. */ + { + /* Clear IDDQ, read to be sure it takes, wait 5 us. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_IDDQ, OFF)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + WaitMicroSeconds(5); + + /* Write the expected value sans IDDQ/PLLC4_ENABLE. */ + constexpr u32 ExpectedPllc4BaseMask = ~ams::reg::EncodeMask(CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_ENABLE), + CLK_RST_REG_BITS_MASK(PLLC4_BASE_PLLC4_IDDQ)); + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, ExpectedPllc4Base & ExpectedPllc4BaseMask); + } + + /* Write PLLC4_ENABLE, and read to be sure our configuration takes. */ + ams::reg::ReadWrite(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_ENABLE, ENABLE)); + ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE); + + /* Wait up to 1s for changes to take. */ + { + ManualTimer timer(1000); + while (true) { + /* Check if we're done. */ + if (!ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_PLLC4_BASE, CLK_RST_REG_BITS_ENUM(PLLC4_BASE_PLLC4_LOCK, NOT_LOCK))) { + break; + } + + /* Check that we haven't timed out. */ + AMS_ABORT_UNLESS(timer.Update()); + } + } + } + + void InitializeLegacyTmClk() { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Configure the legacy tm clock as 12MHz. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM, CLK_RST_REG_BITS_ENUM (CLK_SOURCE_LEGACY_TM_CLK_SRC, PLLP_OUT0), + CLK_RST_REG_BITS_VALUE(CLK_SOURCE_LEGACY_TM_CLK_DIVISOR, 66)); + + /* Enable clock to the legacy tm. */ + ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_Y_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_Y_CLK_ENB_LEGACY_TM, ENABLE)); + } + + bool IsResetReleased(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the reset bit from RST_DEVICES_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC1_RST, DISABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC2_RST, DISABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U, CLK_RST_REG_BITS_ENUM(RST_DEVICES_U_SWR_SDMMC3_RST, DISABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L, CLK_RST_REG_BITS_ENUM(RST_DEVICES_L_SWR_SDMMC4_RST, DISABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearReset(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set reset in RST_DEV_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC1_RST, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC2_RST, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_U_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_U_SDMMC3_RST, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, CLK_RST_REG_BITS_ENUM(RST_DEV_L_SDMMC4_RST, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + bool IsClockEnabled(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Get the enable bit from CLK_OUT_ENB_* */ + switch (module) { + case Module_Sdmmc1: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_U, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::HasValue(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_OUT_ENB_L, CLK_RST_REG_BITS_ENUM(CLK_OUT_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_SET */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_SET, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void ClearClockEnable(Module module) { + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Set clock enable bit in CLK_ENB_*_CLR */ + switch (module) { + case Module_Sdmmc1: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC1, ENABLE)); + case Module_Sdmmc2: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC2, ENABLE)); + case Module_Sdmmc3: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_U_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_U_CLK_ENB_SDMMC3, ENABLE)); + case Module_Sdmmc4: return ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_ENB_L_CLR, CLK_RST_REG_BITS_ENUM(CLK_ENB_L_CLK_ENB_SDMMC4, ENABLE)); + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SetClockSourceSdmmc(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_actual_frequency_khz != nullptr); + + /* Determine frequency/divisor. */ + u32 clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLP_OUT0)); + u8 n; + switch (target_frequency_khz) { + case 25'000: + *out_actual_frequency_khz = 24'728; + n = 31; + break; + case 26'000: + *out_actual_frequency_khz = 25'500; + n = 30; + break; + case 40'800: + *out_actual_frequency_khz = 40'800; + n = 18; + break; + case 50'000: + *out_actual_frequency_khz = 48'000; + n = 15; + break; + case 52'000: + *out_actual_frequency_khz = 51'000; + n = 14; + break; + case 100'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 99'840; + n = 2; + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + #else + *out_actual_frequency_khz = 90'667; + n = 7; + #endif + break; + case 200'000: + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + *out_actual_frequency_khz = 199'680; + n = 0; + if (module == Module_Sdmmc2 || module == Module_Sdmmc4) { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMC24_SDMMC24_CLK_SRC, PLLC4_OUT2_LJ)); + } else { + clk_m = ams::reg::Encode(CLK_RST_REG_BITS_ENUM(CLK_SOURCE_SDMMCX_SDMMCX_CLK_SRC, PLLC4_OUT2)); + } + #else + *out_actual_frequency_khz = 163'200; + n = 3; + #endif + break; + case 208'000: + *out_actual_frequency_khz = 204'000; + n = 2; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Set frequencies in module info. */ + g_module_infos[module].target_frequency_khz = target_frequency_khz; + g_module_infos[module].actual_frequency_khz = *out_actual_frequency_khz; + + /* Check that we have registers we can write to. */ + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Update the clock source. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1, clk_m | static_cast(n)); break; + case Module_Sdmmc2: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2, clk_m | static_cast(n)); break; + case Module_Sdmmc3: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3, clk_m | static_cast(n)); break; + case Module_Sdmmc4: ams::reg::Write(g_clkrst_registers_address + CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4, clk_m | static_cast(n)); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void EnsureControl(Module module) { + /* Read from RST_DEVICES_* to be sure previous configuration takes. */ + switch (module) { + case Module_Sdmmc1: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc2: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + case Module_Sdmmc3: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_U); break; + case Module_Sdmmc4: ams::reg::Read(g_clkrst_registers_address + CLK_RST_CONTROLLER_RST_DEVICES_L); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + + void Initialize(Module module) { + /* Initialization isn't module specific. */ + AMS_UNUSED(module); + + /* Acquire exclusive access to the initialization lock. */ + AMS_SDMMC_LOCK_INIT_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (g_is_initialized) { + return; + } + + /* Clear module infos. */ + std::memset(g_module_infos, 0, sizeof(g_module_infos)); + + /* Get the registers address. */ + g_clkrst_registers_address = dd::QueryIoMapping(ClockResetControllerRegistersPhysicalAddress, ClockResetControllerRegistersSize); + AMS_ABORT_UNLESS(g_clkrst_registers_address != 0); + + /* Perform register initialization. */ + #if defined(AMS_SDMMC_SET_PLLC4_BASE) + InitializePllc4(); + #endif + InitializeLegacyTmClk(); + + /* Mark that we've initialized. */ + g_is_initialized = true; + } + + void Finalize(Module module) { + /* Nothing is needed for finalization. */ + AMS_UNUSED(module); + } + + bool IsAvailable(Module module) { + return IsResetReleased(module) && IsClockEnabled(module); + } + + void SetClockFrequencyKHz(u32 *out_actual_frequency_khz, Module module, u32 target_frequency_khz) { + /* If we're not changing the clock frequency, we don't need to do anything. */ + if (target_frequency_khz == g_module_infos[module].target_frequency_khz) { + *out_actual_frequency_khz = g_module_infos[module].actual_frequency_khz; + return; + } + + /* Temporarily disable clock. */ + const bool clock_enabled = IsClockEnabled(module); + if (clock_enabled) { + ClearClockEnable(module); + } + + /* Set the clock source. */ + SetClockSourceSdmmc(out_actual_frequency_khz, module, target_frequency_khz); + + /* Re-enable clock, if we should. */ + if (clock_enabled) { + SetClockEnable(module); + } + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void AssertReset(Module module) { + /* Set reset and disable clock. */ + SetReset(module); + ClearClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + + void ReleaseReset(Module module, u32 target_frequency_khz) { + /* Disable clock if it's enabled. */ + if (IsClockEnabled(module)) { + ClearClockEnable(module); + } + + /* Set reset. */ + SetReset(module); + + /* Set the clock source. */ + u32 actual_source_frequency_khz; + SetClockSourceSdmmc(std::addressof(actual_source_frequency_khz), module, target_frequency_khz); + + /* Enable clock. */ + SetClockEnable(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + + /* Wait 100 clocks. */ + WaitClocks(100, actual_source_frequency_khz); + + /* Clear reset. */ + ClearReset(module); + + /* Ensure that our configuration takes. */ + EnsureControl(module); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp new file mode 100644 index 000000000..4043037e4 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_clock_reset_controller.reg.board.nintendo_nx.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl::ClockResetController::reg { + + void Initialize(Module module); + void Finalize(Module module); + bool IsAvailable(Module module); + + void SetClockFrequencyKHz(u32 *out_actual_frequency, Module module, u32 target_frequency); + void AssertReset(Module module); + void ReleaseReset(Module module, u32 target_frequency_khz); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp new file mode 100644 index 000000000..3529b85a0 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" + +namespace ams::sdmmc::impl { + + bool DeviceDetector::IsCurrentInserted() { + return gpio::GetValue(std::addressof(this->gpio_pad_session)) == this->inserted_gpio_value; + } + + void DeviceDetector::HandleDeviceStatus(bool prev_inserted, bool cur_inserted) { + if (!prev_inserted && !cur_inserted) { + /* Not inserted -> Not inserted, nothing to do. */ + } else if (!prev_inserted && cur_inserted) { + /* Card was inserted. */ + if (this->callback_info.inserted_callback != nullptr) { + this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); + } + } else if (prev_inserted && !cur_inserted) { + /* Card was removed. */ + if (this->callback_info.removed_callback != nullptr) { + this->callback_info.removed_callback(this->callback_info.removed_callback_arg); + } + } else /* if (prev_inserted && cur_inserted) */ { + /* Card was removed, and then inserted. */ + if (this->callback_info.removed_callback != nullptr) { + this->callback_info.removed_callback(this->callback_info.removed_callback_arg); + } + + if (this->callback_info.inserted_callback != nullptr) { + this->callback_info.inserted_callback(this->callback_info.inserted_callback_arg); + } + } + } + + void DeviceDetector::DetectorThread() { + /* Initialize the gpio session. */ + sm::DoWithSession([] { gpio::Initialize(); }); + gpio::OpenSession(std::addressof(this->gpio_pad_session), this->gpio_device_code); + gpio::SetDirection(std::addressof(this->gpio_pad_session), gpio::Direction_Input); + gpio::SetDebounceTime(std::addressof(this->gpio_pad_session), this->gpio_debounce_ms); + gpio::SetDebounceEnabled(std::addressof(this->gpio_pad_session), true); + gpio::SetInterruptMode(std::addressof(this->gpio_pad_session), gpio::InterruptMode_AnyEdge); + + /* Get the gpio session's interrupt event. */ + os::SystemEventType gpio_event; + R_ABORT_UNLESS(gpio::BindInterrupt(std::addressof(gpio_event), std::addressof(this->gpio_pad_session))); + + /* Initialize and link waitable holders. */ + os::WaitableManagerType wait_manager; + os::WaitableHolderType detector_thread_end_holder; + os::WaitableHolderType request_sleep_wake_event_holder; + os::WaitableHolderType gpio_event_holder; + os::InitializeWaitableManager(std::addressof(wait_manager)); + os::InitializeWaitableHolder(std::addressof(detector_thread_end_holder), std::addressof(this->detector_thread_end_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(detector_thread_end_holder)); + os::InitializeWaitableHolder(std::addressof(request_sleep_wake_event_holder), std::addressof(this->request_sleep_wake_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(request_sleep_wake_event_holder)); + os::InitializeWaitableHolder(std::addressof(gpio_event_holder), std::addressof(gpio_event)); + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); + + /* Wait before detecting the initial state of the card. */ + os::SleepThread(TimeSpan::FromMilliSeconds(this->gpio_debounce_ms)); + bool cur_inserted = this->IsCurrentInserted(); + this->is_prev_inserted = cur_inserted; + + /* Set state as awake. */ + this->state = State_Awake; + os::SignalEvent(std::addressof(this->ready_device_status_event)); + + /* Enable interrupts to be informed of device status. */ + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); + + /* Wait, servicing our events. */ + while (true) { + /* Get the signaled holder. */ + os::WaitableHolderType *signaled_holder = os::WaitAny(std::addressof(wait_manager)); + + /* Process the holder. */ + bool insert_change = false; + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(this->detector_thread_end_event)); + this->state = State_Finalized; + break; + } else if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) { + /* A request for us to sleep/wake has come in, so we'll acknowledge it. */ + os::ClearEvent(std::addressof(this->request_sleep_wake_event)); + this->state = State_Sleep; + os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); + + /* Temporarily unlink our interrupt event. */ + os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); + + /* Wait to be signaled. */ + signaled_holder = os::WaitAny(std::addressof(wait_manager)); + + /* Link our interrupt event back in. */ + os::LinkWaitableHolder(std::addressof(wait_manager), std::addressof(gpio_event_holder)); + + /* We're awake again. Either because we should exit, or because we were asked to wake up. */ + os::ClearEvent(std::addressof(this->request_sleep_wake_event)); + this->state = State_Awake; + os::SignalEvent(std::addressof(this->acknowledge_sleep_awake_event)); + + /* If we were asked to exit, do so. */ + if (signaled_holder == std::addressof(detector_thread_end_holder)) { + /* We should kill ourselves. */ + os::ClearEvent(std::addressof(this->detector_thread_end_event)); + this->state = State_Finalized; + break; + } else /* if (signaled_holder == std::addressof(request_sleep_wake_event_holder)) */ { + if ((this->force_detection) || + (({ bool active; R_SUCCEEDED(gpio::IsWakeEventActive(std::addressof(active), this->gpio_device_code)) && active; })) || + (os::TryWaitSystemEvent(std::addressof(gpio_event))) || + (this->is_prev_inserted != this->IsCurrentInserted())) + { + insert_change = true; + } + } + } else /* if (signaled_holder == std::addressof(gpio_event_holder)) */ { + /* An event was detected. */ + insert_change = true; + } + + /* Handle an insert change, if one occurred. */ + if (insert_change) { + /* Call the relevant callback, if we have one. */ + if (this->device_detection_event_callback != nullptr) { + this->device_detection_event_callback(this->device_detection_event_callback_arg); + } + + /* Clear the interrupt event. */ + os::ClearSystemEvent(std::addressof(gpio_event)); + gpio::ClearInterruptStatus(std::addressof(this->gpio_pad_session)); + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), true); + + /* Update insertion status. */ + cur_inserted = this->IsCurrentInserted(); + this->HandleDeviceStatus(this->is_prev_inserted, cur_inserted); + this->is_prev_inserted = cur_inserted; + } + } + + /* Disable interrupts to our gpio event. */ + gpio::SetInterruptEnable(std::addressof(this->gpio_pad_session), false); + + /* Finalize and unlink waitable holders. */ + os::UnlinkWaitableHolder(std::addressof(gpio_event_holder)); + os::FinalizeWaitableHolder(std::addressof(gpio_event_holder)); + os::UnlinkWaitableHolder(std::addressof(request_sleep_wake_event_holder)); + os::FinalizeWaitableHolder(std::addressof(request_sleep_wake_event_holder)); + os::UnlinkWaitableHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeWaitableHolder(std::addressof(detector_thread_end_holder)); + os::FinalizeWaitableManager(std::addressof(wait_manager)); + + /* Finalize the gpio session. */ + gpio::UnbindInterrupt(std::addressof(this->gpio_pad_session)); + gpio::CloseSession(std::addressof(this->gpio_pad_session)); + gpio::Finalize(); + } + + void DeviceDetector::Initialize(CallbackInfo *ci) { + /* Transition our state from finalized to initializing. */ + AMS_ABORT_UNLESS(this->state == State_Finalized); + this->state = State_Initializing; + + /* Set our callback infos. */ + this->callback_info = *ci; + + /* Initialize our events. */ + os::InitializeEvent(std::addressof(this->ready_device_status_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->request_sleep_wake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->acknowledge_sleep_awake_event), false, os::EventClearMode_ManualClear); + os::InitializeEvent(std::addressof(this->detector_thread_end_event), false, os::EventClearMode_ManualClear); + + /* Create and start the detector thread. */ + os::CreateThread(std::addressof(this->detector_thread), DetectorThreadEntry, this, this->detector_thread_stack, sizeof(this->detector_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(sdmmc, DeviceDetector)); + os::SetThreadNamePointer(std::addressof(this->detector_thread), AMS_GET_SYSTEM_THREAD_NAME(sdmmc, DeviceDetector)); + os::StartThread(std::addressof(this->detector_thread)); + } + + void DeviceDetector::Finalize() { + /* Ensure we're not already finalized. */ + AMS_ABORT_UNLESS(this->state != State_Finalized); + + /* Signal event to end the detector thread. */ + os::SignalEvent(std::addressof(this->detector_thread_end_event)); + os::WaitThread(std::addressof(this->detector_thread)); + + /* Finalize thread and events. */ + os::DestroyThread(std::addressof(this->detector_thread)); + os::FinalizeEvent(std::addressof(this->ready_device_status_event)); + os::FinalizeEvent(std::addressof(this->request_sleep_wake_event)); + os::FinalizeEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::FinalizeEvent(std::addressof(this->detector_thread_end_event)); + } + + void DeviceDetector::PutToSleep() { + /* Signal request, wait for acknowledgement. */ + os::SignalEvent(std::addressof(this->request_sleep_wake_event)); + os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); + } + + void DeviceDetector::Awaken(bool force_det) { + /* Signal request, wait for acknowledgement. */ + this->force_detection = force_det; + os::SignalEvent(std::addressof(this->request_sleep_wake_event)); + os::WaitEvent(std::addressof(this->acknowledge_sleep_awake_event)); + os::ClearEvent(std::addressof(this->acknowledge_sleep_awake_event)); + } + + bool DeviceDetector::IsInserted() { + bool inserted = false; + + switch (this->state) { + case State_Initializing: + /* Wait for us to know whether the device is inserted. */ + os::WaitEvent(std::addressof(this->ready_device_status_event)); + [[fallthrough]]; + case State_Awake: + /* Get whether the device is currently inserted. */ + inserted = this->IsCurrentInserted(); + break; + case State_Sleep: + case State_Finalized: + /* Get whether the device was inserted when we last knew. */ + inserted = this->is_prev_inserted; + break; + } + + return inserted; + } + + void DeviceDetector::RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + this->device_detection_event_callback_arg = arg; + this->device_detection_event_callback = cb; + } + + void DeviceDetector::UnregisterDetectionEventCallback() { + this->device_detection_event_callback = nullptr; + } + +} + +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp new file mode 100644 index 000000000..a61f05095 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_device_detector.hpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include + +namespace ams::sdmmc::impl { + + using InsertedCallback = void (*)(void *); + using RemovedCallback = void (*)(void *); + + struct CallbackInfo { + InsertedCallback inserted_callback; + void *inserted_callback_arg; + RemovedCallback removed_callback; + void *removed_callback_arg; + }; + + class DeviceDetector { + private: + enum State { + State_Initializing = 0, + State_Awake = 1, + State_Sleep = 2, + State_Finalized = 3, + }; + private: + alignas(os::ThreadStackAlignment) u8 detector_thread_stack[8_KB]; + State state; + bool is_prev_inserted; + bool force_detection; + os::ThreadType detector_thread; + os::EventType detector_thread_end_event; + os::EventType request_sleep_wake_event; + os::EventType acknowledge_sleep_awake_event; + os::EventType ready_device_status_event; + + DeviceCode gpio_device_code; + gpio::GpioValue inserted_gpio_value; + u32 gpio_debounce_ms; + gpio::GpioPadSession gpio_pad_session; + + CallbackInfo callback_info; + + DeviceDetectionEventCallback device_detection_event_callback; + void *device_detection_event_callback_arg; + private: + static void DetectorThreadEntry(void *arg) { + reinterpret_cast(arg)->DetectorThread(); + } + + void DetectorThread(); + bool IsCurrentInserted(); + void HandleDeviceStatus(bool prev_inserted, bool cur_inserted); + public: + explicit DeviceDetector(DeviceCode dc, gpio::GpioValue igv, u32 gd) + : gpio_device_code(dc), inserted_gpio_value(igv), gpio_debounce_ms(gd) + { + this->state = State_Finalized; + this->is_prev_inserted = false; + this->force_detection = false; + this->callback_info = {}; + this->device_detection_event_callback = nullptr; + this->device_detection_event_callback_arg = nullptr; + } + + void Initialize(CallbackInfo *ci); + void Finalize(); + + void PutToSleep(); + void Awaken(bool force_det); + + u32 GetDebounceMilliSeconds() const { + return this->gpio_debounce_ms; + } + + bool IsInserted(); + + void RegisterDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg); + void UnregisterDetectionEventCallback(); + }; + +} + +#endif \ No newline at end of file diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp new file mode 100644 index 000000000..a1ebe02f6 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.cpp @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_gc_asic_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() std::scoped_lock lk(this->gc_asic_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() R_UNLESS(!this->gc_asic_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_GC_ASIC_REMOVED() + + #endif + + Result GcAsicDeviceAccessor::IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const { + /* Validate the operation buffer. */ + AMS_ABORT_UNLESS(op_buf != nullptr); + AMS_ABORT_UNLESS(op_buf_size >= GcAsicOperationSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_GcAsicWriteOperation, 0, CommandResponseType, false); + TransferData xfer_data(const_cast(op_buf), GcAsicOperationSize, 1, TransferDirection_WriteToDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + Result result = hc->IssueCommand(std::addressof(command), std::addressof(xfer_data)); + if (R_FAILED(result)) { + /* We failed to write operation. Check if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Determine what result we should return. */ + Result return_result = result; + { + /* Issue a stop transmission command. */ + u32 resp = 0; + result = hc->IssueStopTransmissionCommand(std::addressof(resp)); + if (R_SUCCEEDED(result)) { + /* If we successfully stopped transmission but have an error status, we prefer to return that. */ + result = this->gc_asic_device.CheckDeviceStatus(resp); + if (R_FAILED(result)) { + return_result = result; + } + } + + /* Check again if we were removed. */ + AMS_SDMMC_CHECK_GC_ASIC_REMOVED(); + + /* Request device status. */ + u32 device_status; + result = BaseDeviceAccessor::IssueCommandSendStatus(std::addressof(device_status), 0); + + /* If we got a device status error here and we didn't previously, we prefer to return that. */ + if (!sdmmc::ResultDeviceStatusHasError::Includes(return_result) && sdmmc::ResultDeviceStatusHasError::Includes(result)) { + return_result = result; + } + } + return return_result; + } + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandFinishOperation() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicFinishOperation, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandSleep() { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicSleep, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::IssueCommandUpdateKey() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_GcAsicUpdateKey, 0, true, DeviceState_Tran)); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::StartupGcAsicDevice() { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_1_8V, BusWidth_8Bit, SpeedMode_GcAsicSpeed, false)); + + /* Wait 10 clocks for configuration to take. */ + WaitClocks(10, hc->GetDeviceClockFrequencyKHz()); + + /* Perform tuning with command index 21. */ + AMS_ABORT_UNLESS(hc->IsSupportedTuning()); + R_TRY(hc->Tuning(SpeedMode_GcAsicSpeed, 21)); + + /* Set the device as low capacity/no memory. */ + this->gc_asic_device.SetHighCapacity(false); + this->gc_asic_device.SetMemoryCapacity(0); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::OnActivate() { + /* If we fail to start up the device, ensure the host controller is shut down. */ + auto power_guard = SCOPE_GUARD { BaseDeviceAccessor::GetHostController()->Shutdown(); }; + + /* Try to start up the device. */ + R_TRY(this->StartupGcAsicDevice()); + + /* We started up, so we don't need to power down. */ + power_guard.Cancel(); + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Check that we're not performing zero-byte rw. */ + AMS_ABORT_UNLESS(num_sectors > 0); + + /* Check that the buffer is big enough for the rw. */ + AMS_ABORT_UNLESS((buf_size / SectorSize) >= num_sectors); + + /* Perform the read/write. */ + u32 num_transferred_blocks; + R_TRY(BaseDeviceAccessor::ReadWriteSingle(std::addressof(num_transferred_blocks), sector_index, num_sectors, buf, is_read)); + + /* Require that we read/wrote as many sectors as we expected. */ + AMS_ABORT_UNLESS(num_transferred_blocks == num_sectors); + + return ResultSuccess(); + } + + void GcAsicDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our gc asic device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->gc_asic_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->gc_asic_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(this->gc_asic_device.GetRemovedEvent()); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + this->is_initialized = true; + } + + void GcAsicDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the removed event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->gc_asic_device.FinalizeRemovedEvent(); + } + #endif + } + + Result GcAsicDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + *out_speed_mode = SpeedMode_GcAsicSpeed; + return ResultSuccess(); + } + + void GcAsicDeviceAccessor::PutGcAsicToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->gc_asic_device.IsAwake()) { + return; + } + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) + #else + if (this->gc_asic_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + + /* Put the gc asic device to sleep. */ + this->gc_asic_device.PutToSleep(); + } + + Result GcAsicDeviceAccessor::AwakenGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + R_SUCCEED_IF(this->gc_asic_device.IsAwake()); + + /* Wake the device. */ + this->gc_asic_device.Awaken(); + + /* Wake the host controller, if we need to.*/ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + if (this->gc_asic_device.IsActive() && !this->gc_asic_device.IsRemoved()) + #else + if (this->gc_asic_device.IsActive()) + #endif + { + R_TRY(BaseDeviceAccessor::GetHostController()->Awaken()); + } + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::WriteGcAsicOperation(const void *op_buf, size_t op_buf_size) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandWriteOperation(op_buf, op_buf_size)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::FinishGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandFinishOperation()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::AbortGcAsicOperation() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue stop transmission command. */ + u32 resp = 0; + R_TRY(BaseDeviceAccessor::GetHostController()->IssueStopTransmissionCommand(std::addressof(resp))); + R_TRY(this->gc_asic_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::SleepGcAsic() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandSleep()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result GcAsicDeviceAccessor::UpdateGcAsicKey() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_GC_ASIC_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->gc_asic_device.CheckAccessible()); + + /* Issue the command. */ + R_TRY(this->IssueCommandUpdateKey()); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp new file mode 100644 index 000000000..c6c24ec1c --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_gc_asic_device_accessor.hpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class GcAsicDevice : public BaseDevice { + private: + static constexpr u16 Rca = 0; + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + mutable os::EventType removed_event; + #endif + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(this->removed_event); + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_GcAsic; + } + + virtual u16 GetRca() const override { + return Rca; + } + }; + + class GcAsicDeviceAccessor : public BaseDeviceAccessor { + private: + GcAsicDevice gc_asic_device; + bool is_initialized; + private: + Result IssueCommandWriteOperation(const void *op_buf, size_t op_buf_size) const; + Result IssueCommandFinishOperation() const; + Result IssueCommandSleep(); + Result IssueCommandUpdateKey() const; + Result StartupGcAsicDevice(); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + + virtual Result ReStartup() override { + AMS_ABORT("Can't ReStartup GcAsic\n"); + } + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit GcAsicDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc), is_initialized(false) { + /* ... */ + } + + void PutGcAsicToSleep(); + Result AwakenGcAsic(); + Result WriteGcAsicOperation(const void *op_buf, size_t op_buf_size); + Result FinishGcAsicOperation(); + Result AbortGcAsicOperation(); + Result SleepGcAsic(); + Result UpdateGcAsicKey(); + + void SignalGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->gc_asic_device.SignalRemovedEvent(); + #else + AMS_ABORT("SignalGcRemovedEvent called without event support\n"); + #endif + } + + void ClearGcRemovedEvent() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->gc_asic_device.ClearRemovedEvent(); + #else + AMS_ABORT("ClearGcRemovedEvent called without event support\n"); + #endif + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp new file mode 100644 index 000000000..94e85bd59 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_device_accessor.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" + +namespace ams::sdmmc::impl { + + class IDeviceAccessor { + public: + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual Result Activate() = 0; + virtual void Deactivate() = 0; + + virtual Result ReadWrite(u32 sector_index, u32 num_sectors, void *buffer, size_t buffer_size, bool is_read) = 0; + virtual Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width) = 0; + + virtual Result GetSpeedMode(SpeedMode *out) const = 0; + virtual Result GetMemoryCapacity(u32 *out_sectors) const = 0; + virtual Result GetDeviceStatus(u32 *out) const = 0; + virtual Result GetOcr(u32 *out) const = 0; + virtual Result GetRca(u16 *out) const = 0; + virtual Result GetCid(void *out, size_t size) const = 0; + virtual Result GetCsd(void *out, size_t size) const = 0; + + virtual void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) = 0; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp new file mode 100644 index 000000000..3ac285761 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_i_host_controller.hpp @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#if defined(AMS_SDMMC_USE_OS_EVENTS) +#include +#endif + +namespace ams::sdmmc::impl { + + enum ResponseType { + ResponseType_R0 = 0, + ResponseType_R1 = 1, + ResponseType_R2 = 2, + ResponseType_R3 = 3, + ResponseType_R6 = 4, + ResponseType_R7 = 5, + }; + + enum TransferDirection { + TransferDirection_ReadFromDevice = 0, + TransferDirection_WriteToDevice = 1, + }; + + enum CommandIndex { + /* Generic commands. */ + CommandIndex_GoIdleState = 0, + CommandIndex_SendOpCond = 1, + CommandIndex_AllSendCid = 2, + CommandIndex_SendRelativeAddr = 3, + CommandIndex_SetRelativeAddr = 3, + CommandIndex_SetDsr = 4, + + CommandIndex_Switch = 6, + CommandIndex_SelectCard = 7, + CommandIndex_DeselectCard = 7, + CommandIndex_SendIfCond = 8, + CommandIndex_SendExtCsd = 8, + CommandIndex_SendCsd = 9, + CommandIndex_SendCid = 10, + CommandIndex_VoltageSwitch = 11, + CommandIndex_StopTransmission = 12, + CommandIndex_SendStatus = 13, + CommandIndex_SendTaskStatus = 13, + + CommandIndex_GoInactiveState = 15, + CommandIndex_SetBlockLen = 16, + CommandIndex_ReadSingleBlock = 17, + CommandIndex_ReadMultipleBlock = 18, + CommandIndex_SendTuningBlock = 19, + CommandIndex_SpeedClassControl = 20, + + CommandIndex_AddressExtension = 22, + CommandIndex_SetBlockCount = 23, + CommandIndex_WriteBlock = 24, + CommandIndex_WriteMultipleBlock = 25, + + CommandIndex_ProgramCsd = 27, + CommandIndex_SetWriteProt = 28, + CommandIndex_ClearWriteProt = 29, + CommandIndex_SendWriteProt = 30, + + CommandIndex_EraseWriteBlockStart = 32, + CommandIndex_EraseWriteBlockEnd = 33, + + CommandIndex_EraseGroupStart = 35, + CommandIndex_EraseGroupEnd = 36, + + CommandIndex_Erase = 38, + + CommandIndex_LockUnlock = 42, + + CommandIndex_AppCmd = 55, + CommandIndex_GenCmd = 56, + + /* Nintendo specific vendor commands for lotus3. */ + CommandIndex_GcAsicWriteOperation = 60, + CommandIndex_GcAsicFinishOperation = 61, + CommandIndex_GcAsicSleep = 62, + CommandIndex_GcAsicUpdateKey = 63, + }; + + struct Command { + u32 command_index; + u32 command_argument; + ResponseType response_type; + bool is_busy; + + constexpr Command(u32 ci, u32 ca, ResponseType r, bool b) : command_index(ci), command_argument(ca), response_type(r), is_busy(b) { /* ... */ } + }; + + struct TransferData { + void *buffer; + size_t block_size; + u32 num_blocks; + TransferDirection transfer_direction; + bool is_multi_block_transfer; + bool is_stop_transmission_command_enabled; + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd, bool mb, bool st) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(mb), is_stop_transmission_command_enabled(st) + { + if (this->num_blocks > 1) { + AMS_ABORT_UNLESS(this->is_multi_block_transfer); + } + } + + constexpr TransferData(void *b, size_t bs, u32 nb, TransferDirection xd) + : buffer(b), block_size(bs), num_blocks(nb), transfer_direction(xd), is_multi_block_transfer(false), is_stop_transmission_command_enabled(false) + { + AMS_ABORT_UNLESS(this->num_blocks == 1); + } + }; + + class IHostController { + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(ams::os::EventType *event) = 0; + #endif + + virtual void Initialize() = 0; + virtual void Finalize() = 0; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) = 0; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) = 0; + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) = 0; + virtual void Shutdown(); + virtual void PutToSleep(); + virtual Result Awaken(); + + virtual Result SwitchToSdr12(); + + virtual bool IsSupportedBusPower(BusPower bus_power) const = 0; + virtual BusPower GetBusPower() const = 0; + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const = 0; + virtual void SetBusWidth(BusWidth bus_width) = 0; + virtual BusWidth GetBusWidth() const = 0; + + virtual Result SetSpeedMode(SpeedMode speed_mode) = 0; + virtual SpeedMode GetSpeedMode() const = 0; + + virtual u32 GetDeviceClockFrequencyKHz() const = 0; + + virtual void SetPowerSaving(bool en) = 0; + virtual bool IsPowerSavingEnable() const = 0; + + virtual void EnableDeviceClock() = 0; + virtual void DisableDeviceClock() = 0; + virtual bool IsDeviceClockEnable() const = 0; + + virtual u32 GetMaxTransferNumBlocks() const = 0; + + virtual void ChangeCheckTransferInterval(u32 ms) = 0; + virtual void SetDefaultCheckTransferInterval() = 0; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) = 0; + virtual Result IssueStopTransmissionCommand(u32 *out_response) = 0; + + ALWAYS_INLINE Result IssueCommand(const Command *command, TransferData *xfer_data) { + return this->IssueCommand(command, xfer_data, nullptr); + } + + ALWAYS_INLINE Result IssueCommand(const Command *command) { + return this->IssueCommand(command, nullptr, nullptr); + } + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const = 0; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const = 0; + + virtual bool IsSupportedTuning() const = 0; + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) = 0; + virtual void SaveTuningStatusForHs400() = 0; + virtual Result GetInternalStatus() const = 0; + }; + +} 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..56f15bcd7 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_io_impl.board.nintendo_nx.cpp @@ -0,0 +1,1054 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_timer.hpp" +#include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" +#include "sdmmc_io_impl.board.nintendo_nx.hpp" + +namespace ams::sdmmc::impl { + + /* Lovingly taken from libexosphere. */ + namespace i2c_impl { + + enum Port { + Port_1 = 0, + /* TODO: Support other ports? */ + Port_5 = 4, + + Port_Count = 5, + }; + + constexpr inline const dd::PhysicalAddress I2c5RegistersAddress = UINT64_C(0x7000D000); + constexpr inline const size_t I2c5RegistersSize = 4_KB; + + #define I2C_I2C_CNFG (0x000) + #define I2C_I2C_CMD_ADDR0 (0x004) + #define I2C_I2C_CMD_DATA1 (0x00C) + #define I2C_I2C_STATUS (0x01C) + #define I2C_INTERRUPT_STATUS_REGISTER (0x068) + #define I2C_CLK_DIVISOR_REGISTER (0x06C) + #define I2C_BUS_CLEAR_CONFIG (0x084) + #define I2C_BUS_CLEAR_STATUS (0x088) + #define I2C_CONFIG_LOAD (0x08C) + + #define I2C_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (I2C, NAME) + #define I2C_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (I2C, NAME, VALUE) + #define I2C_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (I2C, NAME, ENUM) + #define I2C_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(I2C, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_I2C_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (I2C, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_I2C_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_I2C_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_I2C_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_I2C_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (I2C, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + /* I2C_CNFG */ + DEFINE_I2C_REG(I2C_CNFG_LENGTH, 1, 3); + DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_CMD1, 6, WRITE, READ); + DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_SEND, 9, NOP, GO); + DEFINE_I2C_REG_BIT_ENUM(I2C_CNFG_NEW_MASTER_FSM, 11, DISABLE, ENABLE); + DEFINE_I2C_REG_THREE_BIT_ENUM(I2C_CNFG_DEBOUNCE_CNT, 12, NO_DEBOUNCE, DEBOUNCE_2T, DEBOUNCE_4T, DEBOUNCE_6T, DEBOUNCE_8T, DEBOUNCE_10T, DEBOUNCE_12T, DEBOUNCE_14T); + + /* I2C_CMD_ADDR0 */ + DEFINE_I2C_REG_BIT_ENUM(I2C_CMD_ADDR0_7BIT_RW, 0, WRITE, READ); + DEFINE_I2C_REG(I2C_CMD_ADDR0_7BIT_ADDR, 1, 7); + + /* I2C_STATUS */ + DEFINE_I2C_REG_FOUR_BIT_ENUM(I2C_STATUS_CMD1_STAT, 0, SL1_XFER_SUCCESSFUL, SL1_NOACK_FOR_BYTE1, SL1_NOACK_FOR_BYTE2, SL1_NOACK_FOR_BYTE3, SL1_NOACK_FOR_BYTE4, SL1_NOACK_FOR_BYTE5, SL1_NOACK_FOR_BYTE6, SL1_NOACK_FOR_BYTE7, SL1_NOACK_FOR_BYTE8, SL1_NOACK_FOR_BYTE9, SL1_NOACK_FOR_BYTE10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); + DEFINE_I2C_REG_FOUR_BIT_ENUM(I2C_STATUS_CMD2_STAT, 4, SL2_XFER_SUCCESSFUL, SL2_NOACK_FOR_BYTE1, SL2_NOACK_FOR_BYTE2, SL2_NOACK_FOR_BYTE3, SL2_NOACK_FOR_BYTE4, SL2_NOACK_FOR_BYTE5, SL2_NOACK_FOR_BYTE6, SL2_NOACK_FOR_BYTE7, SL2_NOACK_FOR_BYTE8, SL2_NOACK_FOR_BYTE9, SL2_NOACK_FOR_BYTE10, RESERVED11, RESERVED12, RESERVED13, RESERVED14, RESERVED15); + DEFINE_I2C_REG_BIT_ENUM(I2C_STATUS_BUSY, 8, NOT_BUSY, BUSY); + + /* INTERRUPT_STATUS_REGISTER */ + DEFINE_I2C_REG_BIT_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, 11, UNSET, SET); + + /* CLK_DIVISOR_REGISTER */ + DEFINE_I2C_REG(CLK_DIVISOR_REGISTER_HSMODE, 0, 16); + DEFINE_I2C_REG(CLK_DIVISOR_REGISTER_STD_FAST_MODE, 16, 16); + + /* BUS_CLEAR_CONFIG */ + DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_ENABLE, 0, DISABLE, ENABLE); + DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_TERMINATE, 1, THRESHOLD, IMMEDIATE); + DEFINE_I2C_REG_BIT_ENUM(BUS_CLEAR_CONFIG_BC_STOP_COND, 2, NO_STOP, STOP); + DEFINE_I2C_REG(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 16, 8); + + /* CONFIG_LOAD */ + DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, 0, DISABLE, ENABLE); + DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_SLV_CONFIG_LOAD, 1, DISABLE, ENABLE); + DEFINE_I2C_REG_BIT_ENUM(CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, 2, DISABLE, ENABLE); + DEFINE_I2C_REG(CONFIG_LOAD_RESERVED_BIT_5, 5, 1); + + namespace { + + constexpr inline size_t MaxTransferSize = sizeof(u32); + + constinit std::array g_register_addresses = [] { + std::array arr = {}; + return arr; + }(); + + void LoadConfig(uintptr_t address) { + /* Configure for TIMEOUT and MSTR config load. */ + /* NOTE: Nintendo writes value 1 to reserved bit 5 here. This bit is documented as having no meaning. */ + /* We will reproduce the write just in case it is undocumented. */ + reg::Write(address + I2C_CONFIG_LOAD, I2C_REG_BITS_VALUE(CONFIG_LOAD_RESERVED_BIT_5, 1), + I2C_REG_BITS_ENUM (CONFIG_LOAD_TIMEOUT_CONFIG_LOAD, ENABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_SLV_CONFIG_LOAD, DISABLE), + I2C_REG_BITS_ENUM (CONFIG_LOAD_MSTR_CONFIG_LOAD, ENABLE)); + + /* Wait up to 20 microseconds for the master config to be loaded. */ + for (int i = 0; i < 20; ++i) { + if (reg::HasValue(address + I2C_CONFIG_LOAD, I2C_REG_BITS_ENUM(CONFIG_LOAD_MSTR_CONFIG_LOAD, DISABLE))) { + return; + } + util::WaitMicroSeconds(1); + } + } + + void ClearBus(uintptr_t address) { + /* Configure the bus clear register. */ + reg::Write(address + I2C_BUS_CLEAR_CONFIG, I2C_REG_BITS_VALUE(BUS_CLEAR_CONFIG_BC_SCLK_THRESHOLD, 9), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_STOP_COND, NO_STOP), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_TERMINATE, IMMEDIATE), + I2C_REG_BITS_ENUM (BUS_CLEAR_CONFIG_BC_ENABLE, ENABLE)); + + /* Load the config. */ + LoadConfig(address); + + /* Wait up to 250us (in 25 us increments) until the bus clear is done. */ + for (int i = 0; i < 10; ++i) { + if (reg::HasValue(address + I2C_INTERRUPT_STATUS_REGISTER, I2C_REG_BITS_ENUM(INTERRUPT_STATUS_REGISTER_BUS_CLEAR_DONE, SET))) { + break; + } + + util::WaitMicroSeconds(25); + } + + /* Read the bus clear status. */ + reg::Read(address + I2C_BUS_CLEAR_STATUS); + } + + void InitializePort(uintptr_t address) { + /* Calculate the divisor. */ + constexpr int Divisor = util::DivideUp(19200, 8 * 400); + + /* Set the divisor. */ + reg::Write(address + I2C_CLK_DIVISOR_REGISTER, I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_STD_FAST_MODE, Divisor - 1), + I2C_REG_BITS_VALUE(CLK_DIVISOR_REGISTER_HSMODE, 1)); + + /* Clear the bus. */ + ClearBus(address); + + /* Clear the status. */ + reg::Write(address + I2C_INTERRUPT_STATUS_REGISTER, reg::Read(address + I2C_INTERRUPT_STATUS_REGISTER)); + } + + bool Write(uintptr_t base_address, Port port, int address, const void *src, size_t src_size, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't write too much. */ + u32 data = 0; + if (src_size > MaxTransferSize) { + return false; + } + + /* Copy the data to a transfer word. */ + std::memcpy(std::addressof(data), src, src_size); + + + /* Configure the to write the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, WRITE)); + + /* Configure to write the data. */ + reg::Write(base_address + I2C_I2C_CMD_DATA1, data); + + /* Configure to write the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, WRITE), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, src_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check if the transfer was successful. */ + return reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL)); + } + + bool Read(uintptr_t base_address, Port port, void *dst, size_t dst_size, int address, bool unused) { + AMS_UNUSED(port, unused); + + /* Ensure we don't read too much. */ + if (dst_size > MaxTransferSize) { + return false; + } + + /* Configure the to read the 7-bit address. */ + reg::Write(base_address + I2C_I2C_CMD_ADDR0, I2C_REG_BITS_VALUE(I2C_CMD_ADDR0_7BIT_ADDR, address), + I2C_REG_BITS_ENUM (I2C_CMD_ADDR0_7BIT_RW, READ)); + + /* Configure to read the correct amount of data. */ + reg::Write(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM (I2C_CNFG_DEBOUNCE_CNT, DEBOUNCE_4T), + I2C_REG_BITS_ENUM (I2C_CNFG_NEW_MASTER_FSM, ENABLE), + I2C_REG_BITS_ENUM (I2C_CNFG_CMD1, READ), + I2C_REG_BITS_VALUE(I2C_CNFG_LENGTH, dst_size - 1)); + + /* Load the configuration. */ + LoadConfig(base_address); + + /* Start the command. */ + reg::ReadWrite(base_address + I2C_I2C_CNFG, I2C_REG_BITS_ENUM(I2C_CNFG_SEND, GO)); + + /* Wait for the command to be done. */ + while (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_BUSY, NOT_BUSY))) { /* ... */ } + + /* Check that the transfer was successful. */ + if (!reg::HasValue(base_address + I2C_I2C_STATUS, I2C_REG_BITS_ENUM(I2C_STATUS_CMD1_STAT, SL1_XFER_SUCCESSFUL))) { + return false; + } + + /* Read and copy out the data. */ + u32 data = reg::Read(base_address + I2C_I2C_CMD_DATA1); + std::memcpy(dst, std::addressof(data), dst_size); + return true; + } + + } + + void SetRegisterAddress(Port port, uintptr_t address) { + g_register_addresses[port] = address; + } + + void Initialize(Port port) { + InitializePort(g_register_addresses[port]); + } + + bool Query(void *dst, size_t dst_size, Port port, int address, int r) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Select the register we want to read. */ + bool success = Write(base_address, port, address, std::addressof(r), 1, false); + if (success) { + /* If we successfully selected, read data from the register. */ + success = Read(base_address, port, dst, dst_size, address, true); + } + + return success; + } + + bool Send(Port port, int address, int r, const void *src, size_t src_size) { + const uintptr_t base_address = g_register_addresses[port]; + + /* Create a transfer buffer, make sure we can use it. */ + u8 buffer[MaxTransferSize]; + if (src_size > sizeof(buffer) - 1) { + return false; + } + + /* Copy data into the buffer. */ + buffer[0] = static_cast(r); + std::memcpy(buffer + 1, src, src_size); + + return Write(base_address, port, address, buffer, src_size + 1, false); + } + + } + + namespace max7762x { + + /* The only regulator we care about for SD card power is ldo2. */ + constexpr inline const u8 Max77620PwrI2cAddr = 0x3C; + + constexpr inline const u32 Max77620Ldo2MvStep = 50'000; /* 50 mV (50K uV) steps. */ + constexpr inline const u32 Max77620Ldo2MvMin = 800'000; /* 0.8V min voltage. */ + constexpr inline const u32 Max77620Ldo2MvDefault = 1'800'000; /* 1.8V default voltage. */ + constexpr inline const u32 Max77620Ldo2MvMax = 3'300'000; /* 3.3V max voltage. */ + constexpr inline const u8 Max77620Ldo2VoltAddr = 0x27; + constexpr inline const u8 Max77620Ldo2CfgAddr = 0x28; + constexpr inline const u8 Max77620Ldo2VoltMask = 0x3F; + constexpr inline const u8 Max77620Ldo2EnableMask = 0xC0; + constexpr inline const u8 Max77620Ldo2EnableShift = 0x6; + constexpr inline const u8 Max77620Ldo2StatusMask = 0x00; + + namespace { + + #if defined(AMS_SDMMC_THREAD_SAFE) + constinit os::SdkMutex g_i2c_init_mutex; + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() std::scoped_lock lk(g_i2c_init_mutex) + + #else + + #define AMS_SDMMC_LOCK_I2C_INIT_MUTEX() + + #endif + + constinit bool g_initialized_i2c = false; + + void EnsureI2cInitialized() { + if (AMS_UNLIKELY(!g_initialized_i2c)) { + /* Ensure we have exclusive access to the i2c init status. */ + AMS_SDMMC_LOCK_I2C_INIT_MUTEX(); + + if (AMS_LIKELY(!g_initialized_i2c)) { + i2c_impl::SetRegisterAddress(i2c_impl::Port_5, dd::QueryIoMapping(i2c_impl::I2c5RegistersAddress, i2c_impl::I2c5RegistersSize)); + i2c_impl::Initialize(i2c_impl::Port_5); + g_initialized_i2c = true; + } + } + } + + } + + bool SetVoltageEnabled(bool en) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set or clear the enable mask. */ + val &= ~Max77620Ldo2EnableMask; + if (en) { + val |= ((3 << Max77620Ldo2EnableShift) & Max77620Ldo2EnableMask); + } + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now enabled/disabled. */ + return true; + } + + bool SetVoltageValue(u32 micro_volts) { + /* Ensure that we can use i2c to communicate with the max7762x regulator. */ + EnsureI2cInitialized(); + + /* Check that the value is within range. */ + if (micro_volts < Max77620Ldo2MvMin || Max77620Ldo2MvMax < micro_volts) { + return false; + } + + /* Determine the mult. */ + const u32 mult = util::DivideUp(micro_volts - Max77620Ldo2MvMin, Max77620Ldo2MvStep); + + /* Read the current value. */ + u8 val; + if (!i2c_impl::Query(std::addressof(val), sizeof(val), i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr)) { + return false; + } + + /* Set the new voltage. */ + val &= ~Max77620Ldo2VoltMask; + val |= (mult & Max77620Ldo2VoltMask); + + /* Write the updated value. */ + if (!i2c_impl::Send(i2c_impl::Port_5, Max77620PwrI2cAddr, Max77620Ldo2VoltAddr, std::addressof(val), sizeof(val))) { + return false; + } + + /* Wait 1ms for change to take. */ + WaitMicroSeconds(1); + + /* Voltage is now set. */ + return true; + } + + } + + Result SetSdCardVoltageEnabled(bool en) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageEnabled(en)); + + return ResultSuccess(); + } + + Result SetSdCardVoltageValue(u32 micro_volts) { + /* TODO: A way for this to be non-fatal? */ + AMS_ABORT_UNLESS(max7762x::SetVoltageValue(micro_volts)); + + return ResultSuccess(); + } + + namespace gpio_impl { + + namespace { + + constexpr inline dd::PhysicalAddress GpioRegistersPhysicalAddress = 0x6000d000; + constexpr inline size_t GpioRegistersSize = 4_KB; + + enum GpioPadPort { + GpioPadPort_A = 0, + GpioPadPort_B = 1, + GpioPadPort_C = 2, + GpioPadPort_D = 3, + GpioPadPort_E = 4, + GpioPadPort_F = 5, + GpioPadPort_G = 6, + GpioPadPort_H = 7, + GpioPadPort_I = 8, + GpioPadPort_J = 9, + GpioPadPort_K = 10, + GpioPadPort_L = 11, + GpioPadPort_M = 12, + GpioPadPort_N = 13, + GpioPadPort_O = 14, + GpioPadPort_P = 15, + GpioPadPort_Q = 16, + GpioPadPort_R = 17, + GpioPadPort_S = 18, + GpioPadPort_T = 19, + GpioPadPort_U = 20, + GpioPadPort_V = 21, + GpioPadPort_W = 22, + GpioPadPort_X = 23, + GpioPadPort_Y = 24, + GpioPadPort_Z = 25, + GpioPadPort_AA = 26, + GpioPadPort_BB = 27, + GpioPadPort_CC = 28, + GpioPadPort_DD = 29, + GpioPadPort_EE = 30, + GpioPadPort_FF = 31, + }; + + consteval unsigned int GetInternalGpioPadNumber(GpioPadPort port, unsigned int which) { + AMS_ASSUME(which < 8); + + return (static_cast(port) * 8) + which; + } + + enum InternalGpioPadNumber { + InternalGpioPadNumber_E4 = GetInternalGpioPadNumber(GpioPadPort_E, 4), + InternalGpioPadNumber_M0 = GetInternalGpioPadNumber(GpioPadPort_M, 0), + }; + + constexpr int ConvertInternalGpioPadNumberToController(InternalGpioPadNumber number) { + return (number >> 5); + } + + constexpr int ConvertInternalGpioPadNumberToPort(InternalGpioPadNumber number) { + return (number >> 3); + } + + constexpr int ConvertInternalGpioPadNumberToBitIndex(InternalGpioPadNumber number) { + return (number & 7); + } + + constexpr int ConvertPortNumberToOffset(int port_number) { + return (port_number & 3); + } + + struct PadNameToInternalPadNumberEntry { + GpioPadName pad_name; + InternalGpioPadNumber internal_number; + }; + + constexpr inline const PadNameToInternalPadNumberEntry PadNameToInternalPadNumberTable[] = { + { GpioPadName_PowSdEn, InternalGpioPadNumber_E4 }, + }; + + constexpr InternalGpioPadNumber ConvertPadNameToInternalPadNumber(GpioPadName pad) { + const PadNameToInternalPadNumberEntry *target = nullptr; + for (const auto &entry : PadNameToInternalPadNumberTable) { + if (entry.pad_name == pad) { + target = std::addressof(entry); + break; + } + } + + AMS_ABORT_UNLESS(target != nullptr); + return target->internal_number; + } + + enum GpioRegisterType { + GpioRegisterType_GPIO_CNF = 0, + GpioRegisterType_GPIO_OE = 1, + GpioRegisterType_GPIO_OUT = 2, + GpioRegisterType_GPIO_IN = 3, + GpioRegisterType_GPIO_INT_STA = 4, + GpioRegisterType_GPIO_INT_ENB = 5, + GpioRegisterType_GPIO_INT_LVL = 6, + GpioRegisterType_GPIO_INT_CLR = 7, + GpioRegisterType_GPIO_DB_CTRL = 8, + GpioRegisterType_GPIO_DB_CNT = 9, + }; + + constexpr inline uintptr_t MaskedWriteAddressOffset = 0x80; + constexpr inline int MaskedWriteBitOffset = 8; + + constexpr uintptr_t GetGpioRegisterAddress(uintptr_t gpio_address, GpioRegisterType reg_type, InternalGpioPadNumber pad_number) { + const auto controller = ConvertInternalGpioPadNumberToController(pad_number); + const auto port = ConvertInternalGpioPadNumberToPort(pad_number); + const auto offset = ConvertPortNumberToOffset(port); + + switch (reg_type) { + default: + return gpio_address + (0x100 * controller) + (0x10 * reg_type) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CTRL: + return gpio_address + (0x100 * controller) + (0x10 * GpioRegisterType_GPIO_IN) + (0x4 * offset); + case GpioRegisterType_GPIO_DB_CNT: + return gpio_address + (0x100 * controller) + MaskedWriteAddressOffset + (0x10 * GpioRegisterType_GPIO_INT_CLR) + (0x4 * offset); + } + } + + void SetMaskedBit(uintptr_t pad_address, int index, int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (1u << (MaskedWriteBitOffset + index)) | (static_cast(value) << index)); + } + + void SetMaskedBits(uintptr_t pad_address, unsigned int mask, unsigned int value) { + const uintptr_t mask_address = pad_address + MaskedWriteAddressOffset; + + reg::Write(mask_address, (mask << MaskedWriteBitOffset) | (value)); + } + + } + + void OpenSession(GpioPadName pad) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad as GPIO by setting the appropriate bit in CNF. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_CNF, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, 1); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void CloseSession(GpioPadName pad) { + /* Nothing needs to be done here, as the only thing official code does is unbind the interrupt event. */ + AMS_UNUSED(pad); + } + + void SetDirection(GpioPadName pad, Direction direction) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad direction modifying the appropriate bit in OE. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OE, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, direction); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + void SetValue(GpioPadName pad, GpioValue value) { + /* Convert the pad to an internal number. */ + const auto pad_number = ConvertPadNameToInternalPadNumber(pad); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(GpioRegistersPhysicalAddress, GpioRegistersSize); + + /* Configure the pad value modifying the appropriate bit in OUT. */ + const uintptr_t pad_address = GetGpioRegisterAddress(gpio_address, GpioRegisterType_GPIO_OUT, pad_number); + const uintptr_t pad_index = ConvertInternalGpioPadNumberToBitIndex(pad_number); + SetMaskedBit(pad_address, pad_index, value); + + /* Read the pad address to make sure our configuration takes. */ + reg::Read(pad_address); + } + + } + + namespace pinmux_impl { + + namespace { + + constexpr auto Sdmmc1ClkCmdDat03PadNumber = gpio_impl::InternalGpioPadNumber_M0; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadMask = 0x3F; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfGpio = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadCnfSfio = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutHigh = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOutLow = 0x00; + + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeOutput = 0x3F; + constexpr unsigned int Sdmmc1ClkCmdDat03PadOeInput = 0x00; + + struct PinmuxDefinition { + u32 reg_offset; + u32 mask_val; + u32 pm_val; + }; + + /* NOTE: We only use the SDMMC1 pins, which are conveniently the first few... */ + constexpr const PinmuxDefinition PinmuxDefinitionMap[] = { + {0x00003000, 0x72FF, 0x01}, /* Sdmmc1Clk */ + {0x00003004, 0x72FF, 0x02}, /* Sdmmc1Cmd */ + {0x00003008, 0x72FF, 0x02}, /* Sdmmc1Dat3 */ + {0x0000300C, 0x72FF, 0x02}, /* Sdmmc1Dat2 */ + {0x00003010, 0x72FF, 0x02}, /* Sdmmc1Dat1 */ + {0x00003014, 0x72FF, 0x01}, /* Sdmmc1Dat0 */ + }; + + enum PinmuxPadIndex { + PinmuxPadIndex_Sdmmc1Clk = 0, + PinmuxPadIndex_Sdmmc1Cmd = 1, + PinmuxPadIndex_Sdmmc1Dat3 = 2, + PinmuxPadIndex_Sdmmc1Dat2 = 3, + PinmuxPadIndex_Sdmmc1Dat1 = 4, + PinmuxPadIndex_Sdmmc1Dat0 = 5, + + PinmuxPadIndex_Count, + }; + + static_assert(util::size(PinmuxDefinitionMap) == PinmuxPadIndex_Count); + + consteval const PinmuxDefinition GetDefinition(PinmuxPadIndex pad_index) { + AMS_ABORT_UNLESS(pad_index < PinmuxPadIndex_Count); + + return PinmuxDefinitionMap[pad_index]; + } + + template + ALWAYS_INLINE u32 UpdatePinmuxPad(uintptr_t pinmux_base_vaddr) { + constexpr const PinmuxDefinition Definition = GetDefinition(PadIndex); + + /* Fetch this PINMUX's register offset */ + constexpr u32 PinmuxRegOffset = Definition.reg_offset; + + /* Fetch this PINMUX's mask value */ + constexpr u32 PinmuxMaskVal = Definition.mask_val; + + /* Get current register ptr. */ + const uintptr_t pinmux_reg = pinmux_base_vaddr + PinmuxRegOffset; + + /* Read from the PINMUX register */ + u32 pinmux_val = reg::Read(pinmux_reg); + + /* This PINMUX register is locked */ + AMS_ABORT_UNLESS((pinmux_val & 0x80) == 0); + + constexpr u32 PmVal = (PinmuxConfigVal & 0x07); + + /* Adjust PM */ + if constexpr (PinmuxConfigMaskVal & 0x07) { + /* Apply additional changes first */ + if constexpr (PmVal == 0x05) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if ((pinmux_val & 0x0C) != 0x04) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= 0x04; + } + } + + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + } + + /* Translate PM value if necessary */ + constexpr u32 TranslatedPmVal = (PmVal == 0x04 || PmVal == 0x05 || PmVal >= 0x06) ? Definition.pm_val : PmVal; + + /* This pin supports PM change */ + if constexpr (PinmuxMaskVal & 0x03) { + /* Change PM */ + if ((pinmux_val & 0x03) != (TranslatedPmVal & 0x03)) { + pinmux_val &= 0xFFFFFFFC; + pinmux_val |= (TranslatedPmVal & 0x03); + } + } + } + + constexpr u32 PupdConfigVal = (PinmuxConfigVal & 0x18); + + /* Adjust PUPD */ + if constexpr (PinmuxConfigMaskVal & 0x18) { + if constexpr (PupdConfigVal < 0x11) { + /* This pin supports PUPD change */ + if constexpr (PinmuxMaskVal & 0x0C) { + /* Change PUPD */ + if (((pinmux_val >> 0x02) & 0x03) != (PupdConfigVal >> 0x03)) { + pinmux_val &= 0xFFFFFFF3; + pinmux_val |= (PupdConfigVal >> 0x01); + } + } + } + } + + constexpr u32 EodConfigVal = (PinmuxConfigVal & 0x60); + + /* Adjust EOd field */ + if constexpr (PinmuxConfigMaskVal & 0x60) { + if constexpr (EodConfigVal == 0x20) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (!(pinmux_val & 0x10)) { + pinmux_val |= 0x10; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x40) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } else if constexpr (EodConfigVal == 0x60) { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (!(pinmux_val & 0x40)) { + pinmux_val |= 0x40; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (!(pinmux_val & 0x800)) { + pinmux_val |= 0x800; + } + } + } else { + /* This pin supports Tristate change */ + if constexpr (PinmuxMaskVal & 0x10) { + /* Change Tristate */ + if (pinmux_val & 0x10) { + pinmux_val &= 0xFFFFFFEF; + } + } + + /* This pin supports EInput change */ + if constexpr (PinmuxMaskVal & 0x40) { + /* Change EInput */ + if (pinmux_val & 0x40) { + pinmux_val &= 0xFFFFFFBF; + } + } + + /* This pin supports EOd change */ + if constexpr (PinmuxMaskVal & 0x800) { + /* Change EOd */ + if (pinmux_val & 0x800) { + pinmux_val &= 0xFFFFF7FF; + } + } + } + } + + constexpr u32 LockConfigVal = (PinmuxConfigVal & 0x80); + + /* Adjust Lock */ + if constexpr (PinmuxConfigMaskVal & 0x80) { + /* This pin supports Lock change */ + if constexpr (PinmuxMaskVal & 0x80) { + /* Change Lock */ + if ((pinmux_val ^ PinmuxConfigVal) & 0x80) { + pinmux_val &= 0xFFFFFF7F; + pinmux_val |= LockConfigVal; + } + } + } + + constexpr u32 IoResetConfigVal = (((PinmuxConfigVal >> 0x08) & 0x1) << 0x10); + + /* Adjust IoReset */ + if constexpr (PinmuxConfigMaskVal & 0x100) { + /* This pin supports IoReset change */ + if constexpr (PinmuxMaskVal & 0x10000) { + /* Change IoReset */ + if (((pinmux_val >> 0x10) ^ (PinmuxConfigVal >> 0x08)) & 0x01) { + pinmux_val &= 0xFFFEFFFF; + pinmux_val |= IoResetConfigVal; + } + } + } + + constexpr u32 ParkConfigVal = (((PinmuxConfigVal >> 0x0A) & 0x1) << 0x5); + + /* Adjust Park */ + if constexpr (PinmuxConfigMaskVal & 0x400) { + /* This pin supports Park change */ + if constexpr (PinmuxMaskVal & 0x20) { + /* Change Park */ + if (((pinmux_val >> 0x05) ^ (PinmuxConfigVal >> 0x0A)) & 0x01) { + pinmux_val &= 0xFFFFFFDF; + pinmux_val |= ParkConfigVal; + } + } + } + + constexpr u32 ElpdrConfigVal = (((PinmuxConfigVal >> 0x0B) & 0x1) << 0x08); + + /* Adjust ELpdr */ + if constexpr (PinmuxConfigMaskVal & 0x800) { + /* This pin supports ELpdr change */ + if constexpr (PinmuxMaskVal & 0x100) { + /* Change ELpdr */ + if (((pinmux_val >> 0x08) ^ (PinmuxConfigVal >> 0x0B)) & 0x01) { + pinmux_val &= 0xFFFFFEFF; + pinmux_val |= ElpdrConfigVal; + } + } + } + + constexpr u32 EhsmConfigVal = (((PinmuxConfigVal >> 0x0C) & 0x1) << 0x09); + + /* Adjust EHsm */ + if constexpr (PinmuxConfigMaskVal & 0x1000) { + /* This pin supports EHsm change */ + if constexpr (PinmuxMaskVal & 0x200) { + /* Change EHsm */ + if (((pinmux_val >> 0x09) ^ (PinmuxConfigVal >> 0x0C)) & 0x01) { + pinmux_val &= 0xFFFFFDFF; + pinmux_val |= EhsmConfigVal; + } + } + } + + constexpr u32 EIoHvConfigVal = (((PinmuxConfigVal >> 0x09) & 0x1) << 0x0A); + + /* Adjust EIoHv */ + if constexpr (PinmuxConfigMaskVal & 0x200) { + /* This pin supports EIoHv change */ + if constexpr (PinmuxMaskVal & 0x400) { + /* Change EIoHv */ + if (((pinmux_val >> 0x0A) ^ (PinmuxConfigVal >> 0x09)) & 0x01) { + pinmux_val &= 0xFFFFFBFF; + pinmux_val |= EIoHvConfigVal; + } + } + } + + constexpr u32 EschmtConfigVal = (((PinmuxConfigVal >> 0x0D) & 0x1) << 0x0C); + + /* Adjust ESchmt */ + if constexpr (PinmuxConfigMaskVal & 0x2000) { + /* This pin supports ESchmt change */ + if constexpr (PinmuxMaskVal & 0x1000) { + /* Change ESchmt */ + if (((pinmux_val >> 0x0C) ^ (PinmuxConfigVal >> 0x0D)) & 0x01) { + pinmux_val &= 0xFFFFEFFF; + pinmux_val |= EschmtConfigVal; + } + } + } + + constexpr u32 PreempConfigVal = (((PinmuxConfigVal >> 0x10) & 0x1) << 0xF); + + /* Adjust Preemp */ + if constexpr (PinmuxConfigMaskVal & 0x10000) { + /* This pin supports Preemp change */ + if constexpr (PinmuxMaskVal & 0x8000) { + /* Change Preemp */ + if (((pinmux_val >> 0x0F) ^ (PinmuxConfigVal >> 0x10)) & 0x01) { + pinmux_val &= 0xFFFF7FFF; + pinmux_val |= PreempConfigVal; + } + } + } + + constexpr u32 DrvTypeConfigVal = (((PinmuxConfigVal >> 0x0E) & 0x3) << 0xD); + + /* Adjust DrvType */ + if constexpr (PinmuxConfigMaskVal & 0xC000) { + /* This pin supports DrvType change */ + if constexpr (PinmuxMaskVal & 0x6000) { + /* Change DrvType */ + if (((pinmux_val >> 0x0D) ^ (PinmuxConfigVal >> 0x0E)) & 0x03) { + pinmux_val &= 0xFFFF9FFF; + pinmux_val |= DrvTypeConfigVal; + } + } + } + + /* Write to the appropriate PINMUX register */ + reg::Write(pinmux_reg, pinmux_val); + + /* Do a dummy read from the PINMUX register */ + pinmux_val = reg::Read(pinmux_reg); + + return pinmux_val; + } + + } + + void SetPinAssignment(PinAssignment assignment) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + AMS_UNUSED(apb_address); + + /* Set the pin assignment. */ + switch (assignment) { + case PinAssignment_Sdmmc1OutputHigh: + { + /* Clear Sdmmc1Clk pulldown. */ + UpdatePinmuxPad(apb_address); + + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as gpio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfGpio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as high. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutHigh); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as output. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeOutput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + } + break; + case PinAssignment_Sdmmc1ResetState: + { + /* Get the gpio registers address. */ + const uintptr_t gpio_address = dd::QueryIoMapping(gpio_impl::GpioRegistersPhysicalAddress, gpio_impl::GpioRegistersSize); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as sfio. */ + const uintptr_t cnf_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_CNF, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(cnf_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadCnfSfio); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as low. */ + const uintptr_t out_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OUT, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(out_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOutLow); + + /* Configure GPIO M0-5 (SDMMC1 CLK + CMD + DAT0/1/2/3) as input. */ + const uintptr_t oe_address = gpio_impl::GetGpioRegisterAddress(gpio_address, gpio_impl::GpioRegisterType_GPIO_OE, Sdmmc1ClkCmdDat03PadNumber); + gpio_impl::SetMaskedBits(oe_address, Sdmmc1ClkCmdDat03PadMask, Sdmmc1ClkCmdDat03PadOeInput); + + /* Read to be sure that our configuration takes. */ + reg::Read(oe_address); + + /* Set Sdmmc1Clk pulldown. */ + UpdatePinmuxPad(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtEnable: + { + /* Set Schmitt enable for all pins in the group. */ + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + } + break; + case PinAssignment_Sdmmc1SchmtDisable: + { + /* Set Schmitt disable for all pins in the group. */ + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + UpdatePinmuxPad(apb_address); + } + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + } + +} 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 . + */ +#pragma once +#include + +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_mmc_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp new file mode 100644 index 000000000..7468aaba2 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.cpp @@ -0,0 +1,728 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_mmc_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() std::scoped_lock lk(this->mmc_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + + constexpr inline u32 OcrAccessMode_Mask = (3 << 29); + constexpr inline u32 OcrAccessMode_SectorMode = (2 << 29); + + constexpr inline u8 ManufacturerId_Toshiba = 0x11; + + enum DeviceType : u8 { + DeviceType_HighSpeed26MHz = (1u << 0), + DeviceType_HighSpeed52MHz = (1u << 1), + DeviceType_HighSpeedDdr52MHz1_8VOr3_0V = (1u << 2), + DeviceType_HighSpeedDdr52MHz1_2V = (1u << 3), + DeviceType_Hs200Sdr200MHz1_8V = (1u << 4), + DeviceType_Hs200Sdr200MHz1_2V = (1u << 5), + DeviceType_Hs400Sdr200MHz1_8V = (1u << 6), + DeviceType_Hs400Sdr200MHz1_2V = (1u << 7), + }; + + constexpr bool IsToshibaMmc(const u8 *cid) { + /* Check whether the CID's manufacturer id field is Toshiba. */ + AMS_ABORT_UNLESS(cid != nullptr); + return cid[14] == ManufacturerId_Toshiba; + } + + constexpr bool IsLessThanSpecification4(const u8 *csd) { + const u8 spec_vers = ((csd[14] >> 2) & 0xF); + return spec_vers < 4; + } + + constexpr bool IsBkopAutoEnable(const u8 *ext_csd) { + /* Check the AUTO_EN bit of BKOPS_EN. */ + return (ext_csd[163] & (1u << 1)) != 0; + } + + constexpr u8 GetDeviceType(const u8 *ext_csd) { + /* Get the DEVICE_TYPE register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[196]; + } + + constexpr bool IsSupportedHs400(u8 device_type) { + return (device_type & DeviceType_Hs400Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHs200(u8 device_type) { + return (device_type & DeviceType_Hs200Sdr200MHz1_8V) != 0; + } + + constexpr bool IsSupportedHighSpeed(u8 device_type) { + return (device_type & DeviceType_HighSpeed52MHz) != 0; + } + + constexpr u32 GetMemoryCapacityFromExtCsd(const u32 *ext_csd) { + /* Get the SEC_COUNT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[212 / sizeof(u32)]; + } + + constexpr u32 GetBootPartitionMemoryCapacityFromExtCsd(const u8 *ext_csd) { + /* Get the BOOT_SIZE_MULT register. */ + AMS_ABORT_UNLESS(ext_csd != nullptr); + return ext_csd[226] * (128_KB / SectorSize); + } + + constexpr Result GetCurrentSpeedModeFromExtCsd(SpeedMode *out, const u8 *ext_csd) { + /* Get the HS_TIMING register. */ + AMS_ABORT_UNLESS(out != nullptr); + AMS_ABORT_UNLESS(ext_csd != nullptr); + + switch (ext_csd[185] & 0xF) { + case 0: *out = SpeedMode_MmcLegacySpeed; break; + case 1: *out = SpeedMode_MmcHighSpeed; break; + case 2: *out = SpeedMode_MmcHs200; break; + case 3: *out = SpeedMode_MmcHs400; break; + default: return sdmmc::ResultUnexpectedMmcExtendedCsdValue(); + } + + return ResultSuccess(); + } + + } + + void MmcDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrAccessMode_Mask) == OcrAccessMode_SectorMode); + } + + Result MmcDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const { + /* Get the command argument. */ + u32 arg = OcrAccessMode_SectorMode; + switch (bus_power) { + case BusPower_1_8V: arg |= 0x000080; break; + case BusPower_3_3V: arg |= 0x03F800; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(CommandIndex_SendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(*out_ocr), CommandResponseType); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSetRelativeAddr() const { + /* Get rca. */ + const u32 rca = this->mmc_device.GetRca(); + AMS_ABORT_UNLESS(rca > 0); + + /* Issue comamnd. */ + const u32 arg = rca << 16; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_SetRelativeAddr, arg, false, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSwitch(CommandSwitch cs) const { + /* Get the command argument. */ + const u32 arg = GetCommandSwitchArgument(cs); + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Switch, arg, true, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandSendExtCsd(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= MmcExtendedCsdSize); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_SendExtCsd, 0, CommandResponseType, false); + TransferData xfer_data(dst, MmcExtendedCsdSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->mmc_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupStart(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupStart, arg, false, DeviceState_Unknown)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandEraseGroupEnd(u32 sector_index) const { + /* Get the command argument. */ + const u32 arg = this->mmc_device.IsHighCapacity() ? sector_index : sector_index * SectorSize; + + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_EraseGroupEnd, arg, false, DeviceState_Tran)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::IssueCommandErase() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_Erase, 0, false, DeviceState_Tran)); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::CancelToshibaMmcModel() { + /* Special erase sequence done by Nintendo on Toshiba MMCs. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsProductionStateAwarenessEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_ClearBitsAutoModeEnable)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteProductionStateAwarenessNormal)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToReadyState(BusPower bus_power) { + /* Be prepared to wait up to 1.5 seconds to change state. */ + ManualTimer timer(1500); + while (true) { + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), bus_power)); + if ((ocr & OcrCardPowerUpStatus) != 0) { + this->mmc_device.SetOcrAndHighCapacity(ocr); + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result MmcDeviceAccessor::ExtendBusWidth(BusWidth max_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* Determine what bus width to switch to. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + BusWidth target_bw = BusWidth_1Bit; + CommandSwitch cs; + if (max_bw == BusWidth_8Bit && hc->IsSupportedBusWidth(BusWidth_8Bit)) { + target_bw = BusWidth_8Bit; + cs = CommandSwitch_WriteBusWidth8Bit; + } else if ((max_bw == BusWidth_8Bit || max_bw == BusWidth_4Bit) && hc->IsSupportedBusWidth(BusWidth_4Bit)) { + target_bw = BusWidth_4Bit; + cs = CommandSwitch_WriteBusWidth4Bit; + } else { + /* Target bus width is 1bit. */ + return ResultSuccess(); + } + + /* Set the bus width. */ + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + hc->SetBusWidth(target_bw); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::EnableBkopsAuto() { + /* Issue the command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_SetBitsBkopsEnAutoEn)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHighSpeed(bool check_before) { + /* Issue high speed command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHighSpeed)); + + /* If we should check status before setting mode, do so. */ + if (check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + /* Set the host controller to high speed. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_MmcHighSpeed)); + + /* If we should check status after setting mode, do so. */ + if (!check_before) { + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHs200() { + /* Issue Hs200 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs200)); + + /* Set the host controller to Hs200. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs200)); + + /* Perform tuning using command index 21. */ + R_TRY(hc->Tuning(SpeedMode_MmcHs200, 21)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ChangeToHs400() { + /* Change first to Hs200. */ + R_TRY(this->ChangeToHs200()); + + /* Save tuning status. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + hc->SaveTuningStatusForHs400(); + + /* Change to high speed. */ + R_TRY(this->ChangeToHighSpeed(false)); + + /* Issue Hs400 command. */ + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteBusWidth8BitDdr)); + R_TRY(this->IssueCommandSwitch(CommandSwitch_WriteHsTimingHs400)); + + /* Set the host controller to Hs400. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcHs400)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::ExtendBusSpeed(u8 device_type, SpeedMode max_sm) { + /* We want to switch to the highest speed we can. */ + /* Check Hs400/Hs200 first. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + if (hc->IsSupportedTuning() && hc->GetBusPower() == BusPower_1_8V) { + if (hc->GetBusWidth() == BusWidth_8Bit && IsSupportedHs400(device_type) && max_sm == SpeedMode_MmcHs400) { + return this->ChangeToHs400(); + } else if ((hc->GetBusWidth() == BusWidth_8Bit || hc->GetBusWidth() == BusWidth_4Bit) && IsSupportedHs200(device_type) && (max_sm == SpeedMode_MmcHs400 || max_sm == SpeedMode_MmcHs200)) { + return this->ChangeToHs200(); + } + } + + /* Check if we can switch to high speed. */ + if (IsSupportedHighSpeed(device_type)) { + return this->ChangeToHighSpeed(true); + } + + /* We can't, so stay at normal speeds. */ + return ResultSuccess(); + } + + Result MmcDeviceAccessor::StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up at an appropriate bus power. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + const BusPower bp = hc->IsSupportedBusPower(BusPower_1_8V) ? BusPower_1_8V : BusPower_3_3V; + R_TRY(hc->Startup(bp, BusWidth_1Bit, SpeedMode_MmcIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + this->current_partition = MmcPartition_UserData; + + /* Go to ready state. */ + R_TRY(this->ChangeToReadyState(bp)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + this->mmc_device.SetCid(wb, wb_size); + const bool is_toshiba = IsToshibaMmc(static_cast(wb)); + + /* Issue set relative addr. */ + R_TRY(this->IssueCommandSetRelativeAddr()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + this->mmc_device.SetCsd(wb, wb_size); + const bool spec_under_4 = IsLessThanSpecification4(static_cast(wb)); + + /* Set the speed mode to legacy. */ + R_TRY(hc->SetSpeedMode(SpeedMode_MmcLegacySpeed)); + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* If the device SPEC_VERS is less than 4, extended csd/switch aren't supported. */ + if (spec_under_4) { + R_TRY(this->mmc_device.SetLegacyMemoryCapacity()); + + this->mmc_device.SetActive(); + return ResultSuccess(); + } + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw)); + + /* Get the extended csd. */ + R_TRY(this->IssueCommandSendExtCsd(wb, wb_size)); + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(wb), alignof(u32))); + this->mmc_device.SetMemoryCapacity(GetMemoryCapacityFromExtCsd(static_cast(wb))); + + /* If the mmc is manufactured by toshiba, try to enable bkops auto. */ + if (is_toshiba && !IsBkopAutoEnable(static_cast(wb))) { + /* NOTE: Nintendo does not check the result of this. */ + this->EnableBkopsAuto(); + } + + /* Extend the bus speed to as fast as we can. */ + const u8 device_type = GetDeviceType(static_cast(wb)); + R_TRY(this->ExtendBusSpeed(device_type, max_sm)); + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_MMC_HS400) + { BusWidth_8Bit, SpeedMode_MmcHs400 }, + #else + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + #endif + { BusWidth_8Bit, SpeedMode_MmcHighSpeed }, + { BusWidth_1Bit, SpeedMode_MmcHighSpeed }, + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + this->max_bus_width = params.bus_width; + this->max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + return ResultSuccess(); + } + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + BaseDeviceAccessor::PushErrorTimeStamp(); + + return result; + } + + Result MmcDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Get the sector index alignment. */ + u32 sector_index_alignment = 0; + if (!is_read) { + constexpr u32 MmcWriteSectorAlignment = 16_KB / SectorSize; + sector_index_alignment = MmcWriteSectorAlignment; + AMS_ABORT_UNLESS(util::IsAligned(sector_index, MmcWriteSectorAlignment)); + } + + /* Do the read/write. */ + return BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, sector_index_alignment, buf, buf_size, is_read); + } + + Result MmcDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupMmcDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + return result; + } + + return ResultSuccess(); + } + + void MmcDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our mmc device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->mmc_device)); + + /* Initialize. */ + BaseDeviceAccessor::GetHostController()->Initialize(); + this->is_initialized = true; + } + + void MmcDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + } + + Result MmcDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Get the current speed mode from the ext csd. */ + R_TRY(GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); + R_TRY(GetCurrentSpeedModeFromExtCsd(out_speed_mode, static_cast(this->work_buffer))); + + return ResultSuccess(); + } + + void MmcDeviceAccessor::PutMmcToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->mmc_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + this->mmc_device.PutToSleep(); + + /* If necessary, put the host controller to sleep. */ + if (this->mmc_device.IsActive()) { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void MmcDeviceAccessor::AwakenMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (this->mmc_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + if (this->mmc_device.IsActive()) { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_FAILED(result)) { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + } + } + + /* Wake the device. */ + this->mmc_device.Awaken(); + } + + Result MmcDeviceAccessor::SelectMmcPartition(MmcPartition part) { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Determine the appropriate SWITCH subcommand. */ + CommandSwitch cs; + switch (part) { + case MmcPartition_UserData: cs = CommandSwitch_WritePartitionAccessDefault; break; + case MmcPartition_BootPartition1: cs = CommandSwitch_WritePartitionAccessRwBootPartition1; break; + case MmcPartition_BootPartition2: cs = CommandSwitch_WritePartitionAccessRwBootPartition2; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Change partition. */ + this->current_partition = MmcPartition_Unknown; + { + R_TRY(this->IssueCommandSwitch(cs)); + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + } + this->current_partition = part; + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::EraseMmc() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Get the partition capacity. */ + u32 part_capacity; + switch (this->current_partition) { + case MmcPartition_UserData: + part_capacity = this->mmc_device.GetMemoryCapacity(); + break; + case MmcPartition_BootPartition1: + case MmcPartition_BootPartition2: + R_TRY(this->GetMmcBootPartitionCapacity(std::addressof(part_capacity))); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Begin the erase. */ + R_TRY(this->IssueCommandEraseGroupStart(0)); + R_TRY(this->IssueCommandEraseGroupEnd(part_capacity - 1)); + + /* Issue the erase command, allowing 30 seconds for it to complete. */ + ManualTimer timer(30000); + Result result = this->IssueCommandErase(); + R_TRY_CATCH(result) { + R_CATCH(sdmmc::ResultDataTimeoutError) { /* Data timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultCommandCompleteSoftwareTimeout) { /* Command complete software timeout error is acceptable. */ } + R_CATCH(sdmmc::ResultBusySoftwareTimeout) { /* Busy software timeout error is acceptable. */ } + } R_END_TRY_CATCH; + + /* Wait for the erase to finish. */ + while (true) { + /* Check if we're done. */ + result = BaseDeviceAccessor::IssueCommandSendStatus(); + if (R_SUCCEEDED(result)) { + break; + } + + /* Otherwise, check if we should reject the error. */ + if (!sdmmc::ResultUnexpectedDeviceState::Includes(result)) { + return result; + } + + /* Check if timeout has been exceeded. */ + R_UNLESS(timer.Update(), sdmmc::ResultMmcEraseSoftwareTimeout()); + } + + /* If the partition is user data, check if we need to perform toshiba-specific erase. */ + if (this->current_partition == MmcPartition_UserData) { + u8 cid[DeviceCidSize]; + this->mmc_device.GetCid(cid, sizeof(cid)); + if (IsToshibaMmc(cid)) { + /* NOTE: Nintendo does not check the result of this operation. */ + this->CancelToshibaMmcModel(); + } + } + + return ResultSuccess(); + } + + Result MmcDeviceAccessor::GetMmcBootPartitionCapacity(u32 *out_num_sectors) const { + /* Get the capacity from the extended csd. */ + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + R_TRY(this->GetMmcExtendedCsd(this->work_buffer, this->work_buffer_size)); + + *out_num_sectors = GetBootPartitionMemoryCapacityFromExtCsd(static_cast(this->work_buffer)); + return ResultSuccess(); + } + + Result MmcDeviceAccessor::GetMmcExtendedCsd(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_MMC_DEVICE_MUTEX(); + + /* Check that we can access the device. */ + R_TRY(this->mmc_device.CheckAccessible()); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + this->mmc_device.GetCsd(csd, sizeof(csd)); + + /* Check that the card supports ext csd. */ + R_UNLESS(!IsLessThanSpecification4(csd), sdmmc::ResultMmcNotSupportExtendedCsd()); + + /* Get the ext csd. */ + R_TRY(this->IssueCommandSendExtCsd(dst, dst_size)); + + return ResultSuccess(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp new file mode 100644 index 000000000..97ac86499 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_mmc_device_accessor.hpp @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_base_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + class MmcDevice : public BaseDevice { + private: + static constexpr u16 Rca = 2; + public: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_Mmc; + } + + virtual u16 GetRca() const override { + return Rca; + } + + void SetOcrAndHighCapacity(u32 ocr); + }; + + class MmcDeviceAccessor : public BaseDeviceAccessor { + private: + MmcDevice mmc_device; + void *work_buffer; + size_t work_buffer_size; + BusWidth max_bus_width; + SpeedMode max_speed_mode; + MmcPartition current_partition; + bool is_initialized; + private: + enum CommandSwitch { + CommandSwitch_SetBitsProductionStateAwarenessEnable = 0, + CommandSwitch_ClearBitsAutoModeEnable = 1, + CommandSwitch_WriteProductionStateAwarenessNormal = 2, + CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites = 3, + CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites = 4, + CommandSwitch_SetBitsBkopsEnAutoEn = 5, + CommandSwitch_WriteBusWidth1Bit = 6, + CommandSwitch_WriteBusWidth4Bit = 7, + CommandSwitch_WriteBusWidth8Bit = 8, + CommandSwitch_WriteBusWidth8BitDdr = 9, + CommandSwitch_WriteHsTimingLegacySpeed = 10, + CommandSwitch_WriteHsTimingHighSpeed = 11, + CommandSwitch_WriteHsTimingHs200 = 12, + CommandSwitch_WriteHsTimingHs400 = 13, + CommandSwitch_WritePartitionAccessDefault = 14, + CommandSwitch_WritePartitionAccessRwBootPartition1 = 15, + CommandSwitch_WritePartitionAccessRwBootPartition2 = 16, + }; + + static constexpr ALWAYS_INLINE u32 GetCommandSwitchArgument(CommandSwitch cs) { + switch (cs) { + case CommandSwitch_SetBitsProductionStateAwarenessEnable: return 0x01111000; + case CommandSwitch_ClearBitsAutoModeEnable: return 0x02112000; + case CommandSwitch_WriteProductionStateAwarenessNormal: return 0x03850000; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingWrites: return 0x03850100; + case CommandSwitch_WriteProductionStateAwarenessPreSolderingPostWrites: return 0x03850200; + case CommandSwitch_SetBitsBkopsEnAutoEn: return 0x01A30200; + case CommandSwitch_WriteBusWidth1Bit: return 0x03B70000; + case CommandSwitch_WriteBusWidth4Bit: return 0x03B70100; + case CommandSwitch_WriteBusWidth8Bit: return 0x03B70200; + case CommandSwitch_WriteBusWidth8BitDdr: return 0x03B70600; + case CommandSwitch_WriteHsTimingLegacySpeed: return 0x03B90000; + case CommandSwitch_WriteHsTimingHighSpeed: return 0x03B90100; + case CommandSwitch_WriteHsTimingHs200: return 0x03B90200; + case CommandSwitch_WriteHsTimingHs400: return 0x03B90300; + case CommandSwitch_WritePartitionAccessDefault: return 0x03B30000; + case CommandSwitch_WritePartitionAccessRwBootPartition1: return 0x03B30100; + case CommandSwitch_WritePartitionAccessRwBootPartition2: return 0x03B30200; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + private: + Result IssueCommandSendOpCond(u32 *out_ocr, BusPower bus_power) const; + Result IssueCommandSetRelativeAddr() const; + Result IssueCommandSwitch(CommandSwitch cs) const; + Result IssueCommandSendExtCsd(void *dst, size_t dst_size) const; + Result IssueCommandEraseGroupStart(u32 sector_index) const; + Result IssueCommandEraseGroupEnd(u32 sector_index) const; + Result IssueCommandErase() const; + Result CancelToshibaMmcModel(); + Result ChangeToReadyState(BusPower bus_power); + Result ExtendBusWidth(BusWidth max_bus_width); + Result EnableBkopsAuto(); + Result ChangeToHighSpeed(bool check_before); + Result ChangeToHs200(); + Result ChangeToHs400(); + Result ExtendBusSpeed(u8 device_type, SpeedMode max_sm); + Result StartupMmcDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + explicit MmcDeviceAccessor(IHostController *hc) + : BaseDeviceAccessor(hc), work_buffer(nullptr), work_buffer_size(0), + max_bus_width(BusWidth_8Bit), max_speed_mode(SpeedMode_MmcHs400), current_partition(MmcPartition_Unknown), + is_initialized(false) + { + /* ... */ + } + + void SetMmcWorkBuffer(void *wb, size_t wb_size) { + this->work_buffer = wb; + this->work_buffer_size = wb_size; + } + + void PutMmcToSleep(); + void AwakenMmc(); + Result SelectMmcPartition(MmcPartition part); + Result EraseMmc(); + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors) const; + Result GetMmcExtendedCsd(void *dst, size_t dst_size) const; + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp new file mode 100644 index 000000000..763869d54 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_gc_asic0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortGcAsic0 g_gc_asic0_host_controller; + GcAsicDeviceAccessor g_gc_asic0_device_accessor(std::addressof(g_gc_asic0_host_controller)); + + } + + IHostController *GetHostControllerOfPortGcAsic0() { + return std::addressof(g_gc_asic0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0() { + return std::addressof(g_gc_asic0_device_accessor); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp new file mode 100644 index 000000000..8dc7880a0 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_gc_asic0.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_gc_asic_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortGcAsic0(); + IDeviceAccessor *GetDeviceAccessorOfPortGcAsic0(); + GcAsicDeviceAccessor *GetGcAsicDeviceAccessorOfPortGcAsic0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp new file mode 100644 index 000000000..30cfc869e --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_mmc0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" +#include "sdmmc_base_device_accessor.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortMmc0 g_mmc0_host_controller; + MmcDeviceAccessor g_mmc0_device_accessor(std::addressof(g_mmc0_host_controller)); + + } + + IHostController *GetHostControllerOfPortMmc0() { + return std::addressof(g_mmc0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0() { + return std::addressof(g_mmc0_device_accessor); + } + +} diff --git a/stratosphere/boot/source/boot_registers_gpio.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp similarity index 66% rename from stratosphere/boot/source/boot_registers_gpio.hpp rename to libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp index 321735164..b675bd48c 100644 --- a/stratosphere/boot/source/boot_registers_gpio.hpp +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_mmc0.hpp @@ -14,12 +14,15 @@ * along with this program. If not, see . */ #pragma once -#include +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_mmc_device_accessor.hpp" -static constexpr size_t GPIO_PORT3_CNF_0 = 0x200; -static constexpr size_t GPIO_PORT3_OE_0 = 0x210; -static constexpr size_t GPIO_PORT3_OUT_0 = 0x220; +namespace ams::sdmmc::impl { -static constexpr size_t GPIO_PORT6_CNF_1 = 0x504; -static constexpr size_t GPIO_PORT6_OE_1 = 0x514; -static constexpr size_t GPIO_PORT6_OUT_1 = 0x524; + IHostController *GetHostControllerOfPortMmc0(); + IDeviceAccessor *GetDeviceAccessorOfPortMmc0(); + MmcDeviceAccessor *GetMmcDeviceAccessorOfPortMmc0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp new file mode 100644 index 000000000..5288a5e88 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_port_sd_card0.hpp" +#include "sdmmc_select_sdmmc_controller.hpp" + + +namespace ams::sdmmc::impl { + + namespace { + + SdmmcControllerForPortSdCard0 g_sd_card0_host_controller; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + constexpr inline u32 SdCard0DebounceMilliSeconds = 128; + DeviceDetector g_sd_card0_detector(gpio::DeviceCode_SdCd, gpio::GpioValue_Low, SdCard0DebounceMilliSeconds); + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller), std::addressof(g_sd_card0_detector)); + + #else + + SdCardDeviceAccessor g_sd_card0_device_accessor(std::addressof(g_sd_card0_host_controller)); + + #endif + + + } + + IHostController *GetHostControllerOfPortSdCard0() { + return std::addressof(g_sd_card0_host_controller); + } + + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0() { + return std::addressof(g_sd_card0_device_accessor); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp new file mode 100644 index 000000000..690dbb67c --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_port_sd_card0.hpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_i_device_accessor.hpp" +#include "sdmmc_sd_card_device_accessor.hpp" + +namespace ams::sdmmc::impl { + + IHostController *GetHostControllerOfPortSdCard0(); + IDeviceAccessor *GetDeviceAccessorOfPortSdCard0(); + SdCardDeviceAccessor *GetSdCardDeviceAccessorOfPortSdCard0(); + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp new file mode 100644 index 000000000..9bfff1ad3 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.cpp @@ -0,0 +1,1054 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_sd_card_device_accessor.hpp" +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + #if defined(AMS_SDMMC_THREAD_SAFE) + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() std::scoped_lock lk(this->sd_card_device.device_mutex) + + #else + + #define AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX() + + #endif + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() R_UNLESS(!this->sd_card_device.IsRemoved(), sdmmc::ResultDeviceRemoved()) + + #else + + #define AMS_SDMMC_CHECK_SD_CARD_REMOVED() + + #endif + + namespace { + + constexpr inline u32 OcrCardPowerUpStatus = (1 << 31); + constexpr inline u32 OcrCardCapacityStatus = (1 << 30); + constexpr inline u32 OcrSwitchingTo1_8VAccepted = (1 << 24); + + constexpr bool IsLessThanSpecification1_1(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + + const u8 sd_spec = scr[0] & 0xF; + return sd_spec < 1; + } + + constexpr u32 GetSendOpCmdArgument(bool spec_under_2, bool uhs_i_supported) { + const u32 hcs = !spec_under_2 ? (1u << 30) : (0u << 30); + const u32 xpc = !spec_under_2 ? (1u << 28) : (0u << 28); + const u32 s18r = (!spec_under_2 && uhs_i_supported) ? (1u << 24) : (0u << 24); + return hcs | xpc | s18r | 0x00100000u; + } + + constexpr bool IsLessThanCsdVersion2(const u8 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Check whether CSD_STRUCTURE is 0. */ + return ((csd[14] & 0xC0) >> 6) == 0; + } + + constexpr u32 GetMemoryCapacityFromCsd(const u16 *csd) { + AMS_ABORT_UNLESS(csd != nullptr); + + /* Get CSIZE, convert appropriately. */ + const u32 csize = (static_cast(csd[3] & 0x3FFF) << 8) | (static_cast(csd[2] & 0xFF00) >> 8); + return (1 + csize) << 10; + } + + constexpr u8 GetSdBusWidths(const u8 *scr) { + AMS_ABORT_UNLESS(scr != nullptr); + return scr[1] & 0xF; + } + + constexpr bool IsSupportedBusWidth4Bit(u8 sd_bw) { + return (sd_bw & 0x4) != 0; + } + + constexpr bool IsSupportedAccessMode(const u8 *status, SwitchFunctionAccessMode access_mode) { + AMS_ABORT_UNLESS(status != nullptr); + + return (status[13] & (1u << access_mode)) != 0; + } + + constexpr u8 GetAccessModeFromFunctionSelection(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + return (status[16] & 0xF); + } + + constexpr bool IsAccessModeInFunctionSelection(const u8 *status, SwitchFunctionAccessMode mode) { + return GetAccessModeFromFunctionSelection(status) == static_cast(mode); + } + + constexpr u16 GetMaximumCurrentConsumption(const u8 *status) { + AMS_ABORT_UNLESS(status != nullptr); + + return (static_cast(status[0]) << 8) | + (static_cast(status[1]) << 0); + } + + constexpr u32 GetSizeOfProtectedArea(const u8 *sd_status) { + return (static_cast(sd_status[4]) << 24) | + (static_cast(sd_status[5]) << 16) | + (static_cast(sd_status[6]) << 8) | + (static_cast(sd_status[7]) << 0); + } + + Result GetCurrentSpeedMode(SpeedMode *out_sm, const u8 *status, bool is_uhs_i) { + AMS_ABORT_UNLESS(out_sm != nullptr); + + /* Get the access mode. */ + switch (static_cast(GetAccessModeFromFunctionSelection(status))) { + case SwitchFunctionAccessMode_Default: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr12; + } else { + *out_sm = SpeedMode_SdCardDefaultSpeed; + } + break; + case SwitchFunctionAccessMode_HighSpeed: + if (is_uhs_i) { + *out_sm = SpeedMode_SdCardSdr25; + } else { + *out_sm = SpeedMode_SdCardHighSpeed; + } + break; + case SwitchFunctionAccessMode_Sdr50: + *out_sm = SpeedMode_SdCardSdr50; + break; + case SwitchFunctionAccessMode_Sdr104: + *out_sm = SpeedMode_SdCardSdr104; + break; + case SwitchFunctionAccessMode_Ddr50: + *out_sm = SpeedMode_SdCardDdr50; + break; + default: + return sdmmc::ResultUnexpectedSdCardSwitchFunctionStatus(); + } + + return ResultSuccess(); + } + + } + + void SdCardDevice::SetOcrAndHighCapacity(u32 ocr) { + /* Set ocr. */ + BaseDevice::SetOcr(ocr); + + /* Set high capacity. */ + BaseDevice::SetHighCapacity((ocr & OcrCardCapacityStatus) != 0); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void SdCardDeviceAccessor::RemovedCallback() { + /* Signal that the device was removed. */ + this->sd_card_device.SignalRemovedEvent(); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Shut down. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + #endif + + Result SdCardDeviceAccessor::IssueCommandSendRelativeAddr(u16 *out_rca) const { + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R6; + Command command(CommandIndex_SendRelativeAddr, 0, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Set the output rca. */ + AMS_ABORT_UNLESS(out_rca != nullptr); + *out_rca = static_cast(resp >> 16); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendIfCond() const { + /* Get the argument. */ + constexpr u32 SendIfCommandArgument = 0x01AAu; + constexpr u32 SendIfCommandArgumentMask = 0x0FFFu; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R7; + Command command(CommandIndex_SendIfCond, SendIfCommandArgument, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Verify that our argument was returned to us. */ + R_UNLESS((resp & SendIfCommandArgumentMask) == (SendIfCommandArgument & SendIfCommandArgumentMask), sdmmc::ResultSdCardValidationError()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + constexpr u32 CheckSupportedFunctionArgument = 0x00FFFFFF; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, CheckSupportedFunctionArgument, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSwitchFunctionStatusSize); + + /* Get the argument. */ + const u32 arg = (set_function ? (1u << 31) : (0u << 31)) | 0x00FFFFF0 | static_cast(access_mode); + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_Switch, arg, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSwitchFunctionStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandVoltageSwitch() const { + /* Issue the command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(CommandIndex_VoltageSwitch, 0, false, DeviceState_Ready)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask) const { + /* Get arg. */ + const u32 arg = static_cast(this->sd_card_device.GetRca()) << 16; + + /* Issue the command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(CommandIndex_AppCmd, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + + /* Mask out the ignored status bits. */ + if (ignore_mask != 0) { + resp &= ~ignore_mask; + } + + /* Check the device status. */ + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + /* Check the app command bit. */ + R_UNLESS((resp & DeviceStatus_AppCmd) != 0, sdmmc::ResultUnexpectedSdCardAcmdDisabled()); + + /* Check the device state. */ + if (expected_state != DeviceState_Unknown) { + R_UNLESS(this->sd_card_device.GetDeviceState(resp) == expected_state, sdmmc::ResultUnexpectedDeviceState()); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSetBusWidth4Bit() const { + /* Issue the application command. */ + constexpr u32 Arg = 0x2; + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetBusWidth, Arg, false, DeviceState_Tran)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSdStatus(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardSdStatusSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SdStatus, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardSdStatusSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const { + /* Get the argument. */ + const u32 arg = GetSendOpCmdArgument(spec_under_2, uhs_i_supported); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R3; + Command command(SdApplicationCommandIndex_SdSendOpCond, arg, CommandResponseType, false); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command))); + + /* Get the response. */ + hc->GetLastResponse(out_ocr, sizeof(u32), CommandResponseType); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandClearCardDetect() const { + /* Issue the application command. */ + R_TRY(BaseDeviceAccessor::IssueCommandAndCheckR1(SdApplicationCommandIndex_SetClearCardDetect, 0, false, DeviceState_Tran)); + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::IssueCommandSendScr(void *dst, size_t dst_size) const { + /* Validate the output buffer. */ + AMS_ABORT_UNLESS(dst != nullptr); + AMS_ABORT_UNLESS(dst_size >= SdCardScrSize); + + /* Issue the application command. */ + constexpr ResponseType CommandResponseType = ResponseType_R1; + Command command(SdApplicationCommandIndex_SendScr, 0, CommandResponseType, false); + TransferData xfer_data(dst, SdCardScrSize, 1, TransferDirection_ReadFromDevice); + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->IssueCommand(std::addressof(command), std::addressof(xfer_data))); + + /* Get the response. */ + u32 resp; + hc->GetLastResponse(std::addressof(resp), sizeof(resp), CommandResponseType); + R_TRY(this->sd_card_device.CheckDeviceStatus(resp)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::EnterUhsIMode() { + /* Send voltage switch command. */ + R_TRY(this->IssueCommandVoltageSwitch()); + + /* Switch to sdr12. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SwitchToSdr12()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ChangeToReadyState(bool spec_under_2, bool uhs_i_supported) { + /* Decide on an ignore mask. */ + u32 ignore_mask = spec_under_2 ? static_cast(DeviceStatus_IllegalCommand) : 0u; + + /* Be prepared to wait up to 3.0 seconds to change state. */ + ManualTimer timer(3000); + while (true) { + /* We want to get ocr, which requires our sending an application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Unknown, ignore_mask)); + ignore_mask = 0; + + /* Get the ocr, and check if we're done. */ + u32 ocr; + R_TRY(this->IssueCommandSendOpCond(std::addressof(ocr), spec_under_2, uhs_i_supported)); + + if ((ocr & OcrCardPowerUpStatus) != 0) { + this->sd_card_device.SetOcrAndHighCapacity(ocr); + + /* Handle uhs i mode. */ + this->sd_card_device.SetUhsIMode(false); + if (uhs_i_supported && ((ocr & OcrSwitchingTo1_8VAccepted) != 0)) { + R_TRY(this->EnterUhsIMode()); + this->sd_card_device.SetUhsIMode(true); + } + + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardInitializationSoftwareTimeout()); + + /* Try again in 1ms. */ + WaitMicroSeconds(1000); + } + } + + Result SdCardDeviceAccessor::ChangeToStbyStateAndGetRca() { + /* Be prepared to wait up to 1.0 seconds to change state. */ + ManualTimer timer(1000); + while (true) { + /* Get rca. */ + u16 rca; + R_TRY(this->IssueCommandSendRelativeAddr(std::addressof(rca))); + if (rca != 0) { + this->sd_card_device.SetRca(rca); + return ResultSuccess(); + } + + /* Check if we've timed out. */ + R_UNLESS(timer.Update(), sdmmc::ResultSdCardGetValidRcaSoftwareTimeout()); + } + } + + Result SdCardDeviceAccessor::SetMemoryCapacity(const void *csd) { + if (IsLessThanCsdVersion2(static_cast(csd))) { + R_TRY(this->sd_card_device.SetLegacyMemoryCapacity()); + } else { + AMS_ABORT_UNLESS(util::IsAligned(reinterpret_cast(csd), alignof(u16))); + this->sd_card_device.SetMemoryCapacity(GetMemoryCapacityFromCsd(static_cast(csd))); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetScr(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSendScr(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusWidth(BusWidth max_bw, u8 sd_bw) { + /* If the maximum bus width is 1bit, we can't extend. */ + R_SUCCEED_IF(max_bw == BusWidth_1Bit); + + /* If 4bit mode isn't supported, we can't extend. */ + R_SUCCEED_IF(!IsSupportedBusWidth4Bit(sd_bw)); + + /* If the host controller doesn't support 4bit mode, we can't extend. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_SUCCEED_IF(!hc->IsSupportedBusWidth(BusWidth_4Bit)); + + /* Issue the application command to change to 4bit mode. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSetBusWidth4Bit()); + + /* Set the host controller's bus width. */ + hc->SetBusWidth(BusWidth_4Bit); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size) { + /* Issue command to check if we can switch access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, false, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardCannotSwitchAccessMode()); + + /* Check if we can accept the resulting current consumption. */ + constexpr u16 AcceptableCurrentLimit = 800; /* mA */ + R_UNLESS(GetMaximumCurrentConsumption(static_cast(wb)) < AcceptableCurrentLimit, sdmmc::ResultSdCardUnacceptableCurrentConsumption()); + + /* Switch the access mode. */ + R_TRY(this->IssueCommandSwitchAccessMode(wb, wb_size, true, access_mode)); + R_UNLESS(IsAccessModeInFunctionSelection(static_cast(wb), access_mode), sdmmc::ResultSdCardFailedSwitchAccessMode()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size) { + /* Check that we're in 4bit bus mode. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_UNLESS(hc->GetBusWidth() == BusWidth_4Bit, sdmmc::ResultSdCardNot4BitBusWidthAtUhsIMode()); + + /* Determine what speed mode/access mode we should switch to. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + SwitchFunctionAccessMode target_am; + SpeedMode target_sm; + if (max_sm == SpeedMode_SdCardSdr104 && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr104)) { + target_am = SwitchFunctionAccessMode_Sdr104; + target_sm = SpeedMode_SdCardSdr104; + } else if ((max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50) && IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_Sdr50)) { + target_am = SwitchFunctionAccessMode_Sdr50; + target_sm = SpeedMode_SdCardSdr50; + } else { + return sdmmc::ResultSdCardNotSupportSdr104AndSdr50(); + } + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(target_am, wb, wb_size)); + + /* Set the host controller speed mode and perform tuning using command index 19. */ + R_TRY(hc->SetSpeedMode(target_sm)); + R_TRY(hc->Tuning(target_sm, 19)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size) { + /* If the maximum speed is default speed, we have nothing to do. */ + R_SUCCEED_IF(max_sm == SpeedMode_SdCardDefaultSpeed); + + /* Otherwise, if the spec is under 1.1, we have nothing to do. */ + R_SUCCEED_IF(spec_under_1_1); + + /* Otherwise, Check if high speed is supported. */ + R_TRY(this->IssueCommandCheckSupportedFunction(wb, wb_size)); + R_SUCCEED_IF(!IsSupportedAccessMode(static_cast(wb), SwitchFunctionAccessMode_HighSpeed)); + + /* Switch the access mode. */ + R_TRY(this->SwitchAccessMode(SwitchFunctionAccessMode_HighSpeed, wb, wb_size)); + + /* Check status. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendStatus()); + + /* Set the host controller speed mode. */ + R_TRY(BaseDeviceAccessor::GetHostController()->SetSpeedMode(SpeedMode_SdCardHighSpeed)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdStatus(void *dst, size_t dst_size) const { + /* Issue the application command. */ + R_TRY(this->IssueCommandAppCmd(DeviceState_Tran)); + R_TRY(this->IssueCommandSdStatus(dst, dst_size)); + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::TryDisconnectDat3PullUpResistor() const { + /* Issue the application command to clear card detect. */ + /* NOTE: Nintendo accepts a failure. */ + if (R_SUCCEEDED(this->IssueCommandAppCmd(DeviceState_Tran))) { + /* NOTE: Nintendo does not check the result of this. */ + this->IssueCommandClearCardDetect(); + } + + /* NOTE: Nintendo does not check the result of this. */ + BaseDeviceAccessor::IssueCommandSendStatus(); + } + + Result SdCardDeviceAccessor::StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size) { + /* Start up the host controller. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + R_TRY(hc->Startup(BusPower_3_3V, BusWidth_1Bit, SpeedMode_SdCardIdentification, false)); + + /* Wait 1ms for configuration to take. */ + WaitMicroSeconds(1000); + + /* Wait an additional 74 clocks for configuration to take. */ + WaitClocks(74, hc->GetDeviceClockFrequencyKHz()); + + /* Go to idle state. */ + R_TRY(BaseDeviceAccessor::IssueCommandGoIdleState()); + + /* Check whether the spec is under 2.0. */ + bool spec_under_2 = false; + R_TRY_CATCH(this->IssueCommandSendIfCond()) { + R_CATCH(sdmmc::ResultResponseTimeoutError) { spec_under_2 = true; } + } R_END_TRY_CATCH; + + /* Set the rca to 0. */ + this->sd_card_device.SetRca(0); + + /* Go to ready state. */ + const bool can_use_uhs_i_mode = (max_bw != BusWidth_1Bit) && (max_sm == SpeedMode_SdCardSdr104 || max_sm == SpeedMode_SdCardSdr50); + const bool uhs_i_supported = hc->IsSupportedTuning() && hc->IsSupportedBusPower(BusPower_1_8V); + R_TRY(this->ChangeToReadyState(spec_under_2, can_use_uhs_i_mode && uhs_i_supported)); + + /* Get the CID. */ + R_TRY(BaseDeviceAccessor::IssueCommandAllSendCid(wb, wb_size)); + this->sd_card_device.SetCid(wb, wb_size); + + /* Go to stby state and get the RCA. */ + R_TRY(this->ChangeToStbyStateAndGetRca()); + + /* Get the CSD. */ + R_TRY(BaseDeviceAccessor::IssueCommandSendCsd(wb, wb_size)); + this->sd_card_device.SetCsd(wb, wb_size); + R_TRY(this->SetMemoryCapacity(wb)); + + /* Set the host controller speed mode to default if we're not in uhs i mode. */ + if (!this->sd_card_device.IsUhsIMode()) { + R_TRY(hc->SetSpeedMode(SpeedMode_SdCardDefaultSpeed)); + } + + /* Issue select card command. */ + R_TRY(BaseDeviceAccessor::IssueCommandSelectCard()); + + /* Set block length to sector size. */ + R_TRY(BaseDeviceAccessor::IssueCommandSetBlockLenToSectorSize()); + + /* Try to disconnect dat3 pullup resistor. */ + TryDisconnectDat3PullUpResistor(); + + /* Get the SCR. */ + R_TRY(this->GetScr(wb, wb_size)); + const u8 sd_bw = GetSdBusWidths(static_cast(wb)); + const bool spec_under_1_1 = IsLessThanSpecification1_1(static_cast(wb)); + + /* Extend the bus width to the largest that we can. */ + R_TRY(this->ExtendBusWidth(max_bw, sd_bw)); + + /* Extend the bus speed to as fast as we can. */ + if (this->sd_card_device.IsUhsIMode()) { + R_TRY(this->ExtendBusSpeedAtUhsIMode(max_sm, wb, wb_size)); + } else { + R_TRY(this->ExtendBusSpeedAtNonUhsIMode(max_sm, spec_under_1_1, wb, wb_size)); + } + + /* Enable power saving. */ + hc->SetPowerSaving(true); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::OnActivate() { + /* Define the possible startup parameters. */ + constexpr const struct { + BusWidth bus_width; + SpeedMode speed_mode; + } StartupParameters[] = { + #if defined(AMS_SDMMC_ENABLE_SD_UHS_I) + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardSdr104 }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #else + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardHighSpeed }, + { BusWidth_4Bit, SpeedMode_SdCardDefaultSpeed }, + { BusWidth_1Bit, SpeedMode_SdCardHighSpeed }, + #endif + }; + + /* Try to start up with each set of parameters. */ + Result result; + for (int i = 0; i < static_cast(util::size(StartupParameters)); ++i) { + /* Alias the parameters. */ + const auto ¶ms = StartupParameters[i]; + + /* Set our max bus width/speed mode. */ + this->max_bus_width = params.bus_width; + this->max_speed_mode = params.speed_mode; + + /* Try to start up the device. */ + result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_SUCCEEDED(result)) { + /* If we previously failed to start up the device, log the error correction. */ + if (i != 0) { + BaseDeviceAccessor::PushErrorLog(true, "S %d %d:0", this->max_bus_width, this->max_speed_mode); + BaseDeviceAccessor::IncrementNumActivationErrorCorrections(); + } + + return ResultSuccess(); + } + + /* Check if we were removed. */ + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + /* Log that our startup failed. */ + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + + /* Shut down the host controller before we try to start up again. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + } + + /* We failed to start up with all sets of parameters. */ + /* Check the csd for errors. */ + if (sdmmc::ResultUnexpectedDeviceCsdValue::Includes(result)) { + u32 csd[DeviceCsdSize / sizeof(u32)]; + this->sd_card_device.GetCsd(csd, sizeof(csd)); + BaseDeviceAccessor::PushErrorLog(false, "%06X%08X%08X%08X", csd[3] & 0x00FFFFFF, csd[2], csd[1], csd[0]); + } + BaseDeviceAccessor::PushErrorTimeStamp(); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + return result; + } + + Result SdCardDeviceAccessor::OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) { + /* Do the read/write. */ + const Result result = BaseDeviceAccessor::ReadWriteMultiple(sector_index, num_sectors, 0, buf, buf_size, is_read); + + /* Check if we failed because the sd card is removed. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (sdmmc::ResultCommunicationNotAttained::Includes(result)) { + WaitMicroSeconds(this->sd_card_detector->GetDebounceMilliSeconds() * 1000); + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + } + #endif + + return result; + } + + Result SdCardDeviceAccessor::ReStartup() { + /* Shut down the host controller. */ + BaseDeviceAccessor::GetHostController()->Shutdown(); + + /* Perform start up. */ + Result result = this->StartupSdCardDevice(this->max_bus_width, this->max_speed_mode, this->work_buffer, this->work_buffer_size); + if (R_FAILED(result)) { + AMS_SDMMC_CHECK_SD_CARD_REMOVED(); + + BaseDeviceAccessor::PushErrorLog(false, "S %d %d:%X", this->max_bus_width, this->max_speed_mode, result.GetValue()); + return result; + } + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::Initialize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already initialized, we don't need to do anything. */ + if (this->is_initialized) { + return; + } + + /* Set the base device to our sd card device. */ + BaseDeviceAccessor::SetDevice(std::addressof(this->sd_card_device)); + + /* Initialize. */ + IHostController *hc = BaseDeviceAccessor::GetHostController(); + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* TODO: We probably want this (and other sd card detection stuff) to be conditional pcv control active. */ + /* This will be a requirement to support sd card access with detector in stratosphere before PCV is alive. */ + this->sd_card_device.InitializeRemovedEvent(); + hc->PreSetRemovedEvent(this->sd_card_device.GetRemovedEvent()); + CallbackInfo ci = { + .inserted_callback = nullptr, + .inserted_callback_arg = this, + .removed_callback = RemovedCallbackEntry, + .removed_callback_arg = this, + }; + this->sd_card_detector->Initialize(std::addressof(ci)); + } + #endif + hc->Initialize(); + + /* Mark ourselves as initialized. */ + this->is_initialized = true; + } + + void SdCardDeviceAccessor::Finalize() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If we've already finalized, we don't need to do anything. */ + if (!this->is_initialized) { + return; + } + this->is_initialized = false; + + /* Deactivate the device. */ + BaseDeviceAccessor::Deactivate(); + + /* Finalize the host controller. */ + BaseDeviceAccessor::GetHostController()->Finalize(); + + /* Finalize the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + this->sd_card_detector->Finalize(); + this->sd_card_device.FinalizeRemovedEvent(); + } + #endif + } + + Result SdCardDeviceAccessor::Activate() { + /* Activate the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're awake. */ + R_UNLESS(this->sd_card_device.IsAwake(), sdmmc::ResultNotAwakened()); + + /* Check that we're not already active. */ + R_SUCCEED_IF(this->sd_card_device.IsActive()); + + /* Clear the removed event. */ + this->sd_card_device.ClearRemovedEvent(); + + /* Check that the SD card is inserted. */ + R_UNLESS(this->sd_card_detector->IsInserted(), sdmmc::ResultNoDevice()); + } + #endif + + /* Activate the base device. */ + return BaseDeviceAccessor::Activate(); + } + + Result SdCardDeviceAccessor::GetSpeedMode(SpeedMode *out_speed_mode) const { + /* Check that we can write to output. */ + AMS_ABORT_UNLESS(out_speed_mode != nullptr); + + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus default speed). */ + R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); + if (IsLessThanSpecification1_1(static_cast(this->work_buffer))) { + *out_speed_mode = SpeedMode_SdCardDefaultSpeed; + return ResultSuccess(); + } + + /* Get the current speed mode. */ + R_TRY(this->IssueCommandCheckSupportedFunction(this->work_buffer, this->work_buffer_size)); + R_TRY(GetCurrentSpeedMode(out_speed_mode, static_cast(this->work_buffer), this->sd_card_device.IsUhsIMode())); + + return ResultSuccess(); + } + + void SdCardDeviceAccessor::PutSdCardToSleep() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device isn't awake, we don't need to do anything. */ + if (!this->sd_card_device.IsAwake()) { + return; + } + + /* Put the device to sleep. */ + this->sd_card_device.PutToSleep(); + + /* Put the detector to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + this->sd_card_detector->PutToSleep(); + #endif + + /* If necessary, put the host controller to sleep. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) + #else + if (this->sd_card_device.IsActive()) + #endif + { + BaseDeviceAccessor::GetHostController()->PutToSleep(); + } + } + + void SdCardDeviceAccessor::AwakenSdCard() { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* If the device is awake, we don't need to do anything. */ + if (this->sd_card_device.IsAwake()) { + return; + } + + /* Wake the host controller, if we need to.*/ + bool force_det = false; + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + if (this->sd_card_device.IsActive() && !this->sd_card_device.IsRemoved()) + #else + if (this->sd_card_device.IsActive()) + #endif + { + const Result result = BaseDeviceAccessor::GetHostController()->Awaken(); + if (R_SUCCEEDED(result)) { + force_det = R_FAILED(BaseDeviceAccessor::IssueCommandSendStatus()); + } else { + BaseDeviceAccessor::PushErrorLog(true, "A:%X", result.GetValue()); + force_det = true; + } + } + + /* Wake the detector. */ + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + this->sd_card_detector->Awaken(force_det); + #else + AMS_UNUSED(force_det); + #endif + + /* Wake the device. */ + this->sd_card_device.Awaken(); + } + + Result SdCardDeviceAccessor::GetSdCardScr(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Get the SCR. */ + R_TRY(this->GetScr(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(dst, dst_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast(dst)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Get the status. */ + if (switch_function == SdCardSwitchFunction_CheckSupportedFunction) { + R_TRY(this->IssueCommandCheckSupportedFunction(dst, dst_size)); + } else { + SwitchFunctionAccessMode am; + switch (switch_function) { + case SdCardSwitchFunction_CheckDefault: am = SwitchFunctionAccessMode_Default; break; + case SdCardSwitchFunction_CheckHighSpeed: am = SwitchFunctionAccessMode_HighSpeed; break; + case SdCardSwitchFunction_CheckSdr50: am = SwitchFunctionAccessMode_Sdr50; break; + case SdCardSwitchFunction_CheckSdr104: am = SwitchFunctionAccessMode_Sdr104; break; + case SdCardSwitchFunction_CheckDdr50: am = SwitchFunctionAccessMode_Ddr50; break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + R_TRY(this->IssueCommandSwitchAccessMode(dst, dst_size, false, am)); + } + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Check whether we're specification 1 (and thus can't switch). */ + R_TRY(this->GetScr(this->work_buffer, this->work_buffer_size)); + R_UNLESS(!IsLessThanSpecification1_1(static_cast(this->work_buffer)), sdmmc::ResultSdCardNotSupportSwitchFunctionStatus()); + + /* Determine the access mode. */ + SwitchFunctionAccessMode am; + switch (speed_mode) { + case SpeedMode_SdCardSdr12: + case SpeedMode_SdCardDefaultSpeed: + am = SwitchFunctionAccessMode_Default; + break; + case SpeedMode_SdCardSdr25: + case SpeedMode_SdCardHighSpeed: + am = SwitchFunctionAccessMode_HighSpeed; + break; + case SpeedMode_SdCardSdr50: + am = SwitchFunctionAccessMode_Sdr50; + break; + case SpeedMode_SdCardSdr104: + am = SwitchFunctionAccessMode_Sdr104; + break; + case SpeedMode_SdCardDdr50: + am = SwitchFunctionAccessMode_Ddr50; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Check that the mode is supported. */ + R_TRY(this->IssueCommandSwitchAccessMode(this->work_buffer, this->work_buffer_size, false, am)); + R_UNLESS(IsSupportedAccessMode(static_cast(this->work_buffer), am), sdmmc::ResultSdCardNotSupportAccessMode()); + + /* Get the current consumption. */ + AMS_ABORT_UNLESS(out_current_consumption != nullptr); + *out_current_consumption = GetMaximumCurrentConsumption(static_cast(this->work_buffer)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardSdStatus(void *dst, size_t dst_size) const { + /* Acquire exclusive access to the device. */ + AMS_SDMMC_LOCK_SD_CARD_DEVICE_MUTEX(); + + /* Check that we're accessible. */ + R_TRY(this->sd_card_device.CheckAccessible()); + + /* Get the status. */ + R_TRY(this->GetSdStatus(dst, dst_size)); + + return ResultSuccess(); + } + + Result SdCardDeviceAccessor::GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const { + AMS_ABORT_UNLESS(out_num_sectors != nullptr); + + /* Get the sd status. */ + R_TRY(this->GetSdCardSdStatus(this->work_buffer, this->work_buffer_size)); + const u32 size_of_protected_area = GetSizeOfProtectedArea(static_cast(this->work_buffer)); + + /* Get the csd. */ + u8 csd[DeviceCsdSize]; + this->sd_card_device.GetCsd(csd, sizeof(csd)); + + /* Handle based on csd version. */ + if (IsLessThanCsdVersion2(csd)) { + /* Get c_size_mult and read_bl_len. */ + u8 c_size_mult, read_bl_len; + this->sd_card_device.GetLegacyCapacityParameters(std::addressof(c_size_mult), std::addressof(read_bl_len)); + + /* Validate the parameters. */ + R_UNLESS((read_bl_len + c_size_mult + 2) >= 9, sdmmc::ResultUnexpectedDeviceCsdValue()); + + /* Calculate capacity. */ + *out_num_sectors = size_of_protected_area << ((read_bl_len + c_size_mult + 2) - 9); + } else { + /* SIZE_OF_PROTECTED_AREA is in bytes. */ + *out_num_sectors = size_of_protected_area / SectorSize; + } + + return ResultSuccess(); + } + + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp new file mode 100644 index 000000000..4f37b0c29 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_card_device_accessor.hpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_base_device_accessor.hpp" + +#if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) +#include "sdmmc_device_detector.hpp" +#endif + +namespace ams::sdmmc::impl { + + class SdCardDevice : public BaseDevice { + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + mutable os::EventType removed_event; + #endif + u16 rca; + bool is_valid_rca; + bool is_uhs_i_mode; + public: + SdCardDevice() : rca(0) { + this->OnDeactivate(); + } + + virtual void Deactivate() override { + this->OnDeactivate(); + BaseDevice::Deactivate(); + } + + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + virtual os::EventType *GetRemovedEvent() const override { + return std::addressof(this->removed_event); + } + #elif defined(AMS_SDMMC_USE_OS_EVENTS) + virtual os::EventType *GetRemovedEvent() const override { + /* Mmc can't be removed. */ + return nullptr; + } + #endif + + virtual DeviceType GetDeviceType() const override { + return DeviceType_SdCard; + } + + virtual u16 GetRca() const override { + AMS_ABORT_UNLESS(this->is_valid_rca); + return this->rca; + } + + void OnDeactivate() { + this->is_valid_rca = false; + this->is_uhs_i_mode = false; + } + + void SetRca(u16 v) { + this->rca = v; + this->is_valid_rca = true; + } + + void SetOcrAndHighCapacity(u32 ocr); + + void SetUhsIMode(bool en) { + this->is_uhs_i_mode = en; + } + + bool IsUhsIMode() const { + return this->is_uhs_i_mode; + } + }; + + enum SdCardApplicationCommandIndex : std::underlying_type::type { + SdApplicationCommandIndex_SetBusWidth = 6, + + SdApplicationCommandIndex_SdStatus = 13, + + SdApplicationCommandIndex_SendNumWriteBlocks = 22, + SdApplicationCommandIndex_SetWriteBlockEraseCount = 23, + + SdApplicationCommandIndex_SdSendOpCond = 41, + SdApplicationCommandIndex_SetClearCardDetect = 42, + + SdApplicationCommandIndex_SendScr = 51, + }; + + enum SwitchFunctionAccessMode { + SwitchFunctionAccessMode_Default = 0, + SwitchFunctionAccessMode_HighSpeed = 1, + SwitchFunctionAccessMode_Sdr50 = 2, + SwitchFunctionAccessMode_Sdr104 = 3, + SwitchFunctionAccessMode_Ddr50 = 4, + }; + + class SdCardDeviceAccessor : public BaseDeviceAccessor { + private: + SdCardDevice sd_card_device; + void *work_buffer; + size_t work_buffer_size; + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + DeviceDetector *sd_card_detector; + #endif + BusWidth max_bus_width; + SpeedMode max_speed_mode; + bool is_initialized; + private: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + void RemovedCallback(); + + static void RemovedCallbackEntry(void *arg) { + static_cast(arg)->RemovedCallback(); + } + #endif + + Result IssueCommandSendRelativeAddr(u16 *out_rca) const; + Result IssueCommandSendIfCond() const; + Result IssueCommandCheckSupportedFunction(void *dst, size_t dst_size) const; + Result IssueCommandSwitchAccessMode(void *dst, size_t dst_size, bool set_function, SwitchFunctionAccessMode access_mode) const; + Result IssueCommandVoltageSwitch() const; + Result IssueCommandAppCmd(DeviceState expected_state, u32 ignore_mask = 0) const; + Result IssueCommandSetBusWidth4Bit() const; + Result IssueCommandSdStatus(void *dst, size_t dst_size) const; + Result IssueCommandSendOpCond(u32 *out_ocr, bool spec_under_2, bool uhs_i_supported) const; + Result IssueCommandClearCardDetect() const; + Result IssueCommandSendScr(void *dst, size_t dst_size) const; + + Result EnterUhsIMode(); + Result ChangeToReadyState(bool spec_under_2, bool uhs_i_supported); + Result ChangeToStbyStateAndGetRca(); + Result SetMemoryCapacity(const void *csd); + Result GetScr(void *dst, size_t dst_size) const; + Result ExtendBusWidth(BusWidth max_bw, u8 sd_bw); + Result SwitchAccessMode(SwitchFunctionAccessMode access_mode, void *wb, size_t wb_size); + Result ExtendBusSpeedAtUhsIMode(SpeedMode max_sm, void *wb, size_t wb_size); + Result ExtendBusSpeedAtNonUhsIMode(SpeedMode max_sm, bool spec_under_1_1, void *wb, size_t wb_size); + Result GetSdStatus(void *dst, size_t dst_size) const; + Result StartupSdCardDevice(BusWidth max_bw, SpeedMode max_sm, void *wb, size_t wb_size); + + void TryDisconnectDat3PullUpResistor() const; + protected: + virtual Result OnActivate() override; + virtual Result OnReadWrite(u32 sector_index, u32 num_sectors, void *buf, size_t buf_size, bool is_read) override; + virtual Result ReStartup() override; + public: + virtual void Initialize() override; + virtual void Finalize() override; + virtual Result Activate() override; + virtual Result GetSpeedMode(SpeedMode *out_speed_mode) const override; + public: + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + explicit SdCardDeviceAccessor(IHostController *hc, DeviceDetector *dd) : BaseDeviceAccessor(hc), sd_card_detector(dd) + #else + explicit SdCardDeviceAccessor(IHostController *hc) : BaseDeviceAccessor(hc) + #endif + { + this->work_buffer = nullptr; + this->work_buffer_size = 0; + this->max_bus_width = BusWidth_4Bit; + this->max_speed_mode = SpeedMode_SdCardSdr104; + this->is_initialized = false; + } + + void SetSdCardWorkBuffer(void *wb, size_t wb_size) { + this->work_buffer = wb; + this->work_buffer_size = wb_size; + } + + void PutSdCardToSleep(); + void AwakenSdCard(); + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors) const; + Result GetSdCardScr(void *dst, size_t dst_size) const; + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, SdCardSwitchFunction switch_function) const; + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, SpeedMode speed_mode) const; + Result GetSdCardSdStatus(void *dst, size_t dst_size) const; + + bool IsSdCardInserted() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->IsInserted(); + #else + AMS_ABORT("IsSdCardInserted without SdCardDetector"); + #endif + } + + bool IsSdCardRemoved() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_device.IsRemoved(); + #else + AMS_ABORT("IsSdCardRemoved without SdCardDetector"); + #endif + } + + void RegisterSdCardDetectionEventCallback(DeviceDetectionEventCallback cb, void *arg) { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->RegisterDetectionEventCallback(cb, arg); + #else + AMS_UNUSED(cb, arg); + AMS_ABORT("RegisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + + void UnregisterSdCardDetectionEventCallback() { + #if defined(AMS_SDMMC_USE_SD_CARD_DETECTOR) + return this->sd_card_detector->UnregisterDetectionEventCallback(); + #else + AMS_ABORT("UnregisterSdCardDetectionEventCallback without SdCardDetector"); + #endif + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp new file mode 100644 index 000000000..5cb9af01a --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.cpp @@ -0,0 +1,1045 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_timer.hpp" + +#if defined(ATMOSPHERE_IS_STRATOSPHERE) + #include +#endif + + +namespace ams::sdmmc::impl { + + namespace { + + constexpr inline u32 ControllerReactionTimeoutMilliSeconds = 2000; + constexpr inline u32 CommandTimeoutMilliSeconds = 2000; + constexpr inline u32 DefaultCheckTransferIntervalMilliSeconds = 1500; + constexpr inline u32 BusyTimeoutMilliSeconds = 2000; + + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::ResetBufferInfos() { + for (auto &info : this->buffer_infos) { + info.buffer_address = 0; + info.buffer_size = 0; + } + } + + dd::DeviceVirtualAddress SdHostStandardController::GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size) { + /* Try to find the buffer in our registered regions. */ + dd::DeviceVirtualAddress device_addr = 0; + for (const auto &info : this->buffer_infos) { + if (info.buffer_address <= buffer && (buffer + buffer_size) <= (info.buffer_address + info.buffer_size)) { + device_addr = info.buffer_device_virtual_address + (buffer - info.buffer_address); + break; + } + } + + /* Ensure that we found the buffer. */ + AMS_ABORT_UNLESS(device_addr != 0); + return device_addr; + } + #endif + + void SdHostStandardController::EnsureControl() { + /* Perform a read of clock control to be sure previous configuration takes. */ + reg::Read(this->registers->clock_control); + } + + Result SdHostStandardController::EnableInternalClock() { + /* Enable internal clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, OSCILLATE)); + this->EnsureControl(); + + /* Wait for the internal clock to become stable. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the clock is steady. */ + if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, READY))) { + break; + } + + /* If not, check for timeout. */ + R_UNLESS(timer.Update(), sdmmc::ResultInternalClockStableSoftwareTimeout()); + } + } + + /* Configure to use host controlled divided clock. */ + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, HOST_DRIVER)); + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, DIVIDED_CLOCK)); + + /* Set host version 4.0.0 enable. */ + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, VERSION_4)); + + /* Set host 64 bit addressing enable. */ + AMS_ABORT_UNLESS(reg::HasValue(this->registers->capabilities, SD_REG_BITS_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, SUPPORTED))); + reg::ReadWrite(this->registers->host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 64_BIT_ADDRESSING)); + + /* Select SDMA mode. */ + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DMA_SELECT, SDMA)); + + /* Configure timeout control to use the maximum timeout value (TMCLK * 2^27) */ + reg::ReadWrite(this->registers->timeout_control, SD_REG_BITS_VALUE(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0b1110)); + + return ResultSuccess(); + } + + void SdHostStandardController::SetBusPower(BusPower bus_power) { + /* Check that we support the bus power. */ + AMS_ABORT_UNLESS(this->IsSupportedBusPower(bus_power)); + + /* Set the appropriate power. */ + switch (bus_power) { + case BusPower_Off: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, OFF)); + break; + case BusPower_1_8V: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1_8V)); + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + case BusPower_3_3V: + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 3_3V)); + reg::ReadWrite(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::EnableInterruptStatus() { + /* Set the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + + /* Read/write the interrupt enables to be sure they take. */ + reg::Write(this->registers->normal_int_enable, reg::Read(this->registers->normal_int_enable)); + reg::Write(this->registers->error_int_enable, reg::Read(this->registers->error_int_enable)); + + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Clear interrupts. */ + this->ClearInterrupt(); + + /* Enable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(ENABLED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (ENABLED)); + } + #endif + } + + void SdHostStandardController::DisableInterruptStatus() { + /* If we're using interrupt events, configure appropriately. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Disable the interrupt signals. */ + reg::ReadWrite(this->registers->normal_signal_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_signal_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + #endif + + /* Mask the status register interrupt enables. */ + reg::ReadWrite(this->registers->normal_int_enable, SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(MASKED)); + reg::ReadWrite(this->registers->error_int_enable, SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND (MASKED)); + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result SdHostStandardController::WaitInterrupt(u32 timeout_ms) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait for the interrupt to be signaled. */ + os::WaitableHolderType *signaled_holder = os::TimedWaitAny(std::addressof(this->waitable_manager), TimeSpan::FromMilliSeconds(timeout_ms)); + if (signaled_holder == std::addressof(this->interrupt_event_holder)) { + /* We received the interrupt. */ + return ResultSuccess(); + } else if (signaled_holder == std::addressof(this->removed_event_holder)) { + /* The device was removed. */ + return sdmmc::ResultDeviceRemoved(); + } else { + /* Timeout occurred. */ + return sdmmc::ResultWaitInterruptSoftwareTimeout(); + } + } + + void SdHostStandardController::ClearInterrupt() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Clear the interrupt event. */ + os::ClearInterruptEvent(this->interrupt_event); + } + #endif + + void SdHostStandardController::SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data) { + /* Ensure the transfer data is valid. */ + AMS_ABORT_UNLESS(xfer_data->block_size != 0); + AMS_ABORT_UNLESS(xfer_data->num_blocks != 0); + + /* Determine the number of blocks. */ + const u16 num_blocks = std::min(xfer_data->num_blocks, SdHostStandardRegisters::BlockCountMax); + + /* Determine the address/how many blocks to transfer. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + const u64 address = this->GetDeviceVirtualAddress(reinterpret_cast(xfer_data->buffer), xfer_data->block_size * num_blocks); + const u16 num_xfer_blocks = num_blocks; + #else + const u64 address = reinterpret_cast(xfer_data->buffer); + const u16 num_xfer_blocks = num_blocks; + #endif + + /* Verify the address is usable. */ + AMS_ABORT_UNLESS(util::IsAligned(address, BufferDeviceVirtualAddressAlignment)); + + /* Configure for sdma. */ + reg::Write(this->registers->adma_address, static_cast(address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(address >> BITSIZEOF(u32))); + + /* Set our next sdma address. */ + this->next_sdma_address = util::AlignDown(address + SdmaBufferBoundary, SdmaBufferBoundary); + + /* Configure block size. */ + AMS_ABORT_UNLESS(xfer_data->block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(this->registers->block_size, SD_REG_BITS_ENUM (BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 512_KB), + SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, static_cast(xfer_data->block_size))); + + /* Configure transfer blocks. */ + reg::Write(this->registers->block_count, num_xfer_blocks); + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_xfer_blocks; + } + + /* Configure transfer mode. */ + reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM (TRANSFER_MODE_DMA_ENABLE, ENABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_BLOCK_COUNT_ENABLE, (xfer_data->is_multi_block_transfer), ENABLE, DISABLE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_MULTI_BLOCK_SELECT, (xfer_data->is_multi_block_transfer), MULTI_BLOCK, SINGLE_BLOCK), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, (xfer_data->transfer_direction == TransferDirection_ReadFromDevice), READ, WRITE), + SD_REG_BITS_ENUM_SEL(TRANSFER_MODE_AUTO_CMD_ENABLE, (xfer_data->is_stop_transmission_command_enabled), CMD12_ENABLE, DISABLE)); + } + + void SdHostStandardController::SetTransferForTuning() { + /* Get the tuning block size. */ + u16 tuning_block_size; + switch (this->GetBusWidth()) { + case BusWidth_4Bit: + tuning_block_size = 64; + break; + case BusWidth_8Bit: + tuning_block_size = 128; + break; + case BusWidth_1Bit: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure block size. */ + AMS_ABORT_UNLESS(tuning_block_size <= SdHostStandardBlockSizeTransferBlockSizeMax); + reg::Write(this->registers->block_size, SD_REG_BITS_VALUE(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, tuning_block_size)); + + /* Configure transfer blocks. */ + reg::Write(this->registers->block_count, 1); + + /* Configure transfer mode. */ + reg::Write(this->registers->transfer_mode, SD_REG_BITS_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, READ)); + } + + void SdHostStandardController::SetCommand(const Command *command, bool has_xfer_data) { + /* Encode the command value. */ + u16 command_val = 0; + + /* Encode the response type. */ + switch (command->response_type) { + case ResponseType_R0: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, NO_RESPONSE), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R1: + case ResponseType_R6: + case ResponseType_R7: + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_RESPONSE_TYPE, command->is_busy, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM (COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM (COMMAND_INDEX_CHECK, ENABLE)); + break; + case ResponseType_R2: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_136), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, ENABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + case ResponseType_R3: + command_val |= reg::Encode(SD_REG_BITS_ENUM(COMMAND_RESPONSE_TYPE, RESPONSE_LENGTH_48), + SD_REG_BITS_ENUM(COMMAND_CRC_CHECK, DISABLE), + SD_REG_BITS_ENUM(COMMAND_INDEX_CHECK, DISABLE)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Encode the data present select. */ + command_val |= reg::Encode(SD_REG_BITS_ENUM_SEL(COMMAND_DATA_PRESENT, has_xfer_data, DATA_PRESENT, NO_DATA_PRESENT)); + + /* Encode the command index. */ + AMS_ABORT_UNLESS(command->command_index <= SdHostStandardCommandIndexMax); + command_val |= reg::Encode(SD_REG_BITS_VALUE(COMMAND_COMMAND_INDEX, command->command_index)); + + /* Write the command and argument. */ + reg::Write(this->registers->argument, command->command_argument); + reg::Write(this->registers->command, command_val); + } + + void SdHostStandardController::SetCommandForTuning(u32 command_index) { + Command command(command_index, 0, ResponseType_R1, false); + return this->SetCommand(std::addressof(command), true); + } + + Result SdHostStandardController::ResetCmdDatLine() { + /* Set the software reset cmd/dat bits. */ + reg::ReadWrite(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, RESET), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, RESET)); + + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait until reset is done. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->software_reset, SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_CMD, WORK), + SD_REG_BITS_ENUM(SOFTWARE_RESET_FOR_DAT, WORK))) + { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + return sdmmc::ResultAbortTransactionSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::AbortTransaction() { + R_TRY(this->ResetCmdDatLine()); + return ResultSuccess(); + } + + Result SdHostStandardController::WaitWhileCommandInhibit(bool has_dat) { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command inhibit cmd is set. */ + { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitCmdSoftwareTimeout(); + } + } + } + + /* Wait while command inhibit dat is set. */ + if (has_dat) { + ManualTimer timer(ControllerReactionTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* Check if command inhibit is no longer present. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, READY))) { + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandInhibitDatSoftwareTimeout(); + } + } + } + + return ResultSuccess(); + } + + Result SdHostStandardController::CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask) { + /* Read the statuses. */ + volatile u16 normal_int_status = reg::Read(this->registers->normal_int_status); + volatile u16 error_int_status = reg::Read(this->registers->error_int_status); + volatile u16 auto_cmd_err_status = reg::Read(this->registers->acmd12_err); + + /* Set the output status, if necessary. */ + if (out_normal_int_status != nullptr) { + *out_normal_int_status = normal_int_status; + } + + /* If we don't have an error interrupt, just use the normal status. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, NO_ERROR))) { + /* If the wait mask has any bits set, we're done waiting for the interrupt. */ + const u16 masked_status = (normal_int_status & wait_mask); + R_UNLESS(masked_status != 0, sdmmc::ResultNoWaitedInterrupt()); + + /* Write the masked value to the status register to ensure consistent state. */ + reg::Write(this->registers->normal_int_status, masked_status); + return ResultSuccess(); + } + + /* We have an error interrupt. Write the status to the register to ensure consistent state. */ + reg::Write(this->registers->error_int_status, error_int_status); + + /* Check the error interrupt status bits, and return appropriate errors. */ + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, NO_ERROR)), sdmmc::ResultResponseIndexError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, NO_ERROR)), sdmmc::ResultResponseEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, NO_ERROR)), sdmmc::ResultResponseCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, NO_ERROR)), sdmmc::ResultResponseTimeoutError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, NO_ERROR)), sdmmc::ResultDataEndBitError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, NO_ERROR)), sdmmc::ResultDataCrcError()); + R_UNLESS(reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, NO_ERROR)), sdmmc::ResultDataTimeoutError()); + + /* Check for auto cmd errors. */ + if (reg::HasValue(error_int_status, SD_REG_BITS_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, ERROR))) { + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, NO_ERROR)), sdmmc::ResultAutoCommandResponseIndexError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, NO_ERROR)), sdmmc::ResultAutoCommandResponseEndBitError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, NO_ERROR)), sdmmc::ResultAutoCommandResponseCrcError()); + R_UNLESS(reg::HasValue(auto_cmd_err_status, SD_REG_BITS_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, NO_ERROR)), sdmmc::ResultAutoCommandResponseTimeoutError()); + + /* An known auto cmd error occurred. */ + return sdmmc::ResultSdHostStandardUnknownAutoCmdError(); + } else { + /* Unknown error occurred. */ + return sdmmc::ResultSdHostStandardUnknownError(); + } + } + + Result SdHostStandardController::WaitCommandComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(CommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + this->ClearInterrupt(); + + if (R_FAILED(result)) { + this->AbortTransaction(); + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* If the device wasn't removed, cancel our transaction. */ + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } + #else + { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while command is not complete. */ + { + ManualTimer timer(CommandTimeoutMilliSeconds); + while (true) { + /* Check and clear the interrupt status. */ + const auto result = this->CheckAndClearInterruptStatus(nullptr, reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, COMPLETE))); + + /* If we succeeded, we're done. */ + if (R_SUCCEEDED(result)) { + return ResultSuccess(); + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultCommandCompleteSoftwareTimeout(); + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitTransferComplete() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait for interrupt. */ + Result result = this->WaitInterrupt(this->check_transfer_interval_ms); + if (R_SUCCEEDED(result)) { + /* If we succeeded, check/clear our interrupt status. */ + volatile u16 normal_int_status; + result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + this->ClearInterrupt(); + + /* If the interrupt succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else { + /* Abort the transaction. */ + this->AbortTransaction(); + return result; + } + + return result; + } else if (sdmmc::ResultDeviceRemoved::Includes(result)) { + /* Otherwise, check if the device was removed. */ + return result; + } else { + /* Otherwise, timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + } + } + } + #else + { + /* Wait while transfer is not complete. */ + while (true) { + /* Get the last block count. */ + const u16 last_block_count = reg::Read(this->registers->block_count); + + /* Wait until transfer times out. */ + { + ManualTimer timer(this->check_transfer_interval_ms); + while (true) { + /* Check/clear our interrupt status. */ + volatile u16 normal_int_status; + const auto result = this->CheckAndClearInterruptStatus(std::addressof(normal_int_status), reg::Encode(SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE), + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))); + + /* If the check succeeded, check status. */ + if (R_SUCCEEDED(result)) { + /* If the transfer is complete, we're done. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, COMPLETE))) { + return ResultSuccess(); + } + + /* Otherwise, if a DMA interrupt was generated, advance to the next address. */ + if (reg::HasValue(normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, GENERATED))) { + reg::Write(this->registers->adma_address, static_cast(this->next_sdma_address >> 0)); + reg::Write(this->registers->upper_adma_address, static_cast(this->next_sdma_address >> BITSIZEOF(u32))); + + this->next_sdma_address += SdmaBufferBoundary; + } + } else if (sdmmc::ResultNoWaitedInterrupt::Includes(result)) { + /* Otherwise, if the wait for the interrupt isn't done, update the timer and check for timeout. */ + if (!timer.Update()) { + /* Only timeout if the transfer hasn't advanced. */ + if (last_block_count != reg::Read(this->registers->block_count)) { + this->AbortTransaction(); + return sdmmc::ResultTransferCompleteSoftwareTimeout(); + } + break; + } + } else { + /* Otherwise, we have a generic failure. */ + this->AbortTransaction(); + return result; + } + } + } + } + } + #endif + } + + Result SdHostStandardController::WaitWhileBusy() { + /* Ensure that we control the registers. */ + this->EnsureControl(); + + /* Wait while busy. */ + { + ManualTimer timer(BusyTimeoutMilliSeconds); + while (true) { + /* Check if the target has been removed. */ + R_TRY(this->CheckRemoved()); + + /* If the DAT0 line signal is level high, we're done. */ + if (reg::HasValue(this->registers->present_state, SD_REG_BITS_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, HIGH))) { + return ResultSuccess(); + } + + /* Otherwise, check if we're timed out. */ + if (!timer.Update()) { + this->AbortTransaction(); + return sdmmc::ResultBusySoftwareTimeout(); + } + } + } + } + + void SdHostStandardController::GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can write the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = reg::Read(this->registers->response[0]); + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = reg::Read(this->registers->response[0]); + out_response[1] = reg::Read(this->registers->response[1]); + out_response[2] = reg::Read(this->registers->response[2]); + out_response[3] = reg::Read(this->registers->response[3]); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + Result SdHostStandardController::IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit((xfer_data != nullptr) || command->is_busy)); + + /* Configure for the transfer. */ + u32 num_transferred_blocks = 0; + if (xfer_data != nullptr) { + /* Setup the transfer, and get the number of blocks. */ + this->SetTransfer(std::addressof(num_transferred_blocks), xfer_data); + + /* Ensure the device sees consistent data with the cpu. */ + dd::FlushDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Issue the command with interrupt status enabled. */ + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + this->SetCommand(command, xfer_data != nullptr); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + + /* Process any response. */ + if (command->response_type != ResponseType_R0) { + this->last_response_type = command->response_type; + this->GetResponse(this->last_response, sizeof(this->last_response), this->last_response_type); + } + + /* Wait for data to be transferred. */ + if (xfer_data != nullptr) { + R_TRY(this->WaitTransferComplete()); + } + } + + /* If data was transferred, ensure we're in a consistent state. */ + if (xfer_data != nullptr) { + /* Ensure the cpu sees consistent data with the device. */ + if (xfer_data->transfer_direction == TransferDirection_ReadFromDevice) { + dd::InvalidateDataCache(xfer_data->buffer, xfer_data->block_size * num_transferred_blocks); + } + + /* Set the number of transferred blocks. */ + if (out_num_transferred_blocks != nullptr) { + *out_num_transferred_blocks = num_transferred_blocks; + } + + /* Process stop transition command. */ + this->last_stop_transmission_response = this->registers->response[3]; + } + + /* Wait until we're no longer busy. */ + if (command->is_busy || (xfer_data != nullptr)) { + R_TRY(this->WaitWhileBusy()); + } + + return ResultSuccess(); + } + + Result SdHostStandardController::IssueStopTransmissionCommandWithDeviceClock(u32 *out_response) { + /* Wait until we can issue the command. */ + R_TRY(this->WaitWhileCommandInhibit(false)); + + /* Issue the command with interrupt status enabled. */ + constexpr ResponseType StopTransmissionCommandResponseType = ResponseType_R1; + { + this->EnableInterruptStatus(); + ON_SCOPE_EXIT { this->DisableInterruptStatus(); }; + + /* Set the command. */ + Command command(CommandIndex_StopTransmission, 0, StopTransmissionCommandResponseType, true); + this->SetCommand(std::addressof(command), false); + + /* Wait for the command to complete. */ + R_TRY(this->WaitCommandComplete()); + } + + /* Process response. */ + this->GetResponse(out_response, sizeof(u32), StopTransmissionCommandResponseType); + + /* Wait until we're done. */ + R_TRY(this->WaitWhileBusy()); + + return ResultSuccess(); + } + + SdHostStandardController::SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size) { + /* Translate the physical address to a address. */ + #if defined(ATMOSPHERE_IS_STRATOSPHERE) + const uintptr_t registers_addr = dd::QueryIoMapping(registers_phys_addr, registers_size); + #else + /* TODO: Discriminate between bpmp, exosphere address? */ + AMS_UNUSED(registers_size); + const uintptr_t registers_addr = static_cast(registers_phys_addr); + #endif + + /* Set registers. */ + AMS_ABORT_UNLESS(registers_addr != 0); + this->registers = reinterpret_cast(registers_addr); + + /* Reset DMA buffers, if we have any. */ + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + this->ResetBufferInfos(); + #endif + + /* Clear removed event, if we have one. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + this->removed_event = nullptr; + #endif + + /* Clear dma address. */ + this->next_sdma_address = 0; + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + + /* Clear clock/power trackers. */ + this->device_clock_frequency_khz = 0; + this->is_power_saving_enable = false; + this->is_device_clock_enable = false; + + /* Clear last response. */ + this->last_response_type = ResponseType_R0; + std::memset(this->last_response, 0, sizeof(this->last_response)); + this->last_stop_transmission_response = 0; + } + + void SdHostStandardController::Initialize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InitializeWaitableManager(std::addressof(this->waitable_manager)); + + AMS_ABORT_UNLESS(this->interrupt_event != nullptr); + os::InitializeWaitableHolder(std::addressof(this->interrupt_event_holder), this->interrupt_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->interrupt_event_holder)); + + if (this->removed_event != nullptr) { + os::InitializeWaitableHolder(std::addressof(this->removed_event_holder), this->removed_event); + os::LinkWaitableHolder(std::addressof(this->waitable_manager), std::addressof(this->removed_event_holder)); + } + } + #endif + } + + void SdHostStandardController::Finalize() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + if (this->removed_event != nullptr) { + os::UnlinkWaitableHolder(std::addressof(this->removed_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->removed_event_holder)); + } + + os::UnlinkWaitableHolder(std::addressof(this->interrupt_event_holder)); + os::FinalizeWaitableHolder(std::addressof(this->interrupt_event_holder)); + + os::FinalizeWaitableManager(std::addressof(this->waitable_manager)); + } + #endif + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void SdHostStandardController::RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and set a free info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + info = { + .buffer_address = buffer, + .buffer_size = buffer_size, + .buffer_device_virtual_address = buffer_device_virtual_address, + }; + return; + } + } + + AMS_ABORT("Out of BufferInfos\n"); + } + + void SdHostStandardController::UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + /* Find and clear the buffer info. */ + for (auto &info : this->buffer_infos) { + if (info.buffer_address == 0) { + AMS_ABORT_UNLESS(info.buffer_size == buffer_size); + AMS_ABORT_UNLESS(info.buffer_device_virtual_address == buffer_device_virtual_address); + + info.buffer_address = 0; + info.buffer_size = 0; + return; + } + } + + AMS_ABORT("BufferInfo not found\n"); + } + #endif + + void SdHostStandardController::SetWorkBuffer(void *wb, size_t wb_size) { + AMS_UNUSED(wb, wb_size); + AMS_ABORT("WorkBuffer is not needed\n"); + } + + BusPower SdHostStandardController::GetBusPower() const { + /* Check if the bus has power. */ + if (reg::HasValue(this->registers->power_control, SD_REG_BITS_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, ON))) { + /* If it does, return the corresponding power. */ + switch (reg::GetValue(this->registers->power_control, SD_REG_BITS_MASK(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1))) { + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_1_8V: + return BusPower_1_8V; + case SD_HOST_STANDARD_POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1_3_3V: + return BusPower_3_3V; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + /* It doesn't, so it's off. */ + return BusPower_Off; + } + } + + void SdHostStandardController::SetBusWidth(BusWidth bus_width) { + /* Check that we support the bus width. */ + AMS_ABORT_UNLESS(this->IsSupportedBusWidth(bus_width)); + + /* Set the appropriate data transfer width. */ + switch (bus_width) { + case BusWidth_1Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, ONE_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_4Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT)); + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, USE_DATA_TRANSFER_WIDTH)); + break; + case BusWidth_8Bit: + reg::ReadWrite(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + BusWidth SdHostStandardController::GetBusWidth() const { + /* Check if the bus is using eight-bit extended data transfer. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, EIGHT_BIT))) { + return BusWidth_8Bit; + } else { + /* Bus is configured as USE_DATA_TRANSFER_WIDTH, so check if it's four bit. */ + if (reg::HasValue(this->registers->host_control, SD_REG_BITS_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, FOUR_BIT))) { + return BusWidth_4Bit; + } else { + return BusWidth_1Bit; + } + } + } + + void SdHostStandardController::SetPowerSaving(bool en) { + /* Set whether we're power saving enable. */ + this->is_power_saving_enable = en; + + /* Configure accordingly. */ + if (this->is_power_saving_enable) { + /* We want to disable SD clock if it's enabled. */ + if (reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + } else { + /* We want to enable SD clock if it's disabled and we're supposed to enable device clock. */ + if (this->is_device_clock_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + } + } + + void SdHostStandardController::EnableDeviceClock() { + /* If we're not in power-saving mode and the device clock is disabled, enable it. */ + if (!this->is_power_saving_enable && reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE))) { + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + this->is_device_clock_enable = true; + } + + void SdHostStandardController::DisableDeviceClock() { + /* Unconditionally disable the device clock. */ + this->is_device_clock_enable = false; + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + void SdHostStandardController::ChangeCheckTransferInterval(u32 ms) { + this->check_transfer_interval_ms = ms; + } + + void SdHostStandardController::SetDefaultCheckTransferInterval() { + this->check_transfer_interval_ms = DefaultCheckTransferIntervalMilliSeconds; + } + + Result SdHostStandardController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueCommandWithDeviceClock(command, xfer_data, out_num_transferred_blocks); + } + } + + Result SdHostStandardController::IssueStopTransmissionCommand(u32 *out_response) { + /* We need to have device clock enabled to issue commands. */ + AMS_ABORT_UNLESS(this->is_device_clock_enable); + + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled and the device is usable for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that our configuration takes. */ + this->EnsureControl(); + + /* Wait 8 device clocks to be sure that it's usable. */ + WaitClocks(8, this->device_clock_frequency_khz); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->registers->clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Issue the command. */ + { + /* After we issue the command, we need to wait 8 device clocks. */ + ON_SCOPE_EXIT { WaitClocks(8, this->device_clock_frequency_khz); }; + + return this->IssueStopTransmissionCommandWithDeviceClock(out_response); + } + } + + void SdHostStandardController::GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_type == this->last_response_type); + + /* Get the response appropriately. */ + switch (response_type) { + case ResponseType_R1: + case ResponseType_R3: + case ResponseType_R6: + case ResponseType_R7: + /* 32-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 1); + out_response[0] = this->last_response[0]; + break; + case ResponseType_R2: + /* 128-bit response. */ + AMS_ABORT_UNLESS(response_size >= sizeof(u32) * 4); + out_response[0] = this->last_response[0]; + out_response[1] = this->last_response[1]; + out_response[2] = this->last_response[2]; + out_response[3] = this->last_response[3]; + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + void SdHostStandardController::GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const { + /* Check that we can get the response. */ + AMS_ABORT_UNLESS(out_response != nullptr); + AMS_ABORT_UNLESS(response_size >= sizeof(u32)); + + /* Get the response. */ + out_response[0] = this->last_stop_transmission_response; + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp new file mode 100644 index 000000000..7bdf0a3cd --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_controller.hpp @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_i_host_controller.hpp" +#include "sdmmc_sd_host_standard_registers.hpp" + +namespace ams::sdmmc::impl { + + class SdHostStandardController : public IHostController { + protected: + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + struct BufferInfo { + uintptr_t buffer_address; + size_t buffer_size; + dd::DeviceVirtualAddress buffer_device_virtual_address; + }; + + static constexpr inline auto NumBufferInfos = 3; + #endif + protected: + SdHostStandardRegisters *registers; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + BufferInfo buffer_infos[NumBufferInfos]; + #endif + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + os::WaitableManagerType waitable_manager; + os::InterruptEventType *interrupt_event; + os::WaitableHolderType interrupt_event_holder; + os::EventType *removed_event; + os::WaitableHolderType removed_event_holder; + #endif + + u64 next_sdma_address; + u32 check_transfer_interval_ms; + + u32 device_clock_frequency_khz; + bool is_power_saving_enable; + bool is_device_clock_enable; + + ResponseType last_response_type; + u32 last_response[4]; + u32 last_stop_transmission_response; + protected: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + void PreSetInterruptEvent(os::InterruptEventType *ie) { + this->interrupt_event = ie; + } + + bool IsRemoved() const { + return this->removed_event != nullptr && os::TryWaitEvent(this->removed_event); + } + #endif + + void SetDeviceClockFrequencyKHz(u32 khz) { + this->device_clock_frequency_khz = khz; + } + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void ResetBufferInfos(); + dd::DeviceVirtualAddress GetDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size); + #endif + + void EnsureControl(); + Result EnableInternalClock(); + void SetBusPower(BusPower bus_power); + + void EnableInterruptStatus(); + void DisableInterruptStatus(); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + Result WaitInterrupt(u32 timeout_ms); + void ClearInterrupt(); + #endif + + void SetTransfer(u32 *out_num_transferred_blocks, const TransferData *xfer_data); + void SetTransferForTuning(); + + void SetCommand(const Command *command, bool has_xfer_data); + void SetCommandForTuning(u32 command_index); + + Result ResetCmdDatLine(); + Result AbortTransaction(); + Result WaitWhileCommandInhibit(bool has_dat); + Result CheckAndClearInterruptStatus(volatile u16 *out_normal_int_status, u16 wait_mask); + Result WaitCommandComplete(); + Result WaitTransferComplete(); + Result WaitWhileBusy(); + + void GetResponse(u32 *out_response, size_t response_size, ResponseType response_type) const; + + Result IssueCommandWithDeviceClock(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks); + Result IssueStopTransmissionCommandWithDeviceClock(u32 *out_response); + + ALWAYS_INLINE Result CheckRemoved() { + #if defined(AMS_SDMMC_USE_OS_EVENTS) + R_UNLESS(!this->IsRemoved(), sdmmc::ResultDeviceRemoved()); + #endif + + return ResultSuccess(); + } + public: + SdHostStandardController(dd::PhysicalAddress registers_phys_addr, size_t registers_size); + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual void PreSetRemovedEvent(os::EventType *e) override { + this->removed_event = e; + } + #endif + + virtual void Initialize() override; + virtual void Finalize() override; + + #if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + virtual void RegisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + virtual void UnregisterDeviceVirtualAddress(uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) override; + #endif + + virtual void SetWorkBuffer(void *wb, size_t wb_size) override; + + virtual Result SwitchToSdr12() override { + AMS_ABORT("SwitchToSdr12 not supported\n"); + } + + virtual BusPower GetBusPower() const override; + + virtual void SetBusWidth(BusWidth bus_width) override; + virtual BusWidth GetBusWidth() const override; + + virtual u32 GetDeviceClockFrequencyKHz() const override { + return this->device_clock_frequency_khz; + } + + virtual void SetPowerSaving(bool en) override; + virtual bool IsPowerSavingEnable() const override { + return this->is_power_saving_enable; + } + + virtual void EnableDeviceClock() override; + virtual void DisableDeviceClock() override; + virtual bool IsDeviceClockEnable() const override { + return this->is_device_clock_enable; + } + + virtual u32 GetMaxTransferNumBlocks() const override { + return SdHostStandardRegisters::BlockCountMax; + } + + virtual void ChangeCheckTransferInterval(u32 ms) override; + virtual void SetDefaultCheckTransferInterval() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual void GetLastResponse(u32 *out_response, size_t response_size, ResponseType response_type) const override; + virtual void GetLastStopTransmissionResponse(u32 *out_response, size_t response_size) const override; + + virtual bool IsSupportedTuning() const override { + return false; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) { + AMS_UNUSED(speed_mode, command_index); + AMS_ABORT("Tuning not supported\n"); + } + + virtual void SaveTuningStatusForHs400() { + AMS_ABORT("SaveTuningStatusForHs400 not supported\n"); + } + + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp new file mode 100644 index 000000000..e104374ed --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sd_host_standard_registers.hpp @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::sdmmc::impl { + + struct SdHostStandardRegisters { + volatile uint32_t dma_address; + volatile uint16_t block_size; + volatile uint16_t block_count; + volatile uint32_t argument; + volatile uint16_t transfer_mode; + volatile uint16_t command; + volatile uint32_t response[0x4]; + volatile uint32_t buffer; + volatile uint32_t present_state; + volatile uint8_t host_control; + volatile uint8_t power_control; + volatile uint8_t block_gap_control; + volatile uint8_t wake_up_control; + volatile uint16_t clock_control; + volatile uint8_t timeout_control; + volatile uint8_t software_reset; + volatile uint16_t normal_int_status; + volatile uint16_t error_int_status; + volatile uint16_t normal_int_enable; + volatile uint16_t error_int_enable; + volatile uint16_t normal_signal_enable; + volatile uint16_t error_signal_enable; + volatile uint16_t acmd12_err; + volatile uint16_t host_control2; + volatile uint32_t capabilities; + volatile uint32_t capabilities_1; + volatile uint32_t max_current; + volatile uint32_t _0x4c; + volatile uint16_t set_acmd12_error; + volatile uint16_t set_int_error; + volatile uint8_t adma_error; + volatile uint8_t _0x56[0x3]; + volatile uint32_t adma_address; + volatile uint32_t upper_adma_address; + volatile uint16_t preset_for_init; + volatile uint16_t preset_for_default; + volatile uint16_t preset_for_high; + volatile uint16_t preset_for_sdr12; + volatile uint16_t preset_for_sdr25; + volatile uint16_t preset_for_sdr50; + volatile uint16_t preset_for_sdr104; + volatile uint16_t preset_for_ddr50; + volatile uint32_t _0x70[0x23]; + volatile uint16_t slot_int_status; + volatile uint16_t host_version; + + static constexpr inline u16 BlockCountMax = 0xFFFF; + }; + static_assert(sizeof(SdHostStandardRegisters) == 0x100); + + constexpr inline size_t SdmaBufferBoundary = 512_KB; + + #define SD_REG_BITS_MASK(NAME) REG_NAMED_BITS_MASK (SD_HOST_STANDARD, NAME) + #define SD_REG_BITS_VALUE(NAME, VALUE) REG_NAMED_BITS_VALUE (SD_HOST_STANDARD, NAME, VALUE) + #define SD_REG_BITS_ENUM(NAME, ENUM) REG_NAMED_BITS_ENUM (SD_HOST_STANDARD, NAME, ENUM) + #define SD_REG_BITS_ENUM_SEL(NAME, __COND__, TRUE_ENUM, FALSE_ENUM) REG_NAMED_BITS_ENUM_SEL(SD_HOST_STANDARD, NAME, __COND__, TRUE_ENUM, FALSE_ENUM) + + #define DEFINE_SD_REG(NAME, __OFFSET__, __WIDTH__) REG_DEFINE_NAMED_REG (SD_HOST_STANDARD, NAME, __OFFSET__, __WIDTH__) + #define DEFINE_SD_REG_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE) REG_DEFINE_NAMED_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE) + #define DEFINE_SD_REG_TWO_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE) REG_DEFINE_NAMED_TWO_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE) + #define DEFINE_SD_REG_THREE_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) REG_DEFINE_NAMED_THREE_BIT_ENUM(SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN) + #define DEFINE_SD_REG_FOUR_BIT_ENUM(NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) REG_DEFINE_NAMED_FOUR_BIT_ENUM (SD_HOST_STANDARD, NAME, __OFFSET__, ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, ELEVEN, TWELVE, THIRTEEN, FOURTEEN, FIFTEEN) + + DEFINE_SD_REG(BLOCK_SIZE_TRANSFER_BLOCK_SIZE, 0, 12); + DEFINE_SD_REG_THREE_BIT_ENUM(BLOCK_SIZE_SDMA_BUFFER_BOUNDARY, 12, 4_KB, 8_KB, 16_KB, 32_KB, 64_KB, 128_KB, 256_KB, 512_KB); + constexpr inline size_t SdHostStandardBlockSizeTransferBlockSizeMax = 0xFFF; + + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DMA_ENABLE, 0, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_BLOCK_COUNT_ENABLE, 1, DISABLE, ENABLE); + DEFINE_SD_REG_TWO_BIT_ENUM(TRANSFER_MODE_AUTO_CMD_ENABLE, 2, DISABLE, CMD12_ENABLE, CMD23_ENABLE, AUTO_SELECT); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_DATA_TRANSFER_DIRECTION, 4, WRITE, READ); + DEFINE_SD_REG_BIT_ENUM(TRANSFER_MODE_MULTI_BLOCK_SELECT, 5, SINGLE_BLOCK, MULTI_BLOCK); + + DEFINE_SD_REG_TWO_BIT_ENUM(COMMAND_RESPONSE_TYPE, 0, NO_RESPONSE, RESPONSE_LENGTH_136, RESPONSE_LENGTH_48, RESPONSE_LENGTH_48_CHECK_BUSY_AFTER_RESPONSE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_CRC_CHECK, 3, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_INDEX_CHECK, 4, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(COMMAND_DATA_PRESENT, 5, NO_DATA_PRESENT, DATA_PRESENT); + DEFINE_SD_REG(COMMAND_COMMAND_INDEX, 8, 6); + constexpr inline size_t SdHostStandardCommandIndexMax = 0x3F; + + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_CMD, 0, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_COMMAND_INHIBIT_DAT, 1, READY, NOT_READY); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT0_LINE_SIGNAL_LEVEL, 20, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT1_LINE_SIGNAL_LEVEL, 21, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT2_LINE_SIGNAL_LEVEL, 22, LOW, HIGH); + DEFINE_SD_REG_BIT_ENUM(PRESENT_STATE_DAT3_LINE_SIGNAL_LEVEL, 23, LOW, HIGH); + DEFINE_SD_REG(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 20, 4); + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_DATA_TRANSFER_WIDTH, 1, ONE_BIT, FOUR_BIT); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, 2, NORMAL_SPEED, HIGH_SPEED); + DEFINE_SD_REG_TWO_BIT_ENUM(HOST_CONTROL_DMA_SELECT, 3, SDMA, RESERVED1, ADMA2, ADMA2_OR_ADMA3); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL_EXTENDED_DATA_TRANSFER_WIDTH, 5, USE_DATA_TRANSFER_WIDTH, EIGHT_BIT); + + DEFINE_SD_REG_BIT_ENUM(POWER_CONTROL_SD_BUS_POWER_FOR_VDD1, 0, OFF, ON); + DEFINE_SD_REG_THREE_BIT_ENUM(POWER_CONTROL_SD_BUS_VOLTAGE_SELECT_FOR_VDD1, 1, RESERVED0, RESERVED1, RESERVED2, RESERVED3, RESERVED4, 1_8V, 3_0V, 3_3V); + + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_ENABLE, 0, STOP, OSCILLATE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_INTERNAL_CLOCK_STABLE, 1, NOT_READY, READY); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, 2, DISABLE, ENABLE); + DEFINE_SD_REG_BIT_ENUM(CLOCK_CONTROL_CLOCK_GENERATOR_SELECT, 5, DIVIDED_CLOCK, PROGRAMMABLE_CLOCK); + DEFINE_SD_REG(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, 6, 2); + DEFINE_SD_REG(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, 8, 8); + + DEFINE_SD_REG(TIMEOUT_CONTROL_DATA_TIMEOUT_COUNTER, 0, 4); + + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_ALL, 0, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_CMD, 1, WORK, RESET); + DEFINE_SD_REG_BIT_ENUM(SOFTWARE_RESET_FOR_DAT, 2, WORK, RESET); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_COMMAND_COMPLETE, 0, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_TRANSFER_COMPLETE, 1, NOT_COMPLETE, COMPLETE); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_DMA_INTERRUPT, 3, NOT_GENERATED, GENERATED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_STATUS_ERROR_INTERRUPT, 15, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_TIMEOUT, 0, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_CRC, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_END_BIT, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_COMMAND_INDEX, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_TIMEOUT, 4, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_CRC, 5, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_DATA_END_BIT, 6, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_STATUS_AUTO_CMD, 8, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_TIMEOUT, 1, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_CRC, 2, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_END_BIT, 3, NO_ERROR, ERROR); + DEFINE_SD_REG_BIT_ENUM(AUTO_CMD_ERROR_AUTO_CMD_INDEX, 4, NO_ERROR, ERROR); + + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, 5, MASKED, ENABLED); + + #define SD_HOST_STANDARD_NORMAL_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_COMMAND_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_TRANSFER_COMPLETE, __ENUM__), \ + SD_REG_BITS_ENUM(NORMAL_INTERRUPT_DMA_INTERRUPT, __ENUM__) + + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, 0, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, 1, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, 2, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, 3, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, 4, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, 5, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, 6, MASKED, ENABLED); + DEFINE_SD_REG_BIT_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, 8, MASKED, ENABLED); + + #define SD_HOST_STANDARD_ERROR_INTERRUPT_ENABLE_ISSUE_COMMAND(__ENUM__) \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_COMMAND_INDEX_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_TIMEOUT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_CRC_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_DATA_END_BIT_ERROR, __ENUM__), \ + SD_REG_BITS_ENUM(ERROR_INTERRUPT_AUTO_CMD_ERROR, __ENUM__) + + + DEFINE_SD_REG_THREE_BIT_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, 0, SDR12, SDR25, SDR50, SDR104, DDR50, HS400, RSVD6, UHS_II); + + constexpr inline auto SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_HS200 = SD_HOST_STANDARD_HOST_CONTROL2_UHS_MODE_SELECT_SDR104; + + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3, 3_3V_SIGNALING, 1_8V_SIGNALING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_EXECUTE_TUNING, 6, TUNING_COMPLETED, EXECUTE_TUNING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, 7, USING_FIXED_CLOCK, USING_TUNED_CLOCK); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_HOST_VERSION_4_ENABLE, 12, VERSION_300_COMPATIBLE, VERSION_4); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_64_BIT_ADDRESSING, 13, 32_BIT_ADDRESSING, 64_BIT_ADDRESSING); + DEFINE_SD_REG_BIT_ENUM(HOST_CONTROL2_PRESET_VALUE_ENABLE, 15, HOST_DRIVER, AUTOMATIC_SELECTION); + + DEFINE_SD_REG_BIT_ENUM(CAPABILITIES_64_BIT_SYSTEM_ADDRESS_SUPPORT_FOR_V3, 28, NOT_SUPPORTED, SUPPORTED); + +} 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 new file mode 100644 index 000000000..d69d498e1 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.cpp @@ -0,0 +1,1304 @@ +/* + * 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 . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#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(this->sdmmc_registers->io_spare, SD_REG_BITS_ENUM(IO_SPARE_SPARE_OUT_3, ONE_CYCLE_DELAY)); + + /* Select regulated reference voltage for trimmer and DLL supply. */ + reg::ReadWrite(this->sdmmc_registers->vendor_io_trim_cntrl, SD_REG_BITS_VALUE(VENDOR_IO_TRIM_CNTRL_SEL_VREG, 0)); + + /* Configure outbound tap value. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TRIM_VAL, this->GetOutboundTapValue())); + + /* Configure SPI_MODE_CLKEN_OVERRIDE. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_ENUM(VENDOR_CLOCK_CNTRL_SPI_MODE_CLKEN_OVERRIDE, NORMAL)); + + /* Set slew codes. */ + this->SetSlewCodes(); + + /* Set vref sel. */ + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_SDMMC2TMC_CFG_SDMEMCOMP_VREF_SEL, this->GetVrefSelValue())); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(bus_power); + this->CalibrateDriveStrength(bus_power); + + /* Enable internal clock. */ + R_TRY(SdHostStandardController::EnableInternalClock()); + + return ResultSuccess(); + } + + Result SdmmcController::SetClockTrimmer(SpeedMode speed_mode, u8 tap_value) { + /* If speed mode is Hs400, set the dqs trim value. */ + if (speed_mode == SpeedMode_MmcHs400) { + reg::ReadWrite(this->sdmmc_registers->vendor_cap_overrides, SD_REG_BITS_VALUE(VENDOR_CAP_OVERRIDES_DQS_TRIM_VAL, 40)); + } + + /* Configure tap value as updated by software. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, NOT_UPDATED_BY_HW)); + + /* Set the inbound tap value. */ + reg::ReadWrite(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_VALUE(VENDOR_CLOCK_CNTRL_TAP_VAL, tap_value)); + + /* Reset the cmd/dat line. */ + R_TRY(SdHostStandardController::ResetCmdDatLine()); + + return ResultSuccess(); + } + + u8 SdmmcController::GetCurrentTapValue() { + return static_cast(reg::GetValue(this->sdmmc_registers->vendor_clock_cntrl, SD_REG_BITS_MASK(VENDOR_CLOCK_CNTRL_TAP_VAL))); + } + + Result SdmmcController::CalibrateDll() { + /* Check if we need to temporarily re-enable the device clock. */ + const bool clock_disabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Ensure that the clock is enabled for the period we're using it. */ + if (clock_disabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + ON_SCOPE_EXIT { if (clock_disabled) { reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); } }; + + /* Begin calibration. */ + reg::ReadWrite(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE)); + + /* Wait up to 5ms for calibration to begin. */ + { + ManualTimer timer(5); + while (true) { + /* If calibration is done, we're done. */ + if (!reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_CALIBRATE, ENABLE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllCalibrationSoftwareTimeout()); + } + } + + /* Wait up to 10ms for calibration to complete. */ + { + ManualTimer timer(10); + while (true) { + /* If calibration is done, we're done. */ + if (reg::HasValue(this->sdmmc_registers->vendor_dllcal_cfg_sta, SD_REG_BITS_ENUM(VENDOR_DLLCAL_CFG_STA_DLL_CAL_ACTIVE, DONE))) { + break; + } + + /* Otherwise, check if we've timed out. */ + R_UNLESS((timer.Update()), sdmmc::ResultSdmmcDllApplicationSoftwareTimeout()); + } + } + + return ResultSuccess(); + } + + Result SdmmcController::SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value) { + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Set clock trimmer. */ + /* NOTE: Nintendo does not re-enable the clock if this fails... */ + R_TRY(this->SetClockTrimmer(speed_mode, tap_value)); + + /* Configure for the desired speed mode. */ + switch (speed_mode) { + case SpeedMode_MmcIdentification: + case SpeedMode_SdCardIdentification: + case SpeedMode_MmcLegacySpeed: + case SpeedMode_SdCardDefaultSpeed: + /* Set as normal speed, 3.3V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, NORMAL_SPEED)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHighSpeed: + case SpeedMode_SdCardHighSpeed: + /* Set as high speed, 3.3V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control, SD_REG_BITS_ENUM(HOST_CONTROL_HIGH_SPEED_ENABLE, HIGH_SPEED)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 3_3V_SIGNALING)); + break; + case SpeedMode_MmcHs200: + /* Set as HS200, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS200)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_MmcHs400: + /* Set as HS400, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, HS400)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr12: + /* Set as SDR12, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR12)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_SdCardSdr104: + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + /* Set as SDR104, 1.8V. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_UHS_MODE_SELECT, SDR104)); + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + SdHostStandardController::EnsureControl(); + + /* Get the divider setting. */ + u32 target_source_clock_frequency_khz; + u16 x; + GetDividerSetting(std::addressof(target_source_clock_frequency_khz), std::addressof(x), speed_mode); + + /* Set the clock frequency. */ + u32 actual_source_clock_frequency_khz; + ClockResetController::SetClockFrequencyKHz(std::addressof(actual_source_clock_frequency_khz), this->GetClockResetModule(), target_source_clock_frequency_khz); + + /* Set the device clock frequency. */ + const u32 actual_device_clock_frequency_khz = util::DivideUp(actual_source_clock_frequency_khz, x); + SdHostStandardController::SetDeviceClockFrequencyKHz(actual_device_clock_frequency_khz); + + /* Check that the divider is correct. */ + AMS_ABORT_UNLESS((x == 1) || util::IsAligned(x, 2)); + + /* Write the divider val to clock control. */ + const u16 n = x / 2; + const u16 upper_n = n >> 8; + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_VALUE(CLOCK_CONTROL_SDCLK_FREQUENCY_SELECT, n), + SD_REG_BITS_VALUE(CLOCK_CONTROL_UPPER_BITS_OF_SDCLK_FREQUENCY_SELECT, upper_n)); + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If speed mode is Hs400, calibrate dll. */ + if (speed_mode == SpeedMode_MmcHs400) { + R_TRY(this->CalibrateDll()); + } + + /* Set the current speed mode. */ + this->current_speed_mode = speed_mode; + + return ResultSuccess(); + } + + Result SdmmcController::IssueTuningCommand(u32 command_index) { + /* Check that we're not power saving enable. */ + AMS_ABORT_UNLESS(!SdHostStandardController::IsPowerSavingEnable()); + + /* Wait until command inhibit is done. */ + R_TRY(SdHostStandardController::WaitWhileCommandInhibit(true)); + + /* Set transfer for tuning. */ + SdHostStandardController::SetTransferForTuning(); + + /* If necessary, clear interrupt and enable buffer read ready signal. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + this->ClearInterrupt(); + + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + } + #endif + + /* Set the buffer read ready enable, and read status to ensure it takes. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + reg::Read(this->sdmmc_registers->sd_host_standard_registers.normal_int_status); + + /* Issue command with clock disabled. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + { + SdHostStandardController::SetCommandForTuning(command_index); + + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + SdHostStandardController::AbortTransaction(); + } + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* When we're done waiting, ensure that we clean up appropriately. */ + ON_SCOPE_EXIT { + /* Clear the buffer read ready signal, if we should. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_signal_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + #endif + + /* Clear the buffer read ready enable. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.normal_int_enable, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, MASKED)); + + /* Wait 8 clocks to ensure configuration takes. */ + SdHostStandardController::EnsureControl(); + WaitClocks(8, SdHostStandardController::GetDeviceClockFrequencyKHz()); + }; + + /* Wait for the command to finish. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + const auto result = SdHostStandardController::WaitInterrupt(TuningCommandTimeoutMilliSeconds); + if (R_SUCCEEDED(result)) { + /* If we succeeded, clear the interrupt. */ + reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + this->ClearInterrupt(); + return ResultSuccess(); + } else if (sdmmc::ResultWaitInterruptSoftwareTimeout::Includes(result)) { + SdHostStandardController::AbortTransaction(); + return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); + } else { + return result; + } + } + #else + { + SdHostStandardController::EnsureControl(); + ManualTimer timer(TuningCommandTimeoutMilliSeconds); + while (true) { + /* Check if we received the interrupt. */ + if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED))) { + /* If we did, acknowledge it. */ + reg::Write(this->sdmmc_registers->sd_host_standard_registers.normal_int_status, SD_REG_BITS_ENUM(NORMAL_INTERRUPT_BUFFER_READ_READY, ENABLED)); + return ResultSuccess(); + } + + /* Otherwise, check if we timed out. */ + if (!timer.Update()) { + SdHostStandardController::AbortTransaction(); + return sdmmc::ResultIssueTuningCommandSoftwareTimeout(); + } + } + } + #endif + } + + void SdmmcController::SetDriveCodeOffsets(BusPower bus_power) { + /* Get the offsets. */ + u8 pd, pu; + this->GetAutoCalOffsets(std::addressof(pd), std::addressof(pu), bus_power); + + /* Set the offsets. */ + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PD_OFFSET, pd), + SD_REG_BITS_VALUE(AUTO_CAL_CONFIG_AUTO_CAL_PU_OFFSET, pu)); + } + + void SdmmcController::CalibrateDriveStrength(BusPower bus_power) { + /* Reset drive strength calibration status. */ + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + + /* Check if we need to temporarily disable the device clock. */ + const bool clock_enabled = reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + + /* Ensure that the clock is disabled for the period we're using it. */ + if (clock_enabled) { + /* Turn off the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + } + + /* Calibrate with the clock disabled. */ + { + /* Set SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + if (reg::HasValue(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0))) { + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 1)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1); + } + + /* Calibrate with SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD set. */ + { + /* Begin autocal. */ + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_START, ENABLED), + SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, ENABLED)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(2); + + /* Wait up to 10ms for auto cal to complete. */ + ManualTimer timer(10); + while (true) { + /* Check if auto cal is inactive. */ + if (reg::HasValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_ENUM(AUTO_CAL_STATUS_AUTO_CAL_ACTIVE, INACTIVE))) { + /* Check the pullup status. */ + const u32 pullup = (reg::GetValue(this->sdmmc_registers->auto_cal_status, SD_REG_BITS_MASK(AUTO_CAL_STATUS_AUTO_CAL_PULLUP))) & 0x1F; + if (pullup == 0x1F) { + this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompShortToGnd(); + } + if (pullup == 0) { + this->drive_strength_calibration_status = sdmmc::ResultSdmmcCompOpen(); + } + + break; + } + + /* Otherwise, check if we've timed out. */ + if (!timer.Update()) { + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationSoftwareTimeout(); + + this->SetDriveStrengthToDefaultValues(bus_power); + reg::ReadWrite(this->sdmmc_registers->auto_cal_config, SD_REG_BITS_ENUM(AUTO_CAL_CONFIG_AUTO_CAL_ENABLE, DISABLED)); + break; + } + } + } + + /* Clear SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD. */ + reg::ReadWrite(this->sdmmc_registers->sdmemcomppadctrl, SD_REG_BITS_VALUE(SDMEMCOMPPADCTRL_PAD_E_INPUT_OR_E_PWRD, 0)); + } + + /* Re-enable the clock, if we should. */ + if (clock_enabled) { + /* Turn on the clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, ENABLE)); + } + + /* If calibration didn't receive a replacement error, set internal state to success. */ + if (sdmmc::ResultDriveStrengthCalibrationNotCompleted::Includes(this->drive_strength_calibration_status)) { + this->drive_strength_calibration_status = ResultSuccess(); + } + } + + Result SdmmcController::Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) { + /* Verify that we're awake. */ + AMS_ABORT_UNLESS(this->is_awake); + + /* Release the controller from reset. */ + this->ReleaseReset(speed_mode); + + /* Mark that we're not shutdown. */ + this->is_shutdown = false; + + /* Power on the controller. */ + R_TRY(this->PowerOn(bus_power)); + + /* Start up for the specific power. */ + R_TRY(this->StartupCore(bus_power)); + + /* Set our current power/width/speed. */ + SdHostStandardController::SetBusWidth(bus_width); + SdHostStandardController::SetBusPower(bus_power); + R_TRY(this->SetSpeedMode(speed_mode)); + this->SetPowerSaving(power_saving_enable); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + + /* Ensure that we can control the device. */ + SdHostStandardController::EnsureControl(); + + return ResultSuccess(); + } + + void SdmmcController::Shutdown() { + /* If we're already shut down, there's nothing to do. */ + if (this->is_shutdown) { + return; + } + + /* If we're currently awake, we need to disable clock/power. */ + if (this->is_awake) { + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + } + + /* Power off. */ + this->PowerOff(); + + /* If awake, assert reset. */ + if (this->is_awake) { + this->AssertReset(); + } + + /* Mark that we're shutdown. */ + this->is_shutdown = true; + } + + void SdmmcController::PutToSleep() { + /* If we're already shut down or asleep, there's nothing to do. */ + if (this->is_shutdown || !this->is_awake) { + return; + } + + /* Save values before sleep. */ + this->bus_power_before_sleep = SdHostStandardController::GetBusPower(); + this->bus_width_before_sleep = SdHostStandardController::GetBusWidth(); + this->speed_mode_before_sleep = this->current_speed_mode; + this->tap_value_before_sleep = this->GetCurrentTapValue(); + this->is_powersaving_enable_before_sleep = SdHostStandardController::IsPowerSavingEnable(); + + /* Disable clock/power to the device. */ + SdHostStandardController::DisableDeviceClock(); + SdHostStandardController::SetBusPower(BusPower_Off); + SdHostStandardController::EnsureControl(); + + /* Assert reset. */ + this->AssertReset(); + + /* Mark that we're asleep. */ + this->is_awake = false; + } + + Result SdmmcController::Awaken() { + /* If we're shut down, or if we're awake already, there's nothing to do. */ + R_SUCCEED_IF(this->is_shutdown); + R_SUCCEED_IF(this->is_awake); + + /* Mark that we're awake. */ + this->is_awake = true; + + /* Clear pad parked status. */ + this->ClearPadParked(); + + /* Release reset. */ + this->ReleaseReset(this->speed_mode_before_sleep); + + /* Start up for the correct power. */ + R_TRY(this->StartupCore(this->bus_power_before_sleep)); + + /* Configure values to what they were before sleep. */ + SdHostStandardController::SetBusWidth(this->bus_width_before_sleep); + SdHostStandardController::SetBusPower(this->bus_power_before_sleep); + R_TRY(this->SetSpeedModeWithTapValue(this->speed_mode_before_sleep, this->tap_value_before_sleep)); + this->SetPowerSaving(this->is_powersaving_enable_before_sleep); + + /* Enable clock to the device. */ + SdHostStandardController::EnableDeviceClock(); + SdHostStandardController::EnsureControl(); + + return ResultSuccess(); + } + + Result SdmmcController::SwitchToSdr12() { + /* Disable clock. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + + /* Check that the dat lines are all low. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b0000)), sdmmc::ResultSdCardNotReadyToVoltageSwitch()); + + /* Set voltage to 1.8V. */ + SdHostStandardController::EnsureControl(); + R_TRY(this->LowerBusPower()); + this->SetSchmittTrigger(BusPower_1_8V); + + /* Perform drive strength calibration at the new power. */ + this->SetDriveCodeOffsets(BusPower_1_8V); + this->CalibrateDriveStrength(BusPower_1_8V); + + /* Set the bus power in standard controller. */ + SdHostStandardController::SetBusPower(BusPower_1_8V); + + /* Wait up to 5ms for the switch to take. */ + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(5000); + + /* Check that we switched to 1.8V. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_1_8V_SIGNALING_ENABLE, 1_8V_SIGNALING)), sdmmc::ResultSdHostStandardFailSwitchTo1_8V()); + + /* Enable clock, and wait 1ms. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.clock_control, SD_REG_BITS_ENUM(CLOCK_CONTROL_SD_CLOCK_ENABLE, DISABLE)); + SdHostStandardController::EnsureControl(); + WaitMicroSeconds(1000); + + /* Check that the dat lines are all high. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.present_state, SD_REG_BITS_VALUE(PRESENT_STATE_DAT0_3_LINE_SIGNAL_LEVEL, 0b1111)), sdmmc::ResultSdCardNotCompleteVoltageSwitch()); + + return ResultSuccess(); + } + + Result SdmmcController::SetSpeedMode(SpeedMode speed_mode) { + /* Get the tap value. */ + u8 tap_value; + if (speed_mode == SpeedMode_MmcHs400) { + AMS_ABORT_UNLESS(this->is_valid_tap_value_for_hs_400); + tap_value = this->tap_value_for_hs_400; + } else { + tap_value = this->GetDefaultInboundTapValue(); + } + + /* Set the speed mode. */ + R_TRY(this->SetSpeedModeWithTapValue(speed_mode, tap_value)); + + return ResultSuccess(); + } + + void SdmmcController::SetPowerSaving(bool en) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !en && SdHostStandardController::IsDeviceClockEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::SetPowerSaving(en); + } + + void SdmmcController::EnableDeviceClock() { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && !SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::EnableDeviceClock(); + } + + Result SdmmcController::IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::IssueCommand(command, xfer_data, out_num_transferred_blocks); + } + + Result SdmmcController::IssueStopTransmissionCommand(u32 *out_response) { + /* If necessary, calibrate the drive strength. */ + if (this->IsNeedPeriodicDriveStrengthCalibration() && SdHostStandardController::IsPowerSavingEnable()) { + this->CalibrateDriveStrength(SdHostStandardController::GetBusPower()); + } + + return SdHostStandardController::IssueStopTransmissionCommand(out_response); + } + + Result SdmmcController::Tuning(SpeedMode speed_mode, u32 command_index) { + /* Clear vendor tuning control 1. */ + reg::Write(this->sdmmc_registers->vendor_tuning_cntrl1, 0); + + /* Determine/configure the number of tries. */ + int num_tries; + switch (speed_mode) { + case SpeedMode_MmcHs200: + case SpeedMode_MmcHs400: + case SpeedMode_SdCardSdr104: + num_tries = 128; + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_128)); + break; + case SpeedMode_SdCardSdr50: + case SpeedMode_GcAsicFpgaSpeed: + case SpeedMode_GcAsicSpeed: + num_tries = 256; + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_NUM_TUNING_ITERATIONS, TRIES_256)); + break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Configure the multiplier. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_VALUE(VENDOR_TUNING_CNTRL0_MUL_M, 1)); + + /* Configure tap value to be updated by hardware. */ + reg::ReadWrite(this->sdmmc_registers->vendor_tuning_cntrl0, SD_REG_BITS_ENUM(VENDOR_TUNING_CNTRL0_TAP_VALUE_UPDATED_BY_HW, UPDATED_BY_HW)); + + /* Configure to execute tuning. */ + reg::ReadWrite(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, EXECUTE_TUNING)); + + /* Perform tuning num_tries times. */ + for (int i = 0; /* ... */; ++i) { + /* Check if we've been removed. */ + R_TRY(this->CheckRemoved()); + + /* Issue the command. */ + this->IssueTuningCommand(command_index); + + /* Check if tuning is done. */ + if (i >= num_tries) { + break; + } + ++i; + + if (reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_EXECUTE_TUNING, TUNING_COMPLETED))) { + break; + } + } + + /* Check if we're using the tuned clock. */ + R_UNLESS(reg::HasValue(this->sdmmc_registers->sd_host_standard_registers.host_control2, SD_REG_BITS_ENUM(HOST_CONTROL2_SAMPLING_CLOCK, USING_TUNED_CLOCK)), sdmmc::ResultTuningFailed()); + + return ResultSuccess(); + } + + void SdmmcController::SaveTuningStatusForHs400() { + /* Save the current tap value. */ + this->tap_value_for_hs_400 = GetCurrentTapValue(); + this->is_valid_tap_value_for_hs_400 = true; + } + + 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 { this->current_bus_power = BusPower_3_3V; }; + + /* pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ + R_TRY(this->power_controller->PowerOn(BusPower_3_3V)); + + return ResultSuccess(); + } + + void Sdmmc1Controller::PowerOffForRegisterControl() { + /* If we're already off, there's nothing to do. */ + if (this->current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + { + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + this->power_controller->LowerBusPower(); + + /* Set our bus power. */ + this->current_bus_power = BusPower_1_8V; + } + + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1OutputHigh); + + + /* pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + this->power_controller->PowerOff(); + + /* Set our bus power. */ + this->current_bus_power = BusPower_Off; + + /* 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; }; + + /* pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + R_TRY(this->power_controller->LowerBusPower()); + + return ResultSuccess(); + } + + void Sdmmc1Controller::SetSchmittTriggerForRegisterControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* 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: + /* pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + pinmux_impl::SetPinAssignment(pinmux_impl::PinAssignment_Sdmmc1SchmtEnable); + break; + case BusPower_3_3V: + /* 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(); + } + } + } + + #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 { this->current_bus_power = BusPower_3_3V; }; + + /* TODO: return pcv::PowerOn(pcv::PowerControlTarget_SdCard, 3300000); */ + return ResultSuccess(); + } + + void Sdmmc1Controller::PowerOffForPcvControl() { + /* If we're already off, there's nothing to do. */ + if (this->current_bus_power == BusPower_Off) { + return; + } + + /* If we're at 3.3V, lower to 1.8V. */ + { + /* TODO: pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + this->current_bus_power = BusPower_1_8V; + } + + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1OutputHigh); */ + + /* TODO: pcv::PowerOff(pcv::PowerControlTarget_SdCard); */ + this->current_bus_power = BusPower_Off; + + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1ResetState); */ + + } + + Result Sdmmc1Controller::LowerBusPowerForPcvControl() { + /* Nintendo sets the current bus power regardless of whether the call succeeds. */ + ON_SCOPE_EXIT { this->current_bus_power = BusPower_1_8V; }; + + /* TODO: return pcv::ChangeVoltage(pcv::PowerControlTarget_SdCard, 1800000); */ + return ResultSuccess(); + } + + void Sdmmc1Controller::SetSchmittTriggerForPcvControl(BusPower bus_power) { + SdHostStandardController::EnsureControl(); + + if (IsSocMariko()) { + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + } else { + switch (bus_power) { + case BusPower_1_8V: + /* TODO: pinmux::SetPinAssignment(std::addressof(this->pinmux_session), pinmux::PinAssignment_Sdmmc1SchmtEnable); */ + break; + case BusPower_3_3V: + /* TODO: pinmux::SetPinAssignment(std::addressof(this->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 (this->is_pcv_control) { + return this->PowerOnForPcvControl(bus_power); + } else + #endif + { + return this->PowerOnForRegisterControl(bus_power); + } + } + + void Sdmmc1Controller::PowerOff() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->PowerOffForPcvControl(); + } else + #endif + { + return this->PowerOffForRegisterControl(); + } + } + + Result Sdmmc1Controller::LowerBusPower() { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->is_pcv_control) { + return this->LowerBusPowerForPcvControl(); + } else + #endif + { + return this->LowerBusPowerForRegisterControl(); + } + } + + void Sdmmc1Controller::SetSchmittTrigger(BusPower bus_power) { + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + if (this->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 (this->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. */ + this->is_pcv_control = false; + #endif + + /* 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(); + } + + 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(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. */ + this->is_pcv_control = false; + #endif + } + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void Sdmmc1Controller::InitializeForPcvControl() { + /* Mark ourselves as initialized by pcv control. */ + this->is_pcv_control = true; + + /* TODO: pinmux::Initialize(); */ + /* TODO: pinmux::OpenSession(std::addressof(this->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(this->pinmux_session)); */ + /* TODO: pinmux::Finalize(); */ + + /* Mark ourselves as initialized by register control. */ + this->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(); + } + + 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 new file mode 100644 index 000000000..4c5fba691 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_sdmmc_controller.board.nintendo_nx.hpp @@ -0,0 +1,682 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include +#include "sdmmc_sd_host_standard_controller.hpp" +#include "sdmmc_clock_reset_controller.hpp" + +namespace ams::sdmmc::impl { + + bool IsSocMariko(); + + constexpr inline size_t SdmmcRegistersSize = 0x200; + + constexpr inline dd::PhysicalAddress ApbMiscRegistersPhysicalAddress = UINT64_C(0x70000000); + constexpr inline size_t ApbMiscRegistersSize = 16_KB; + + class SdmmcController : public SdHostStandardController { + private: + struct SdmmcRegisters { + /* Standard registers. */ + volatile SdHostStandardRegisters sd_host_standard_registers; + + /* Vendor specific registers */ + volatile uint32_t vendor_clock_cntrl; + volatile uint32_t vendor_sys_sw_cntrl; + volatile uint32_t vendor_err_intr_status; + volatile uint32_t vendor_cap_overrides; + volatile uint32_t vendor_boot_cntrl; + volatile uint32_t vendor_boot_ack_timeout; + volatile uint32_t vendor_boot_dat_timeout; + volatile uint32_t vendor_debounce_count; + volatile uint32_t vendor_misc_cntrl; + volatile uint32_t max_current_override; + volatile uint32_t max_current_override_hi; + volatile uint32_t _0x12c[0x20]; + volatile uint32_t vendor_io_trim_cntrl; + + /* Start of sdmmc2/sdmmc4 only */ + volatile uint32_t vendor_dllcal_cfg; + volatile uint32_t vendor_dll_ctrl0; + volatile uint32_t vendor_dll_ctrl1; + volatile uint32_t vendor_dllcal_cfg_sta; + /* End of sdmmc2/sdmmc4 only */ + + volatile uint32_t vendor_tuning_cntrl0; + volatile uint32_t vendor_tuning_cntrl1; + volatile uint32_t vendor_tuning_status0; + volatile uint32_t vendor_tuning_status1; + volatile uint32_t vendor_clk_gate_hysteresis_count; + volatile uint32_t vendor_preset_val0; + volatile uint32_t vendor_preset_val1; + volatile uint32_t vendor_preset_val2; + volatile uint32_t sdmemcomppadctrl; + volatile uint32_t auto_cal_config; + volatile uint32_t auto_cal_interval; + volatile uint32_t auto_cal_status; + volatile uint32_t io_spare; + volatile uint32_t sdmmca_mccif_fifoctrl; + volatile uint32_t timeout_wcoal_sdmmca; + volatile uint32_t _0x1fc; + }; + static_assert(sizeof(SdmmcRegisters) == SdmmcRegistersSize); + private: + SdmmcRegisters *sdmmc_registers; + bool is_shutdown; + bool is_awake; + SpeedMode current_speed_mode; + BusPower bus_power_before_sleep; + BusWidth bus_width_before_sleep; + SpeedMode speed_mode_before_sleep; + u8 tap_value_before_sleep; + bool is_powersaving_enable_before_sleep; + u8 tap_value_for_hs_400; + bool is_valid_tap_value_for_hs_400; + Result drive_strength_calibration_status; + private: + void ReleaseReset(SpeedMode speed_mode); + void AssertReset(); + Result StartupCore(BusPower bus_power); + Result SetClockTrimmer(SpeedMode speed_mode, u8 tap_value); + u8 GetCurrentTapValue(); + Result CalibrateDll(); + Result SetSpeedModeWithTapValue(SpeedMode speed_mode, u8 tap_value); + Result IssueTuningCommand(u32 command_index); + protected: + void SetDriveCodeOffsets(BusPower bus_power); + void CalibrateDriveStrength(BusPower bus_power); + + virtual void SetPad() = 0; + + virtual ClockResetController::Module GetClockResetModule() const = 0; + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const = 0; + virtual os::InterruptEventType *GetInterruptEvent() const = 0; + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() = 0; + virtual void ClearPadParked() = 0; + virtual Result PowerOn(BusPower bus_power) = 0; + virtual void PowerOff() = 0; + virtual Result LowerBusPower() = 0; + virtual void SetSchmittTrigger(BusPower bus_power) = 0; + virtual u8 GetOutboundTapValue() const = 0; + virtual u8 GetDefaultInboundTapValue() const = 0; + virtual u8 GetVrefSelValue() const = 0; + virtual void SetSlewCodes() = 0; + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const = 0; + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) = 0; + public: + explicit SdmmcController(dd::PhysicalAddress registers_phys_addr) : SdHostStandardController(registers_phys_addr, SdmmcRegistersSize) { + /* Set sdmmc registers. */ + static_assert(offsetof(SdmmcRegisters, sd_host_standard_registers) == 0); + this->sdmmc_registers = reinterpret_cast(this->registers); + + this->is_shutdown = true; + this->is_awake = true; + this->is_valid_tap_value_for_hs_400 = false; + this->drive_strength_calibration_status = sdmmc::ResultDriveStrengthCalibrationNotCompleted(); + this->tap_value_for_hs_400 = 0; + this->current_speed_mode = SpeedMode_MmcIdentification; + this->bus_power_before_sleep = BusPower_Off; + this->bus_width_before_sleep = BusWidth_1Bit; + this->speed_mode_before_sleep = SpeedMode_MmcIdentification; + this->tap_value_before_sleep = 0; + this->is_powersaving_enable_before_sleep = false; + } + + virtual void Initialize() override { + /* Set pad. */ + this->SetPad(); + + /* Initialize our clock/reset module. */ + ClockResetController::Initialize(this->GetClockResetModule()); + + /* If necessary, initialize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::InterruptEventType *interrupt_event = this->GetInterruptEvent(); + os::InitializeInterruptEvent(interrupt_event, this->GetInterruptNumber(), os::EventClearMode_ManualClear); + SdHostStandardController::PreSetInterruptEvent(interrupt_event); + } + #endif + + /* Perform base initialization. */ + SdHostStandardController::Initialize(); + } + + virtual void Finalize() override { + /* Perform base finalization. */ + SdHostStandardController::Finalize(); + + /* If necessary, finalize our interrupt event. */ + #if defined(AMS_SDMMC_USE_OS_EVENTS) + { + os::FinalizeInterruptEvent(this->GetInterruptEvent()); + } + #endif + + /* Finalize our clock/reset module. */ + ClockResetController::Finalize(this->GetClockResetModule()); + } + + virtual Result Startup(BusPower bus_power, BusWidth bus_width, SpeedMode speed_mode, bool power_saving_enable) override; + virtual void Shutdown() override; + virtual void PutToSleep() override; + virtual Result Awaken() override; + virtual Result SwitchToSdr12() override; + virtual Result SetSpeedMode(SpeedMode speed_mode) override; + + virtual SpeedMode GetSpeedMode() const override { + return this->current_speed_mode; + } + + virtual void SetPowerSaving(bool en) override; + virtual void EnableDeviceClock() override; + + virtual Result IssueCommand(const Command *command, TransferData *xfer_data, u32 *out_num_transferred_blocks) override; + virtual Result IssueStopTransmissionCommand(u32 *out_response) override; + + virtual bool IsSupportedTuning() const override { + return true; + } + + virtual Result Tuning(SpeedMode speed_mode, u32 command_index) override; + virtual void SaveTuningStatusForHs400() override; + + virtual Result GetInternalStatus() const override { + return this->drive_strength_calibration_status; + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc1RegistersPhysicalAddress = UINT64_C(0x700B0000); + + class Sdmmc1Controller : public SdmmcController { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + + /* NOTE: This is a fascimile of pcv's Sdmmc1PowerController. */ + class PowerController { + NON_COPYABLE(PowerController); + NON_MOVEABLE(PowerController); + private: + BusPower current_bus_power; + private: + Result ControlVddioSdmmc1(BusPower bus_power); + void SetSdmmcIoMode(bool is_3_3V); + void ControlRailSdmmc1Io(bool is_power_on); + public: + PowerController(); + ~PowerController(); + + Result PowerOn(BusPower bus_power); + Result PowerOff(); + Result LowerBusPower(); + }; + private: + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + /* TODO: pinmux::PinmuxSession pinmux_session; */ + #endif + BusPower current_bus_power; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + bool is_pcv_control; + #endif + TYPED_STORAGE(PowerController) power_controller_storage; + PowerController *power_controller; + private: + Result PowerOnForRegisterControl(BusPower bus_power); + void PowerOffForRegisterControl(); + Result LowerBusPowerForRegisterControl(); + void SetSchmittTriggerForRegisterControl(BusPower bus_power); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + Result PowerOnForPcvControl(BusPower bus_power); + void PowerOffForPcvControl(); + Result LowerBusPowerForPcvControl(); + void SetSchmittTriggerForPcvControl(BusPower bus_power); + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc1; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 46; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return !IsSocMariko(); + } + + virtual void ClearPadParked() override { + /* Nothing is needed here. */ + } + + virtual Result PowerOn(BusPower bus_power) override; + virtual void PowerOff() override; + virtual Result LowerBusPower() override; + + virtual void SetSchmittTrigger(BusPower bus_power) override; + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xE; + } else { + return 0x2; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x4; + } + } + + virtual u8 GetVrefSelValue() const override { + if (IsSocMariko()) { + return 0x0; + } else { + return 0x7; + } + } + + virtual void SetSlewCodes() override { + if (IsSocMariko()) { + /* Do nothing. */ + } else { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Write the slew values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWR, 0x1), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_CLK_CFG_CAL_DRVDN_SLWF, 0x1)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL); + } + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Set the offsets. */ + if (IsSocMariko()) { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 6; + *out_auto_cal_pu_offset = 6; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } else { + switch (bus_power) { + case BusPower_1_8V: + *out_auto_cal_pd_offset = 0x7B; + *out_auto_cal_pu_offset = 0x7B; + break; + case BusPower_3_3V: + *out_auto_cal_pd_offset = 0x7D; + *out_auto_cal_pu_offset = 0; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drive code values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0x8; + drvup = 0x8; + } else { + switch (bus_power) { + case BusPower_1_8V: + drvdn = 0xF; + drvup = 0xB; + break; + case BusPower_3_3V: + drvdn = 0xC; + drvup = 0xC; + break; + case BusPower_Off: + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + /* Write the drv up/down values to APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVDN, drvdn), + APB_MISC_REG_BITS_VALUE(GP_SDMMC1_PAD_CFGPADCTRL_CFG2TMC_SDMMC1_PAD_CAL_DRVUP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + public: + Sdmmc1Controller() : SdmmcController(Sdmmc1RegistersPhysicalAddress) { + this->current_bus_power = BusPower_Off; + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + this->is_pcv_control = false; + #endif + this->power_controller = nullptr; + } + + virtual void Initialize() override; + virtual void Finalize() override; + + void InitializeForRegisterControl(); + void FinalizeForRegisterControl(); + + #if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void InitializeForPcvControl(); + void FinalizeForPcvControl(); + #endif + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + class Sdmmc2And4Controller : public SdmmcController { + protected: + virtual bool IsNeedPeriodicDriveStrengthCalibration() override { + return false; + } + + virtual Result PowerOn(BusPower bus_power) override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + AMS_UNUSED(bus_power); + return ResultSuccess(); + } + + virtual void PowerOff() override { + /* Power for SDMMC2/4 is assumed on, so we don't need to do anything. */ + } + + virtual Result LowerBusPower() override { + AMS_ABORT("Sdmmc2And4Controller cannot lower bus power\n"); + } + + virtual void SetSchmittTrigger(BusPower bus_power) override { + /* Do nothing. */ + AMS_UNUSED(bus_power); + } + + virtual u8 GetOutboundTapValue() const override { + if (IsSocMariko()) { + return 0xD; + } else { + return 0x8; + } + } + + virtual u8 GetDefaultInboundTapValue() const override { + if (IsSocMariko()) { + return 0xB; + } else { + return 0x0; + } + } + + virtual u8 GetVrefSelValue() const override { + return 0x7; + } + + virtual void SetSlewCodes() override { + /* Do nothing. */ + } + + virtual void GetAutoCalOffsets(u8 *out_auto_cal_pd_offset, u8 *out_auto_cal_pu_offset, BusPower bus_power) const override { + /* Ensure that we can write the offsets. */ + AMS_ABORT_UNLESS(out_auto_cal_pd_offset != nullptr); + AMS_ABORT_UNLESS(out_auto_cal_pu_offset != nullptr); + + /* Sdmmc2And4Controller only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Set the offsets. */ + *out_auto_cal_pd_offset = 5; + *out_auto_cal_pu_offset = 5; + } + public: + explicit Sdmmc2And4Controller(dd::PhysicalAddress registers_phys_addr) : SdmmcController(registers_phys_addr) { + /* ... */ + } + + virtual bool IsSupportedBusPower(BusPower bus_power) const override { + switch (bus_power) { + case BusPower_Off: return true; + case BusPower_1_8V: return true; + case BusPower_3_3V: return false; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + + virtual bool IsSupportedBusWidth(BusWidth bus_width) const override { + switch (bus_width) { + case BusWidth_1Bit: return true; + case BusWidth_4Bit: return true; + case BusWidth_8Bit: return true; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc2RegistersPhysicalAddress = UINT64_C(0x700B0200); + + class Sdmmc2Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + /* Nothing is needed here. */ + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc2; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 47; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + if (IsSocMariko()) { + /* Nothing is needed here. */ + } else { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC2_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_MISC2PMC_EMMC2_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC2 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + if (IsSocMariko()) { + /* Write the drv up/down values to APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVDN, 0xA), + APB_MISC_REG_BITS_VALUE(GP_SDMMC2_PAD_CFGPADCTRL_CFG2TMC_SDMMC2_PAD_CAL_DRVUP, 0xA)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_SDMMC2_PAD_CFGPADCTRL); + } else { + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVDN_COMP, 0x10), + APB_MISC_REG_BITS_VALUE(GP_EMMC2_PAD_CFGPADCTRL_CFG2TMC_EMMC2_PAD_DRVUP_COMP, 0x10)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC2_PAD_CFGPADCTRL); + } + } + public: + Sdmmc2Controller() : Sdmmc2And4Controller(Sdmmc2RegistersPhysicalAddress) { + /* ... */ + } + }; + + constexpr inline dd::PhysicalAddress Sdmmc4RegistersPhysicalAddress = UINT64_C(0x700B0600); + + class Sdmmc4Controller : public Sdmmc2And4Controller { + private: + #if defined(AMS_SDMMC_USE_OS_EVENTS) + static constinit inline os::InterruptEventType s_interrupt_event{}; + #endif + protected: + virtual void SetPad() override { + if (IsSocMariko()) { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Enable Schmitt Trigger in emmc4 iobrick. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_ENUM(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_E_SCH, ENABLE)); + + /* Clear CMD_PULLU, CLK_PULLD, DQS_PULLD. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CMD_PUPD_PULLU, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_CLK_PUPD_PULLD, 0), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_PUPD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DQS_PUPD_PULLD, 0)); + + /* Read again to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_PUPD_CFGPADCTRL); + } else { + /* On Erista, we can just leave the reset value intact. */ + } + } + + virtual ClockResetController::Module GetClockResetModule() const override { + return ClockResetController::Module_Sdmmc4; + } + + #if defined(AMS_SDMMC_USE_OS_EVENTS) + virtual int GetInterruptNumber() const override { + return 63; + } + + virtual os::InterruptEventType *GetInterruptEvent() const override { + return std::addressof(s_interrupt_event); + } + #endif + + virtual void ClearPadParked() override { + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Clear all MISC2PMC_EMMC4_*_PARK bits. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_MISC2PMC_EMMC4_ALL_PARK, 0)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + + virtual void SetDriveStrengthToDefaultValues(BusPower bus_power) override { + /* SDMMC4 only supports 1.8v. */ + AMS_ABORT_UNLESS(bus_power == BusPower_1_8V); + + /* Ensure that we can control registers. */ + SdHostStandardController::EnsureControl(); + + /* Get the apb registers address. */ + const uintptr_t apb_address = dd::QueryIoMapping(ApbMiscRegistersPhysicalAddress, ApbMiscRegistersSize); + + /* Determine the drv up/down values. */ + u8 drvdn, drvup; + if (IsSocMariko()) { + drvdn = 0xA; + drvup = 0xA; + } else { + drvdn = 0x10; + drvup = 0x10; + } + + /* Write the drv up/down values to APB_MISC_GP_EMMC4_PAD_CFGPADCTRL. */ + reg::ReadWrite(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL, APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVDN_COMP, drvdn), + APB_MISC_REG_BITS_VALUE(GP_EMMC4_PAD_CFGPADCTRL_CFG2TMC_EMMC4_PAD_DRVUP_COMP, drvup)); + + /* Read to be sure our config takes. */ + reg::Read(apb_address + APB_MISC_GP_EMMC4_PAD_CFGPADCTRL); + } + public: + Sdmmc4Controller() : Sdmmc2And4Controller(Sdmmc4RegistersPhysicalAddress) { + /* ... */ + } + }; + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp new file mode 100644 index 000000000..9d3b9432a --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_select_sdmmc_controller.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +#if defined(ATMOSPHERE_BOARD_NINTENDO_NX) + + #include "sdmmc_sdmmc_controller.board.nintendo_nx.hpp" + + namespace ams::sdmmc::impl { + + using SdmmcControllerForPortSdCard0 = Sdmmc1Controller; + using SdmmcControllerForPortGcAsic0 = Sdmmc2Controller; + using SdmmcControllerForPortMmc0 = Sdmmc4Controller; + + } + +#else + #error "Unknown board for ams::sdmmc::SdmmcController" +#endif diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp new file mode 100644 index 000000000..ae9033202 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "sdmmc_timer.hpp" + +namespace ams::sdmmc::impl { + + namespace { + + #if defined(AMS_SDMMC_USE_OS_TIMER) + void SpinWaitMicroSeconds(u32 us) { + const os::Tick timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMicroSeconds(us)) + os::Tick(1); + while (true) { + if (os::GetSystemTick() > timeout_tick) { + break; + } + } + } + + ALWAYS_INLINE void DataSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) + __asm__ __volatile__("dsb sy" ::: "memory"); + #elif defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("dsb" ::: "memory"); + #else + #error "Unknown architecture for DataSynchronizationBarrier" + #endif + } + + ALWAYS_INLINE void InstructionSynchronizationBarrier() { + #if defined(ATMOSPHERE_ARCH_ARM64) || defined(ATMOSPHERE_ARCH_ARM) + __asm__ __volatile__("isb" ::: "memory"); + #else + #error "Unknown architecture for InstructionSynchronizationBarrier" + #endif + } + #endif + + } + + void WaitMicroSeconds(u32 us) { + #if defined(AMS_SDMMC_USE_OS_TIMER) + /* Ensure that nothing is reordered before we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + + /* If the time is small, spinloop, otherwise pend ourselves. */ + if (us < 100) { + SpinWaitMicroSeconds(us); + } else { + os::SleepThread(TimeSpan::FromMicroSeconds(us)); + } + + /* Ensure that nothing is reordered after we wait. */ + DataSynchronizationBarrier(); + InstructionSynchronizationBarrier(); + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + util::WaitMicroSeconds(us); + #else + #error "Unknown context for ams::sdmmc::impl::WaitMicroSeconds" + #endif + } + + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz) { + AMS_ABORT_UNLESS(clock_frequency_khz > 0); + WaitMicroSeconds(util::DivideUp(1000 * num_clocks, clock_frequency_khz)); + } + +} diff --git a/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp new file mode 100644 index 000000000..5d8149591 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/impl/sdmmc_timer.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::sdmmc::impl { + + void WaitMicroSeconds(u32 us); + void WaitClocks(u32 num_clocks, u32 clock_frequency_khz); + + #if defined(AMS_SDMMC_USE_OS_TIMER) + class ManualTimer { + private: + os::Tick timeout_tick; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_tick = os::GetSystemTick() + os::ConvertToTick(TimeSpan::FromMilliSeconds(ms)); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = os::GetSystemTick() > this->timeout_tick; + return true; + } + }; + #elif defined(AMS_SDMMC_USE_UTIL_TIMER) + class ManualTimer { + private: + u32 timeout_us; + bool is_timed_out; + public: + explicit ManualTimer(u32 ms) { + this->timeout_us = util::GetMicroSeconds() + (ms * 1000); + this->is_timed_out = false; + } + + bool Update() { + if (this->is_timed_out) { + return false; + } + + this->is_timed_out = util::GetMicroSeconds() > this->timeout_us; + return true; + } + }; + #else + #error "Unknown context for ams::sdmmc::ManualTimer" + #endif + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_common.cpp b/libraries/libvapours/source/sdmmc/sdmmc_common.cpp new file mode 100644 index 000000000..598e048bf --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_common.cpp @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_i_host_controller.hpp" +#include "impl/sdmmc_i_device_accessor.hpp" +#include "impl/sdmmc_clock_reset_controller.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::IHostController *GetHostController(Port port) { + /* Get the controller. */ + impl::IHostController *host_controller = nullptr; + switch (port) { + case Port_Mmc0: host_controller = impl::GetHostControllerOfPortMmc0(); break; + case Port_SdCard0: host_controller = impl::GetHostControllerOfPortSdCard0(); break; + case Port_GcAsic0: host_controller = impl::GetHostControllerOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(host_controller != nullptr); + return host_controller; + } + + impl::IDeviceAccessor *GetDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::IDeviceAccessor *device_accessor = nullptr; + switch (port) { + case Port_Mmc0: device_accessor = impl::GetDeviceAccessorOfPortMmc0(); break; + case Port_SdCard0: device_accessor = impl::GetDeviceAccessorOfPortSdCard0(); break; + case Port_GcAsic0: device_accessor = impl::GetDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(device_accessor != nullptr); + return device_accessor; + } + + } + + + + void Initialize(Port port) { + return GetDeviceAccessor(port)->Initialize(); + } + + void Finalize(Port port) { + return GetDeviceAccessor(port)->Finalize(); + } + +#if defined(AMS_SDMMC_USE_PCV_CLOCK_RESET_CONTROL) + void SwitchToPcvClockResetControl() { + return impl::ClockResetController::SwitchToPcvControl(); + } +#endif + +#if defined(AMS_SDMMC_USE_DEVICE_VIRTUAL_ADDRESS) + void RegisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->RegisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } + + void UnregisterDeviceVirtualAddress(Port port, uintptr_t buffer, size_t buffer_size, ams::dd::DeviceVirtualAddress buffer_device_virtual_address) { + return GetDeviceAccessor(port)->UnregisterDeviceVirtualAddress(buffer, buffer_size, buffer_device_virtual_address); + } +#endif + + void ChangeCheckTransferInterval(Port port, u32 ms) { + return GetHostController(port)->ChangeCheckTransferInterval(ms); + } + void SetDefaultCheckTransferInterval(Port port) { + return GetHostController(port)->SetDefaultCheckTransferInterval(); + } + + Result Activate(Port port) { + return GetDeviceAccessor(port)->Activate(); + } + + void Deactivate(Port port) { + return GetDeviceAccessor(port)->Deactivate(); + } + + Result Read(void *dst, size_t dst_size, Port port, u32 sector_index, u32 num_sectors) { + return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, dst, dst_size, true); + } + + Result Write(Port port, u32 sector_index, u32 num_sectors, const void *src, size_t src_size) { + return GetDeviceAccessor(port)->ReadWrite(sector_index, num_sectors, const_cast(src), src_size, false); + } + + Result CheckConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + + Result GetDeviceSpeedMode(SpeedMode *out, Port port) { + return GetDeviceAccessor(port)->GetSpeedMode(out); + } + + Result GetDeviceMemoryCapacity(u32 *out_num_sectors, Port port) { + return GetDeviceAccessor(port)->GetMemoryCapacity(out_num_sectors); + } + + Result GetDeviceStatus(u32 *out_device_status, Port port) { + return GetDeviceAccessor(port)->GetDeviceStatus(out_device_status); + } + + Result GetDeviceCid(void *out, size_t out_size, Port port) { + return GetDeviceAccessor(port)->GetCid(out, out_size); + } + + Result GetDeviceCsd(void *out, size_t out_size, Port port) { + return GetDeviceAccessor(port)->GetCsd(out, out_size); + } + + void GetAndClearErrorInfo(ErrorInfo *out_error_info, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size, Port port) { + return GetDeviceAccessor(port)->GetAndClearErrorInfo(out_error_info, out_log_size, out_log_buffer, log_buffer_size); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp b/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp new file mode 100644 index 000000000..2071fd315 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_gc_asic.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_gc_asic_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::GcAsicDeviceAccessor *GetGcAsicDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::GcAsicDeviceAccessor *gc_asic_device_accessor = nullptr; + switch (port) { + case Port_GcAsic0: gc_asic_device_accessor = impl::GetGcAsicDeviceAccessorOfPortGcAsic0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(gc_asic_device_accessor != nullptr); + return gc_asic_device_accessor; + } + + } + + void PutGcAsicToSleep(Port port) { + return GetGcAsicDeviceAccessor(port)->PutGcAsicToSleep(); + } + + Result AwakenGcAsic(Port port) { + return GetGcAsicDeviceAccessor(port)->AwakenGcAsic(); + } + + Result WriteGcAsicOperation(Port port, const void *op_buf, size_t op_buf_size) { + return GetGcAsicDeviceAccessor(port)->WriteGcAsicOperation(op_buf, op_buf_size); + } + + Result FinishGcAsicOperation(Port port) { + return GetGcAsicDeviceAccessor(port)->FinishGcAsicOperation(); + } + + Result AbortGcAsicOperation(Port port) { + return GetGcAsicDeviceAccessor(port)->AbortGcAsicOperation(); + } + + Result SleepGcAsic(Port port) { + return GetGcAsicDeviceAccessor(port)->SleepGcAsic(); + } + + Result UpdateGcAsicKey(Port port) { + return GetGcAsicDeviceAccessor(port)->UpdateGcAsicKey(); + } + + void SignalGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->SignalGcRemovedEvent(); + } + + void ClearGcRemovedEvent(Port port) { + return GetGcAsicDeviceAccessor(port)->ClearGcRemovedEvent(); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp b/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp new file mode 100644 index 000000000..76fc5716a --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_mmc.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_mmc_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::MmcDeviceAccessor *GetMmcDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::MmcDeviceAccessor *mmc_device_accessor = nullptr; + switch (port) { + case Port_Mmc0: mmc_device_accessor = impl::GetMmcDeviceAccessorOfPortMmc0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(mmc_device_accessor != nullptr); + return mmc_device_accessor; + } + + } + + void SetMmcWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetMmcDeviceAccessor(port)->SetMmcWorkBuffer(buffer, buffer_size); + } + + void PutMmcToSleep(Port port) { + return GetMmcDeviceAccessor(port)->PutMmcToSleep(); + } + + void AwakenMmc(Port port) { + return GetMmcDeviceAccessor(port)->AwakenMmc(); + } + + Result SelectMmcPartition(Port port, MmcPartition mmc_partition) { + return GetMmcDeviceAccessor(port)->SelectMmcPartition(mmc_partition); + } + + Result EraseMmc(Port port) { + return GetMmcDeviceAccessor(port)->EraseMmc(); + } + + Result GetMmcBootPartitionCapacity(u32 *out_num_sectors, Port port) { + return GetMmcDeviceAccessor(port)->GetMmcBootPartitionCapacity(out_num_sectors); + } + + Result GetMmcExtendedCsd(void *out_buffer, size_t buffer_size, Port port) { + return GetMmcDeviceAccessor(port)->GetMmcExtendedCsd(out_buffer, buffer_size); + } + + Result CheckMmcConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetMmcDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + +} diff --git a/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp b/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp new file mode 100644 index 000000000..c10a38d95 --- /dev/null +++ b/libraries/libvapours/source/sdmmc/sdmmc_sd_card.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#if defined(ATMOSPHERE_IS_STRATOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_MESOSPHERE) +#include +#elif defined(ATMOSPHERE_IS_EXOSPHERE) +#include +#else +#include +#endif +#include "impl/sdmmc_sd_card_device_accessor.hpp" +#include "impl/sdmmc_port_mmc0.hpp" +#include "impl/sdmmc_port_sd_card0.hpp" +#include "impl/sdmmc_port_gc_asic0.hpp" + +namespace ams::sdmmc { + + namespace { + + impl::SdCardDeviceAccessor *GetSdCardDeviceAccessor(Port port) { + /* Get the accessor. */ + impl::SdCardDeviceAccessor *sd_card_device_accessor = nullptr; + switch (port) { + case Port_SdCard0: sd_card_device_accessor = impl::GetSdCardDeviceAccessorOfPortSdCard0(); break; + AMS_UNREACHABLE_DEFAULT_CASE(); + } + + /* Ensure it's valid */ + AMS_ABORT_UNLESS(sd_card_device_accessor != nullptr); + return sd_card_device_accessor; + } + + } + + void SetSdCardWorkBuffer(Port port, void *buffer, size_t buffer_size) { + return GetSdCardDeviceAccessor(port)->SetSdCardWorkBuffer(buffer, buffer_size); + } + + void PutSdCardToSleep(Port port) { + return GetSdCardDeviceAccessor(port)->PutSdCardToSleep(); + } + + void AwakenSdCard(Port port) { + return GetSdCardDeviceAccessor(port)->AwakenSdCard(); + } + + Result GetSdCardProtectedAreaCapacity(u32 *out_num_sectors, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardProtectedAreaCapacity(out_num_sectors); + } + + Result GetSdCardScr(void *dst, size_t dst_size, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardScr(dst, dst_size); + } + + Result GetSdCardSwitchFunctionStatus(void *dst, size_t dst_size, Port port, SdCardSwitchFunction switch_function) { + return GetSdCardDeviceAccessor(port)->GetSdCardSwitchFunctionStatus(dst, dst_size, switch_function); + } + + Result GetSdCardCurrentConsumption(u16 *out_current_consumption, Port port, SpeedMode speed_mode) { + return GetSdCardDeviceAccessor(port)->GetSdCardCurrentConsumption(out_current_consumption, speed_mode); + } + + Result GetSdCardSdStatus(void *dst, size_t dst_size, Port port) { + return GetSdCardDeviceAccessor(port)->GetSdCardSdStatus(dst, dst_size); + } + + Result CheckSdCardConnection(SpeedMode *out_speed_mode, BusWidth *out_bus_width, Port port) { + return GetSdCardDeviceAccessor(port)->CheckConnection(out_speed_mode, out_bus_width); + } + + bool IsSdCardInserted(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardInserted(); + } + + bool IsSdCardRemoved(Port port) { + return GetSdCardDeviceAccessor(port)->IsSdCardRemoved(); + } + + + void RegisterSdCardDetectionEventCallback(Port port, DeviceDetectionEventCallback callback, void *arg) { + return GetSdCardDeviceAccessor(port)->RegisterSdCardDetectionEventCallback(callback, arg); + } + + void UnregisterSdCardDetectionEventCallback(Port port) { + return GetSdCardDeviceAccessor(port)->UnregisterSdCardDetectionEventCallback(); + } + +} + diff --git a/libraries/libvapours/source/util/util_format_string.cpp b/libraries/libvapours/source/util/util_format_string.cpp new file mode 100644 index 000000000..b6510e773 --- /dev/null +++ b/libraries/libvapours/source/util/util_format_string.cpp @@ -0,0 +1,434 @@ +/* + * 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 . + */ +#include + +namespace ams::util { + + #pragma GCC push_options + #pragma GCC optimize ("-Os") + + namespace { + + /* Useful definitions for our VSNPrintf implementation. */ + enum FormatSpecifierFlag : u32 { + FormatSpecifierFlag_None = 0, + FormatSpecifierFlag_EmptySign = (1 << 0), + FormatSpecifierFlag_ForceSign = (1 << 1), + FormatSpecifierFlag_Hash = (1 << 2), + FormatSpecifierFlag_LeftJustify = (1 << 3), + FormatSpecifierFlag_ZeroPad = (1 << 4), + FormatSpecifierFlag_Char = (1 << 5), + FormatSpecifierFlag_Short = (1 << 6), + FormatSpecifierFlag_Long = (1 << 7), + FormatSpecifierFlag_LongLong = (1 << 8), + FormatSpecifierFlag_Uppercase = (1 << 9), + FormatSpecifierFlag_HasPrecision = (1 << 10), + }; + + using FormatSpecifierFlagStorage = std::underlying_type::type; + + constexpr ALWAYS_INLINE bool IsDigit(char c) { + return '0' <= c && c <= '9'; + } + + constexpr ALWAYS_INLINE u32 ParseU32(const char *&str) { + u32 value = 0; + do { + value = (value * 10) + static_cast(*(str++) - '0'); + } while (IsDigit(*str)); + return value; + } + + constexpr ALWAYS_INLINE size_t Strnlen(const char *str, size_t max) { + const char *cur = str; + while (*cur && max--) { + cur++; + } + return static_cast(cur - str); + } + + int TVSNPrintfImpl(char * const dst, const size_t dst_size, const char *format, ::std::va_list vl) { + size_t dst_index = 0; + + auto WriteCharacter = [dst, dst_size, &dst_index](char c) ALWAYS_INLINE_LAMBDA { + if (const size_t i = (dst_index++); i < dst_size) { + dst[i] = c; + } + }; + + /* Loop over every character in the string, looking for format specifiers. */ + while (*format) { + if (const char c = *(format++); c != '%') { + WriteCharacter(c); + continue; + } + + /* We have to parse a format specifier. */ + /* Start by parsing flags. */ + FormatSpecifierFlagStorage flags = FormatSpecifierFlag_None; + auto SetFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags |= f; }; + auto ClearFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { flags &= ~f; }; + auto HasFlag = [&flags](FormatSpecifierFlag f) ALWAYS_INLINE_LAMBDA { return (flags & f) != 0; }; + { + bool parsed_flags = false; + while (!parsed_flags) { + switch (*format) { + case ' ': SetFlag(FormatSpecifierFlag_EmptySign); format++; break; + case '+': SetFlag(FormatSpecifierFlag_ForceSign); format++; break; + case '#': SetFlag(FormatSpecifierFlag_Hash); format++; break; + case '-': SetFlag(FormatSpecifierFlag_LeftJustify); format++; break; + case '0': SetFlag(FormatSpecifierFlag_ZeroPad); format++; break; + default: + parsed_flags = true; + break; + } + } + } + + /* Next, parse width. */ + u32 width = 0; + if (IsDigit(*format)) { + /* Integer width. */ + width = ParseU32(format); + } else if (*format == '*') { + /* Dynamic width. */ + const int _width = va_arg(vl, int); + if (_width >= 0) { + width = static_cast(_width); + } else { + SetFlag(FormatSpecifierFlag_LeftJustify); + width = static_cast(-_width); + } + format++; + } + + /* Next, parse precision if present. */ + u32 precision = 0; + if (*format == '.') { + SetFlag(FormatSpecifierFlag_HasPrecision); + format++; + + if (IsDigit(*format)) { + /* Integer precision. */ + precision = ParseU32(format); + } else if (*format == '*') { + /* Dynamic precision. */ + const int _precision = va_arg(vl, int); + if (_precision > 0) { + precision = static_cast(_precision); + } + format++; + } + } + + /* Parse length. */ + constexpr bool SizeIsLong = sizeof(size_t) == sizeof(long); + constexpr bool IntMaxIsLong = sizeof(intmax_t) == sizeof(long); + constexpr bool PtrDiffIsLong = sizeof(ptrdiff_t) == sizeof(long); + switch (*format) { + case 'z': + SetFlag(SizeIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 'j': + SetFlag(IntMaxIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 't': + SetFlag(PtrDiffIsLong ? FormatSpecifierFlag_Long : FormatSpecifierFlag_LongLong); + format++; + break; + case 'h': + SetFlag(FormatSpecifierFlag_Short); + format++; + if (*format == 'h') { + SetFlag(FormatSpecifierFlag_Char); + format++; + } + break; + case 'l': + SetFlag(FormatSpecifierFlag_Long); + format++; + if (*format == 'l') { + SetFlag(FormatSpecifierFlag_LongLong); + format++; + } + break; + default: + break; + } + + const char specifier = *(format++); + switch (specifier) { + case 'p': + if constexpr (sizeof(uintptr_t) == sizeof(long long)) { + SetFlag(FormatSpecifierFlag_LongLong); + } else { + SetFlag(FormatSpecifierFlag_Long); + } + SetFlag(FormatSpecifierFlag_Hash); + [[fallthrough]]; + case 'd': + case 'i': + case 'u': + case 'b': + case 'o': + case 'x': + case 'X': + { + /* Determine the base to print with. */ + u32 base; + switch (specifier) { + case 'b': + base = 2; + break; + case 'o': + base = 8; + break; + case 'X': + SetFlag(FormatSpecifierFlag_Uppercase); + [[fallthrough]]; + case 'p': + case 'x': + base = 16; + break; + default: + base = 10; + ClearFlag(FormatSpecifierFlag_Hash); + break; + } + + /* Precision implies no zero-padding. */ + if (HasFlag(FormatSpecifierFlag_HasPrecision)) { + ClearFlag(FormatSpecifierFlag_ZeroPad); + } + + /* Unsigned types don't get signs. */ + const bool is_unsigned = base != 10 || specifier == 'u'; + if (is_unsigned) { + ClearFlag(FormatSpecifierFlag_EmptySign); + ClearFlag(FormatSpecifierFlag_ForceSign); + } + + auto PrintInteger = [&](bool negative, uintmax_t value) { + constexpr size_t BufferSize = 64; /* Binary digits for 64-bit numbers may use 64 digits. */ + char buf[BufferSize]; + size_t len = 0; + + /* No hash flag for zero. */ + if (value == 0) { + ClearFlag(FormatSpecifierFlag_Hash); + } + + if (!HasFlag(FormatSpecifierFlag_HasPrecision) || value != 0) { + do { + const char digit = static_cast(value % base); + buf[len++] = (digit < 10) ? ('0' + digit) : ((HasFlag(FormatSpecifierFlag_Uppercase) ? 'A' : 'a') + digit - 10); + value /= base; + } while (value); + } + + /* Determine our prefix length. */ + size_t prefix_len = 0; + const bool has_sign = negative || HasFlag(FormatSpecifierFlag_ForceSign) || HasFlag(FormatSpecifierFlag_EmptySign); + if (has_sign) { + prefix_len++; + } + if (HasFlag(FormatSpecifierFlag_Hash)) { + prefix_len += (base != 8) ? 2 : 1; + } + + /* Determine zero-padding count. */ + size_t num_zeroes = (len < precision) ? precision - len : 0; + if (!HasFlag(FormatSpecifierFlag_LeftJustify) && HasFlag(FormatSpecifierFlag_ZeroPad)) { + num_zeroes = (len + prefix_len < width) ? width - len - prefix_len : 0; + } + + /* Print out left padding. */ + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + for (size_t i = len + prefix_len + num_zeroes; i < static_cast(width); i++) { + WriteCharacter(' '); + } + } + + /* Print out sign. */ + if (negative) { + WriteCharacter('-'); + } else if (HasFlag(FormatSpecifierFlag_ForceSign)) { + WriteCharacter('+'); + } else if (HasFlag(FormatSpecifierFlag_EmptySign)) { + WriteCharacter(' '); + } + + /* Print out base prefix. */ + if (HasFlag(FormatSpecifierFlag_Hash)) { + WriteCharacter('0'); + if (base == 2) { + WriteCharacter('b'); + } else if (base == 16) { + WriteCharacter('x'); + } + } + + /* Print out zeroes. */ + for (size_t i = 0; i < num_zeroes; i++) { + WriteCharacter('0'); + } + + /* Print out digits. */ + for (size_t i = 0; i < len; i++) { + WriteCharacter(buf[len - 1 - i]); + } + + /* Print out right padding. */ + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + for (size_t i = len + prefix_len + num_zeroes; i < static_cast(width); i++) { + WriteCharacter(' '); + } + } + }; + + /* Output the integer. */ + if (is_unsigned) { + uintmax_t n = 0; + if (HasFlag(FormatSpecifierFlag_LongLong)) { + n = static_cast(va_arg(vl, unsigned long long)); + } else if (HasFlag(FormatSpecifierFlag_Long)) { + n = static_cast(va_arg(vl, unsigned long)); + } else if (HasFlag(FormatSpecifierFlag_Char)) { + n = static_cast(va_arg(vl, unsigned int)); + } else if (HasFlag(FormatSpecifierFlag_Short)) { + n = static_cast(va_arg(vl, unsigned int)); + } else { + n = static_cast(va_arg(vl, unsigned int)); + } + if (specifier == 'p' && n == 0) { + WriteCharacter('('); + WriteCharacter('n'); + WriteCharacter('i'); + WriteCharacter('l'); + WriteCharacter(')'); + } else { + PrintInteger(false, n); + } + } else { + intmax_t n = 0; + if (HasFlag(FormatSpecifierFlag_LongLong)) { + n = static_cast(va_arg(vl, signed long long)); + } else if (HasFlag(FormatSpecifierFlag_Long)) { + n = static_cast(va_arg(vl, signed long)); + } else if (HasFlag(FormatSpecifierFlag_Char)) { + n = static_cast(va_arg(vl, signed int)); + } else if (HasFlag(FormatSpecifierFlag_Short)) { + n = static_cast(va_arg(vl, signed int)); + } else { + n = static_cast(va_arg(vl, signed int)); + } + const bool negative = n < 0; + const uintmax_t u = (negative) ? static_cast(-n) : static_cast(n); + PrintInteger(negative, u); + } + } + break; + case 'c': + { + size_t len = 1; + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + WriteCharacter(static_cast(va_arg(vl, int))); + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + } + break; + case 's': + { + const char *str = va_arg(vl, char *); + if (str == nullptr) { + str = "(null)"; + } + + size_t len = Strnlen(str, precision > 0 ? precision : std::numeric_limits::max()); + if (HasFlag(FormatSpecifierFlag_HasPrecision)) { + len = (len < precision) ? len : precision; + } + if (!HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + while (*str && (!HasFlag(FormatSpecifierFlag_HasPrecision) || (precision--) != 0)) { + WriteCharacter(*(str++)); + } + if (HasFlag(FormatSpecifierFlag_LeftJustify)) { + while (len++ < width) { + WriteCharacter(' '); + } + } + } + break; + case '%': + default: + WriteCharacter(specifier); + break; + } + } + + /* Ensure null termination. */ + WriteCharacter('\0'); + dst[dst_size - 1] = '\0'; + + /* Return number of characters that would have been printed sans the null terminator. */ + return static_cast(dst_index) - 1; + } + + } + + #pragma GCC pop_options + + int TVSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl) { + return TVSNPrintfImpl(dst, dst_size, fmt, vl); + } + + int TSNPrintf(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const int len = TVSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + return len; + } + + int VSNPrintf(char *dst, size_t dst_size, const char *fmt, std::va_list vl) { + /* TODO: floating point support? */ + return TVSNPrintfImpl(dst, dst_size, fmt, vl); + } + + int SNPrintf(char *dst, size_t dst_size, const char *fmt, ...) { + std::va_list vl; + va_start(vl, fmt); + const int len = VSNPrintf(dst, dst_size, fmt, vl); + va_end(vl); + + return len; + } + +} diff --git a/stratosphere/boot/source/boot_charger_driver.cpp b/stratosphere/boot/source/boot_charger_driver.cpp index 695d1be9a..7bd8e87da 100644 --- a/stratosphere/boot/source/boot_charger_driver.cpp +++ b/stratosphere/boot/source/boot_charger_driver.cpp @@ -60,7 +60,7 @@ namespace ams::boot { } Result ChargerDriver::SetChargeEnabled(bool enabled) { - gpio::SetValue(GpioPadName_Bq24193Charger, enabled ? GpioValue_Low : GpioValue_High); + boot::gpio::SetValue(GpioPadName_Bq24193Charger, enabled ? GpioValue_Low : GpioValue_High); return this->SetChargerConfiguration(bq24193::ChargerConfiguration_ChargeBattery); } diff --git a/stratosphere/boot/source/boot_charger_driver.hpp b/stratosphere/boot/source/boot_charger_driver.hpp index 0e2421f22..54acb6f3c 100644 --- a/stratosphere/boot/source/boot_charger_driver.hpp +++ b/stratosphere/boot/source/boot_charger_driver.hpp @@ -31,8 +31,8 @@ namespace ams::boot { i2c::driver::Initialize(); i2c::driver::OpenSession(&this->i2c_session, I2cDevice_Bq24193); - gpio::Configure(GpioPadName_Bq24193Charger); - gpio::SetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output); + boot::gpio::Configure(GpioPadName_Bq24193Charger); + boot::gpio::SetDirection(GpioPadName_Bq24193Charger, GpioDirection_Output); } ~ChargerDriver() { diff --git a/stratosphere/boot/source/boot_clock_initial_configuration.cpp b/stratosphere/boot/source/boot_clock_initial_configuration.cpp index 4dd90a83f..a87de78aa 100644 --- a/stratosphere/boot/source/boot_clock_initial_configuration.cpp +++ b/stratosphere/boot/source/boot_clock_initial_configuration.cpp @@ -16,14 +16,12 @@ #include #include "boot_clock_initial_configuration.hpp" #include "boot_pmc_wrapper.hpp" -#include "boot_registers_pmc.hpp" namespace ams::boot { namespace { /* Convenience definitions. */ - constexpr u32 PmcClkOutCntrl = PmcBase + APBDEV_PMC_CLK_OUT_CNTRL; constexpr u32 InitialClockOutMask1x = 0x00C4; constexpr u32 InitialClockOutMask6x = 0xC4C4; @@ -32,7 +30,7 @@ namespace ams::boot { void SetInitialClockConfiguration() { /* Write mask to APBDEV_PMC_PWR_DET, then clear APBDEV_PMC_PWR_DET_VAL. */ const u32 mask = hos::GetVersion() >= hos::Version_6_0_0 ? InitialClockOutMask6x : InitialClockOutMask1x; - WritePmcRegister(PmcClkOutCntrl, mask, mask); + WritePmcRegister(PmcBase + APBDEV_PMC_CLK_OUT_CNTRL, mask, mask); } } diff --git a/stratosphere/boot/source/boot_display.cpp b/stratosphere/boot/source/boot_display.cpp index 8d6a78a83..2f44991ad 100644 --- a/stratosphere/boot/source/boot_display.cpp +++ b/stratosphere/boot/source/boot_display.cpp @@ -18,11 +18,7 @@ #include "boot_i2c_utils.hpp" #include "boot_pmc_wrapper.hpp" -#include "boot_registers_clkrst.hpp" #include "boot_registers_di.hpp" -#include "boot_registers_gpio.hpp" -#include "boot_registers_pinmux.hpp" -#include "boot_registers_pmc.hpp" namespace ams::boot { @@ -61,6 +57,14 @@ namespace ams::boot { constexpr s32 DsiWaitForCommandCompletionMilliSeconds = 5; constexpr s32 DsiWaitForHostControlMilliSecondsMax = 150; + constexpr size_t GPIO_PORT3_CNF_0 = 0x200; + constexpr size_t GPIO_PORT3_OE_0 = 0x210; + constexpr size_t GPIO_PORT3_OUT_0 = 0x220; + + constexpr size_t GPIO_PORT6_CNF_1 = 0x504; + constexpr size_t GPIO_PORT6_OE_1 = 0x514; + constexpr size_t GPIO_PORT6_OUT_1 = 0x524; + /* Types. */ /* Globals. */ @@ -216,15 +220,15 @@ namespace ams::boot { reg::Write(g_clk_rst_regs + CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP, 0xA); /* DPD idle. */ - WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD_REQ, 0x40000000); + WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD_REQ, 0x40000000); WritePmcRegister(PmcBase + APBDEV_PMC_IO_DPD2_REQ, 0x40000000); /* Configure LCD pinmux tristate + passthrough. */ - reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_EN, PINMUX_TRISTATE); - reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_NFC_INT, PINMUX_TRISTATE); - reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, PINMUX_TRISTATE); - reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_EN, PINMUX_TRISTATE); - reg::ClearBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_RST, PINMUX_TRISTATE); + reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_NFC_INT, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_EN, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + reg::ClearBits(g_apb_misc_regs + PINMUX_AUX_LCD_RST, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); /* Configure LCD power, VDD. */ reg::SetBits(g_gpio_regs + GPIO_PORT3_CNF_0, 0x3); @@ -493,8 +497,8 @@ namespace ams::boot { /* Final LCD config for PWM */ reg::ClearBits(g_gpio_regs + GPIO_PORT6_CNF_1, 0x1); - reg::SetBits(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, PINMUX_TRISTATE); - reg::ReadWrite(g_apb_misc_regs + 0x3000 + PINMUX_AUX_LCD_BL_PWM, 1, 0x3); + reg::SetBits(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, reg::EncodeMask(PINMUX_REG_BITS_MASK(AUX_TRISTATE))); + reg::ReadWrite(g_apb_misc_regs + PINMUX_AUX_LCD_BL_PWM, 1, 0x3); /* Unmap framebuffer from DC virtual address space. */ FinalizeFrameBuffer(); diff --git a/stratosphere/boot/source/boot_fan_enable.cpp b/stratosphere/boot/source/boot_fan_enable.cpp index 26d0cf8a4..c0c2b947c 100644 --- a/stratosphere/boot/source/boot_fan_enable.cpp +++ b/stratosphere/boot/source/boot_fan_enable.cpp @@ -29,9 +29,9 @@ namespace ams::boot { void SetFanEnabled() { if (spl::GetHardwareType() == spl::HardwareType::Copper) { - gpio::Configure(GpioPadName_FanEnable); - gpio::SetDirection(GpioPadName_FanEnable, GpioDirection_Output); - gpio::SetValue(GpioPadName_FanEnable, GpioValue_High); + boot::gpio::Configure(GpioPadName_FanEnable); + boot::gpio::SetDirection(GpioPadName_FanEnable, GpioDirection_Output); + boot::gpio::SetValue(GpioPadName_FanEnable, GpioValue_High); } } diff --git a/stratosphere/boot/source/boot_main.cpp b/stratosphere/boot/source/boot_main.cpp index 07e6edc3c..b04f3d2a2 100644 --- a/stratosphere/boot/source/boot_main.cpp +++ b/stratosphere/boot/source/boot_main.cpp @@ -123,7 +123,7 @@ int main(int argc, char **argv) boot::ChangeGpioVoltageTo1_8v(); /* Setup GPIO. */ - gpio::SetInitialConfiguration(); + boot::gpio::SetInitialConfiguration(); /* Check USB PLL/UTMIP clock. */ boot::CheckClock(); diff --git a/stratosphere/boot/source/boot_pmc_wrapper.cpp b/stratosphere/boot/source/boot_pmc_wrapper.cpp index 9003009d4..8feaedbe2 100644 --- a/stratosphere/boot/source/boot_pmc_wrapper.cpp +++ b/stratosphere/boot/source/boot_pmc_wrapper.cpp @@ -23,15 +23,15 @@ namespace ams::boot { /* Convenience definitions. */ constexpr u32 SmcFunctionId_AtmosphereReadWriteRegister = 0xF0000002; - constexpr u32 PmcPhysStart = 0x7000E400; - constexpr u32 PmcPhysEnd = 0x7000EFFF; + constexpr dd::PhysicalAddress PmcPhysStart = 0x7000E400; + constexpr dd::PhysicalAddress PmcPhysLast = 0x7000EFFF; /* Helpers. */ - bool IsValidPmcAddress(u32 phys_addr) { - return (phys_addr & 3) == 0 && PmcPhysStart <= phys_addr && phys_addr <= PmcPhysEnd; + bool IsValidPmcAddress(dd::PhysicalAddress phys_addr) { + return util::IsAligned(phys_addr, alignof(u32)) && PmcPhysStart <= phys_addr && phys_addr <= PmcPhysLast; } - inline u32 ReadWriteRegisterImpl(uintptr_t phys_addr, u32 value, u32 mask) { + inline u32 ReadWriteRegisterImpl(dd::PhysicalAddress phys_addr, u32 value, u32 mask) { u32 out_value; R_ABORT_UNLESS(spl::smc::ConvertResult(spl::smc::AtmosphereReadWriteRegister(phys_addr, mask, value, &out_value))); return out_value; @@ -39,12 +39,12 @@ namespace ams::boot { } - u32 ReadPmcRegister(u32 phys_addr) { + u32 ReadPmcRegister(dd::PhysicalAddress phys_addr) { AMS_ABORT_UNLESS(IsValidPmcAddress(phys_addr)); return ReadWriteRegisterImpl(phys_addr, 0, 0); } - void WritePmcRegister(u32 phys_addr, u32 value, u32 mask) { + void WritePmcRegister(dd::PhysicalAddress phys_addr, u32 value, u32 mask) { AMS_ABORT_UNLESS(IsValidPmcAddress(phys_addr)); ReadWriteRegisterImpl(phys_addr, value, mask); } diff --git a/stratosphere/boot/source/boot_pmc_wrapper.hpp b/stratosphere/boot/source/boot_pmc_wrapper.hpp index 1f96dd0de..5be8103f6 100644 --- a/stratosphere/boot/source/boot_pmc_wrapper.hpp +++ b/stratosphere/boot/source/boot_pmc_wrapper.hpp @@ -18,8 +18,10 @@ namespace ams::boot { + constexpr inline dd::PhysicalAddress PmcBase = 0x7000E400; + /* PMC Access Utilities. */ - u32 ReadPmcRegister(u32 phys_addr); - void WritePmcRegister(u32 phys_addr, u32 value, u32 mask = UINT32_MAX); + u32 ReadPmcRegister(dd::PhysicalAddress phys_addr); + void WritePmcRegister(dd::PhysicalAddress phys_addr, u32 value, u32 mask = std::numeric_limits::max()); } diff --git a/stratosphere/boot/source/boot_registers_clkrst.hpp b/stratosphere/boot/source/boot_registers_clkrst.hpp deleted file mode 100644 index f361e4ef0..000000000 --- a/stratosphere/boot/source/boot_registers_clkrst.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -static constexpr size_t CLK_RST_CONTROLLER_RST_SOURCE = 0x0; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_L = 0x4; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_H = 0x8; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_U = 0xC; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_L = 0x10; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_H = 0x14; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_U = 0x18; -static constexpr size_t CLK_RST_CONTROLLER_CCLK_BURST_POLICY = 0x20; -static constexpr size_t CLK_RST_CONTROLLER_SUPER_CCLK_DIVIDER = 0x24; -static constexpr size_t CLK_RST_CONTROLLER_SCLK_BURST_POLICY = 0x28; -static constexpr size_t CLK_RST_CONTROLLER_SUPER_SCLK_DIVIDER = 0x2C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SYSTEM_RATE = 0x30; -static constexpr size_t CLK_RST_CONTROLLER_MISC_CLK_ENB = 0x48; -static constexpr size_t CLK_RST_CONTROLLER_OSC_CTRL = 0x50; -static constexpr size_t CLK_RST_CONTROLLER_PLLC_BASE = 0x80; -static constexpr size_t CLK_RST_CONTROLLER_PLLC_MISC = 0x88; -static constexpr size_t CLK_RST_CONTROLLER_PLLM_BASE = 0x90; -static constexpr size_t CLK_RST_CONTROLLER_PLLM_MISC1 = 0x98; -static constexpr size_t CLK_RST_CONTROLLER_PLLM_MISC2 = 0x9C; -static constexpr size_t CLK_RST_CONTROLLER_PLLP_BASE = 0xA0; -static constexpr size_t CLK_RST_CONTROLLER_PLLD_BASE = 0xD0; -static constexpr size_t CLK_RST_CONTROLLER_PLLD_MISC1 = 0xD8; -static constexpr size_t CLK_RST_CONTROLLER_PLLD_MISC = 0xDC; -static constexpr size_t CLK_RST_CONTROLLER_PLLX_BASE = 0xE0; -static constexpr size_t CLK_RST_CONTROLLER_PLLX_MISC = 0xE4; -static constexpr size_t CLK_RST_CONTROLLER_PLLE_BASE = 0xE8; -static constexpr size_t CLK_RST_CONTROLLER_PLLE_MISC = 0xEC; -static constexpr size_t CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRA = 0xF8; -static constexpr size_t CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRB = 0xFC; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_PWM = 0x110; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_I2C1 = 0x124; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_I2C5 = 0x128; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_DISP1 = 0x138; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_VI = 0x148; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC1 = 0x150; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC2 = 0x154; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC4 = 0x164; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_UARTA = 0x178; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_UARTB = 0x17C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_HOST1X = 0x180; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_UARTC = 0x1A0; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_I2C3 = 0x1B8; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC3 = 0x1BC; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_CSITE = 0x1D4; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_EMC = 0x19C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_TSEC = 0x1F4; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_X = 0x280; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_X_SET = 0x284; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_X_CLR = 0x288; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_X = 0x28C; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_X_SET = 0x290; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_X_CLR = 0x294; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_Y = 0x298; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_Y_SET = 0x29C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_Y_CLR = 0x2A0; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_Y = 0x2A4; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_Y_SET = 0x2A8; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_Y_CLR = 0x2AC; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_L_SET = 0x300; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_L_CLR = 0x304; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_H_SET = 0x308; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_H_CLR = 0x30C; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_U_SET = 0x310; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEV_U_CLR = 0x314; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_L_SET = 0x320; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_L_CLR = 0x324; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_H_SET = 0x328; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_H_CLR = 0x32C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_U_SET = 0x330; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_U_CLR = 0x334; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_V = 0x358; -static constexpr size_t CLK_RST_CONTROLLER_RST_DEVICES_W = 0x35C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_V = 0x360; -static constexpr size_t CLK_RST_CONTROLLER_CLK_OUT_ENB_W = 0x364; -static constexpr size_t CLK_RST_CONTROLLER_CPU_SOFTRST_CTRL2 = 0x388; -static constexpr size_t CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRC = 0x3A0; -static constexpr size_t CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRD = 0x3A4; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_MSELECT = 0x3B4; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SOR1 = 0x410; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SE = 0x42C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_V_SET = 0x440; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_W_SET = 0x448; -static constexpr size_t CLK_RST_CONTROLLER_CLK_ENB_W_CLR = 0x44C; -static constexpr size_t CLK_RST_CONTROLLER_RST_CPUG_CMPLX_SET = 0x450; -static constexpr size_t CLK_RST_CONTROLLER_RST_CPUG_CMPLX_CLR = 0x454; -static constexpr size_t CLK_RST_CONTROLLER_UTMIP_PLL_CFG2 = 0x488; -static constexpr size_t CLK_RST_CONTROLLER_PLLE_AUX = 0x48C; -static constexpr size_t CLK_RST_CONTROLLER_AUDIO_SYNC_CLK_I2S0 = 0x4A0; -static constexpr size_t CLK_RST_CONTROLLER_PLLX_MISC_3 = 0x518; -static constexpr size_t CLK_RST_CONTROLLER_LVL2_CLK_GATE_OVRE = 0x554; -static constexpr size_t CLK_RST_CONTROLLER_SPARE_REG0 = 0x55C; -static constexpr size_t CLK_RST_CONTROLLER_PLLMB_BASE = 0x5E8; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_DSIA_LP = 0x620; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_EMC_DLL = 0x664; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_UART_FST_MIPI_CAL = 0x66C; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_SDMMC_LEGACY_TM = 0x694; -static constexpr size_t CLK_RST_CONTROLLER_CLK_SOURCE_NVENC = 0x6A0; -static constexpr size_t CLK_RST_CONTROLLER_SE_SUPER_CLK_DIVIDER = 0x704; diff --git a/stratosphere/boot/source/boot_registers_pinmux.hpp b/stratosphere/boot/source/boot_registers_pinmux.hpp deleted file mode 100644 index 5ae6da61a..000000000 --- a/stratosphere/boot/source/boot_registers_pinmux.hpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -static constexpr size_t APB_MISC_GP_SDMMC1_CLK_LPBK_CONTROL = 0x8D4; -static constexpr size_t APB_MISC_GP_SDMMC3_CLK_LPBK_CONTROL = 0x8D8; -static constexpr size_t APB_MISC_GP_SDMMC1_PAD_CFGPADCTRL = 0xA98; -static constexpr size_t APB_MISC_GP_VGPIO_GPIO_MUX_SEL = 0xB74; - -static constexpr size_t PINMUX_AUX_SDMMC1_CLK = 0x00; -static constexpr size_t PINMUX_AUX_SDMMC1_CMD = 0x04; -static constexpr size_t PINMUX_AUX_SDMMC1_DAT3 = 0x08; -static constexpr size_t PINMUX_AUX_SDMMC1_DAT2 = 0x0C; -static constexpr size_t PINMUX_AUX_SDMMC1_DAT1 = 0x10; -static constexpr size_t PINMUX_AUX_SDMMC1_DAT0 = 0x14; -static constexpr size_t PINMUX_AUX_SDMMC3_CLK = 0x1C; -static constexpr size_t PINMUX_AUX_SDMMC3_CMD = 0x20; -static constexpr size_t PINMUX_AUX_SDMMC3_DAT0 = 0x24; -static constexpr size_t PINMUX_AUX_SDMMC3_DAT1 = 0x28; -static constexpr size_t PINMUX_AUX_SDMMC3_DAT2 = 0x2C; -static constexpr size_t PINMUX_AUX_SDMMC3_DAT3 = 0x30; -static constexpr size_t PINMUX_AUX_DMIC3_CLK = 0xB4; -static constexpr size_t PINMUX_AUX_UART2_TX = 0xF4; -static constexpr size_t PINMUX_AUX_UART3_TX = 0x104; -static constexpr size_t PINMUX_AUX_WIFI_EN = 0x1B4; -static constexpr size_t PINMUX_AUX_WIFI_RST = 0x1B8; -static constexpr size_t PINMUX_AUX_NFC_EN = 0x1D0; -static constexpr size_t PINMUX_AUX_NFC_INT = 0x1D4; -static constexpr size_t PINMUX_AUX_LCD_BL_PWM = 0x1FC; -static constexpr size_t PINMUX_AUX_LCD_BL_EN = 0x200; -static constexpr size_t PINMUX_AUX_LCD_RST = 0x204; -static constexpr size_t PINMUX_AUX_GPIO_PE6 = 0x248; -static constexpr size_t PINMUX_AUX_GPIO_PH6 = 0x250; -static constexpr size_t PINMUX_AUX_GPIO_PZ1 = 0x280; - -static constexpr u32 PINMUX_FUNC_MASK = (3 << 0); - -static constexpr u32 PINMUX_PULL_MASK = (3 << 2); -static constexpr u32 PINMUX_PULL_NONE = (0 << 2); -static constexpr u32 PINMUX_PULL_DOWN = (1 << 2); -static constexpr u32 PINMUX_PULL_UP = (2 << 2); - -static constexpr u32 PINMUX_TRISTATE = (1 << 4); -static constexpr u32 PINMUX_PARKED = (1 << 5); -static constexpr u32 PINMUX_INPUT_ENABLE = (1 << 6); -static constexpr u32 PINMUX_LOCK = (1 << 7); -static constexpr u32 PINMUX_LPDR = (1 << 8); -static constexpr u32 PINMUX_HSM = (1 << 9); - -static constexpr u32 PINMUX_IO_HV = (1 << 10); -static constexpr u32 PINMUX_OPEN_DRAIN = (1 << 11); -static constexpr u32 PINMUX_SCHMT = (1 << 12); - -static constexpr u32 PINMUX_DRIVE_1X = (0 << 13) ; -static constexpr u32 PINMUX_DRIVE_2X = (1 << 13); -static constexpr u32 PINMUX_DRIVE_3X = (2 << 13); -static constexpr u32 PINMUX_DRIVE_4X = (3 << 13); - diff --git a/stratosphere/boot/source/boot_registers_pmc.hpp b/stratosphere/boot/source/boot_registers_pmc.hpp deleted file mode 100644 index 8d0d53f2f..000000000 --- a/stratosphere/boot/source/boot_registers_pmc.hpp +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2018-2020 Atmosphère-NX - * - * This program is free software; you can redistribute it and/or modify it - * under the terms and conditions of the GNU General Public License, - * version 2, as published by the Free Software Foundation. - * - * This program is distributed in the hope it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -#pragma once -#include - -static constexpr uintptr_t PmcBase = 0x7000E400ul; - -static constexpr size_t APBDEV_PMC_CNTRL = 0x0; -static constexpr u32 PMC_CNTRL_MAIN_RST = (1 << 4); -static constexpr size_t APBDEV_PMC_SEC_DISABLE = 0x4; -static constexpr size_t APBDEV_PMC_WAKE_MASK = 0xC; -static constexpr size_t APBDEV_PMC_WAKE_LVL = 0x10; -static constexpr size_t APBDEV_PMC_DPD_PADS_ORIDE = 0x1C; -static constexpr size_t APBDEV_PMC_PWRGATE_TOGGLE = 0x30; -static constexpr size_t APBDEV_PMC_PWRGATE_STATUS = 0x38; -static constexpr size_t APBDEV_PMC_BLINK_TIMER = 0x40; -static constexpr size_t APBDEV_PMC_NO_IOPOWER = 0x44; -static constexpr size_t APBDEV_PMC_SCRATCH0 = 0x50; -static constexpr size_t APBDEV_PMC_SCRATCH1 = 0x54; -static constexpr size_t APBDEV_PMC_SCRATCH20 = 0xA0; -static constexpr size_t APBDEV_PMC_AUTO_WAKE_LVL_MASK = 0xDC; -static constexpr size_t APBDEV_PMC_PWR_DET_VAL = 0xE4; -static constexpr u32 PMC_PWR_DET_SDMMC1_IO_EN = (1 << 12); -static constexpr size_t APBDEV_PMC_DDR_PWR = 0xE8; -static constexpr size_t APBDEV_PMC_CRYPTO_OP = 0xF4; -static constexpr u32 PMC_CRYPTO_OP_SE_ENABLE = 0; -static constexpr u32 PMC_CRYPTO_OP_SE_DISABLE = 1; -static constexpr size_t APBDEV_PMC_SCRATCH33 = 0x120; -static constexpr size_t APBDEV_PMC_SCRATCH40 = 0x13C; -static constexpr size_t APBDEV_PMC_WAKE2_MASK = 0x160; -static constexpr size_t APBDEV_PMC_WAKE2_LVL = 0x164; -static constexpr size_t APBDEV_PMC_AUTO_WAKE2_LVL_MASK = 0x170; -static constexpr size_t APBDEV_PMC_OSC_EDPD_OVER = 0x1A4; -static constexpr size_t APBDEV_PMC_CLK_OUT_CNTRL = 0x1A8; -static constexpr size_t APBDEV_PMC_RST_STATUS = 0x1B4; -static constexpr size_t APBDEV_PMC_IO_DPD_REQ = 0x1B8; -static constexpr size_t APBDEV_PMC_IO_DPD2_REQ = 0x1C0; -static constexpr size_t APBDEV_PMC_VDDP_SEL = 0x1CC; -static constexpr size_t APBDEV_PMC_DDR_CFG = 0x1D0; -static constexpr size_t APBDEV_PMC_SCRATCH45 = 0x234; -static constexpr size_t APBDEV_PMC_SCRATCH46 = 0x238; -static constexpr size_t APBDEV_PMC_SCRATCH49 = 0x244; -static constexpr size_t APBDEV_PMC_TSC_MULT = 0x2B4; -static constexpr size_t APBDEV_PMC_SEC_DISABLE2 = 0x2C4; -static constexpr size_t APBDEV_PMC_WEAK_BIAS = 0x2C8; -static constexpr size_t APBDEV_PMC_REG_SHORT = 0x2CC; -static constexpr size_t APBDEV_PMC_SEC_DISABLE3 = 0x2D8; -static constexpr size_t APBDEV_PMC_SECURE_SCRATCH21 = 0x334; -static constexpr size_t APBDEV_PMC_SECURE_SCRATCH32 = 0x360; -static constexpr size_t APBDEV_PMC_SECURE_SCRATCH49 = 0x3A4; -static constexpr size_t APBDEV_PMC_CNTRL2 = 0x440; -static constexpr size_t APBDEV_PMC_IO_DPD3_REQ = 0x45C; -static constexpr size_t APBDEV_PMC_IO_DPD4_REQ = 0x464; -static constexpr size_t APBDEV_PMC_UTMIP_PAD_CFG1 = 0x4C4; -static constexpr size_t APBDEV_PMC_UTMIP_PAD_CFG3 = 0x4CC; -static constexpr size_t APBDEV_PMC_WAKE_DEBOUNCE_EN = 0x4D8; -static constexpr size_t APBDEV_PMC_DDR_CNTRL = 0x4E4; -static constexpr size_t APBDEV_PMC_SEC_DISABLE4 = 0x5B0; -static constexpr size_t APBDEV_PMC_SEC_DISABLE5 = 0x5B4; -static constexpr size_t APBDEV_PMC_SEC_DISABLE6 = 0x5B8; -static constexpr size_t APBDEV_PMC_SEC_DISABLE7 = 0x5BC; -static constexpr size_t APBDEV_PMC_SEC_DISABLE8 = 0x5C0; -static constexpr size_t APBDEV_PMC_SCRATCH188 = 0x810; -static constexpr size_t APBDEV_PMC_SCRATCH190 = 0x818; -static constexpr size_t APBDEV_PMC_SCRATCH200 = 0x840; diff --git a/stratosphere/boot/source/boot_wake_pins.cpp b/stratosphere/boot/source/boot_wake_pins.cpp index ccfc94700..59f710f90 100644 --- a/stratosphere/boot/source/boot_wake_pins.cpp +++ b/stratosphere/boot/source/boot_wake_pins.cpp @@ -17,8 +17,6 @@ #include "boot_pmc_wrapper.hpp" #include "boot_wake_pins.hpp" -#include "boot_registers_pmc.hpp" - namespace ams::boot { /* Include configuration into anonymous namespace. */ @@ -43,7 +41,7 @@ namespace ams::boot { /* Helpers. */ void UpdatePmcControlBit(const u32 reg_offset, const u32 mask_val, const bool flag) { - WritePmcRegister(PmcBase + reg_offset, flag ? UINT32_MAX : 0, mask_val); + WritePmcRegister(PmcBase + reg_offset, flag ? std::numeric_limits::max() : 0, mask_val); ReadPmcRegister(PmcBase + reg_offset); } diff --git a/stratosphere/boot/source/gpio/gpio_initial_configuration.cpp b/stratosphere/boot/source/gpio/gpio_initial_configuration.cpp index 984c4423d..b6525907b 100644 --- a/stratosphere/boot/source/gpio/gpio_initial_configuration.cpp +++ b/stratosphere/boot/source/gpio/gpio_initial_configuration.cpp @@ -17,7 +17,7 @@ #include "gpio_initial_configuration.hpp" #include "gpio_utils.hpp" -namespace ams::gpio { +namespace ams::boot::gpio { namespace { diff --git a/stratosphere/boot/source/gpio/gpio_initial_configuration.hpp b/stratosphere/boot/source/gpio/gpio_initial_configuration.hpp index b46f17eea..0a1c5e65d 100644 --- a/stratosphere/boot/source/gpio/gpio_initial_configuration.hpp +++ b/stratosphere/boot/source/gpio/gpio_initial_configuration.hpp @@ -18,7 +18,7 @@ #include #include -namespace ams::gpio { +namespace ams::boot::gpio { void SetInitialConfiguration(); diff --git a/stratosphere/boot/source/gpio/gpio_map.inc b/stratosphere/boot/source/gpio/gpio_map.inc index 7a76dcc5e..d4c9397e3 100644 --- a/stratosphere/boot/source/gpio/gpio_map.inc +++ b/stratosphere/boot/source/gpio/gpio_map.inc @@ -14,7 +14,7 @@ * along with this program. If not, see . */ -constexpr u32 InvalidPadName = UINT32_MAX; +constexpr u32 InvalidPadName = std::numeric_limits::max(); constexpr u32 Map[] = { InvalidPadName, /* Invalid */ diff --git a/stratosphere/boot/source/gpio/gpio_utils.cpp b/stratosphere/boot/source/gpio/gpio_utils.cpp index 9f933e365..4bd21f1b9 100644 --- a/stratosphere/boot/source/gpio/gpio_utils.cpp +++ b/stratosphere/boot/source/gpio/gpio_utils.cpp @@ -16,7 +16,7 @@ #include #include "gpio_utils.hpp" -namespace ams::gpio { +namespace ams::boot::gpio { namespace { diff --git a/stratosphere/boot/source/gpio/gpio_utils.hpp b/stratosphere/boot/source/gpio/gpio_utils.hpp index ad1418e40..19a6131a5 100644 --- a/stratosphere/boot/source/gpio/gpio_utils.hpp +++ b/stratosphere/boot/source/gpio/gpio_utils.hpp @@ -18,7 +18,7 @@ #include #include -namespace ams::gpio { +namespace ams::boot::gpio { /* GPIO Utilities. */ u32 Configure(u32 gpio_pad_name); diff --git a/stratosphere/boot2/source/boot2_main.cpp b/stratosphere/boot2/source/boot2_main.cpp index b7e7d4570..a7e196b84 100644 --- a/stratosphere/boot2/source/boot2_main.cpp +++ b/stratosphere/boot2/source/boot2_main.cpp @@ -75,7 +75,7 @@ void __appInit(void) { R_ABORT_UNLESS(pminfoInitialize()); R_ABORT_UNLESS(pmshellInitialize()); R_ABORT_UNLESS(setsysInitialize()); - R_ABORT_UNLESS(gpioInitialize()); + gpio::Initialize(); }); /* Mount the SD card. */ @@ -86,7 +86,7 @@ void __appInit(void) { void __appExit(void) { fs::Unmount("sdmc"); - gpioExit(); + gpio::Finalize(); setsysExit(); pmshellExit(); pminfoExit(); diff --git a/stratosphere/fatal/source/fatal_main.cpp b/stratosphere/fatal/source/fatal_main.cpp index 3b576347b..09e866241 100644 --- a/stratosphere/fatal/source/fatal_main.cpp +++ b/stratosphere/fatal/source/fatal_main.cpp @@ -92,7 +92,7 @@ void __appInit(void) { R_ABORT_UNLESS(psmInitialize()); R_ABORT_UNLESS(spsmInitialize()); R_ABORT_UNLESS(plInitialize(::PlServiceType_User)); - R_ABORT_UNLESS(gpioInitialize()); + gpio::Initialize(); R_ABORT_UNLESS(fsInitialize()); }); @@ -105,7 +105,7 @@ void __appExit(void) { /* Cleanup services. */ fsExit(); plExit(); - gpioExit(); + gpio::Finalize(); spsmExit(); psmExit(); lblExit(); diff --git a/stratosphere/fatal/source/fatal_repair.cpp b/stratosphere/fatal/source/fatal_repair.cpp index bc57fe698..77e3685d4 100644 --- a/stratosphere/fatal/source/fatal_repair.cpp +++ b/stratosphere/fatal/source/fatal_repair.cpp @@ -33,22 +33,21 @@ namespace ams::fatal::srv { bool IsInRepairWithoutVolHeld() { if (IsInRepair()) { - GpioPadSession vol_btn; - if (R_FAILED(gpioOpenSession(&vol_btn, GpioPadName_ButtonVolUp))) { + gpio::GpioPadSession vol_btn; + if (R_FAILED(gpio::OpenSession(std::addressof(vol_btn), gpio::DeviceCode_ButtonVolUp))) { return true; } /* Ensure we close even on early return. */ - ON_SCOPE_EXIT { gpioPadClose(&vol_btn); }; + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(vol_btn)); }; /* Set direction input. */ - gpioPadSetDirection(&vol_btn, GpioDirection_Input); + gpio::SetDirection(std::addressof(vol_btn), gpio::Direction_Input); /* Ensure that we're holding the volume button for a full second. */ auto start = os::GetSystemTick(); do { - GpioValue val; - if (R_FAILED(gpioPadGetValue(&vol_btn, &val)) || val != GpioValue_Low) { + if (gpio::GetValue(std::addressof(vol_btn)) != gpio::GpioValue_Low) { return true; } diff --git a/stratosphere/fatal/source/fatal_task_power.cpp b/stratosphere/fatal/source/fatal_task_power.cpp index 4d70a35d5..5f9acaefe 100644 --- a/stratosphere/fatal/source/fatal_task_power.cpp +++ b/stratosphere/fatal/source/fatal_task_power.cpp @@ -156,33 +156,33 @@ namespace ams::fatal::srv { RebootTimingObserver fatal_reboot_helper(config.IsFatalRebootEnabled(), config.GetFatalRebootTimeoutInterval()); bool check_vol_up = true, check_vol_down = true; - GpioPadSession vol_up_btn, vol_down_btn; - if (R_FAILED(gpioOpenSession(&vol_up_btn, GpioPadName_ButtonVolUp))) { + gpio::GpioPadSession vol_up_btn, vol_down_btn; + if (R_FAILED(gpio::OpenSession(std::addressof(vol_up_btn), gpio::DeviceCode_ButtonVolUp))) { check_vol_up = false; } - if (R_FAILED(gpioOpenSession(&vol_down_btn, GpioPadName_ButtonVolDown))) { + if (R_FAILED(gpio::OpenSession(std::addressof(vol_down_btn), gpio::DeviceCode_ButtonVolDn))) { check_vol_down = false; } /* Ensure we close on early return. */ - ON_SCOPE_EXIT { if (check_vol_up) { gpioPadClose(&vol_up_btn); } }; - ON_SCOPE_EXIT { if (check_vol_down) { gpioPadClose(&vol_down_btn); } }; + ON_SCOPE_EXIT { if (check_vol_up) { gpio::CloseSession(std::addressof(vol_up_btn)); } }; + ON_SCOPE_EXIT { if (check_vol_down) { gpio::CloseSession(std::addressof(vol_down_btn)); } }; /* Set direction input. */ if (check_vol_up) { - gpioPadSetDirection(&vol_up_btn, GpioDirection_Input); + gpio::SetDirection(std::addressof(vol_up_btn), gpio::Direction_Input); } if (check_vol_down) { - gpioPadSetDirection(&vol_down_btn, GpioDirection_Input); + gpio::SetDirection(std::addressof(vol_down_btn), gpio::Direction_Input); } BpcSleepButtonState state; - GpioValue val; while (true) { if (fatal_reboot_helper.IsRebootTiming() || (quest_reboot_helper.IsRebootTiming()) || - (check_vol_up && R_SUCCEEDED(gpioPadGetValue(&vol_up_btn, &val)) && val == GpioValue_Low) || - (check_vol_down && R_SUCCEEDED(gpioPadGetValue(&vol_down_btn, &val)) && val == GpioValue_Low) || - (R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held)) { + (check_vol_up && gpio::GetValue(std::addressof(vol_up_btn)) == gpio::GpioValue_Low) || + (check_vol_down && gpio::GetValue(std::addressof(vol_down_btn)) == gpio::GpioValue_Low) || + (R_SUCCEEDED(bpcGetSleepButtonState(&state)) && state == BpcSleepButtonState_Held)) + { /* If any of the above conditions succeeded, we should reboot. */ bpcRebootSystem(); return; diff --git a/stratosphere/fatal/source/fatal_task_sound.cpp b/stratosphere/fatal/source/fatal_task_sound.cpp index cc235b7d5..ad5d6eb59 100644 --- a/stratosphere/fatal/source/fatal_task_sound.cpp +++ b/stratosphere/fatal/source/fatal_task_sound.cpp @@ -70,16 +70,16 @@ namespace ams::fatal::srv { /* Talk to the ALC5639 over GPIO, and disable audio output */ { - GpioPadSession audio; - if (R_SUCCEEDED(gpioOpenSession(&audio, GpioPadName_AudioCodec))) { - ON_SCOPE_EXIT { gpioPadClose(&audio); }; + gpio::GpioPadSession audio; + if (R_SUCCEEDED(gpio::OpenSession(std::addressof(audio), gpio::DeviceCode_CodecLdoEnTemp))) { + ON_SCOPE_EXIT { gpio::CloseSession(std::addressof(audio)); }; /* Set direction output, sleep 200 ms so it can take effect. */ - gpioPadSetDirection(&audio, GpioDirection_Output); - svcSleepThread(200000000UL); + gpio::SetDirection(std::addressof(audio), gpio::Direction_Output); + os::SleepThread(TimeSpan::FromMilliSeconds(200)); /* Pull audio codec low. */ - gpioPadSetValue(&audio, GpioValue_Low); + gpio::SetValue(std::addressof(audio), gpio::GpioValue_Low); } } }