Initial commit
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.bin binary
|
||||||
37
.github/workflows/nightly.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
name: Build Pico Launcher
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["develop"]
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
pull_request:
|
||||||
|
branches: ["develop"]
|
||||||
|
paths-ignore:
|
||||||
|
- 'README.md'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pico_launcher:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container: skylyrac/blocksds:slim-v1.13.1
|
||||||
|
name: Build Pico Launcher
|
||||||
|
env:
|
||||||
|
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||||
|
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1
|
||||||
|
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT: 1
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Run build script
|
||||||
|
run: |
|
||||||
|
make
|
||||||
|
- name: Publish build to GH Actions
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
_pico/
|
||||||
|
LAUNCHER.nds
|
||||||
|
name: Pico Launcher
|
||||||
49
.gitignore
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Ignore build directories #
|
||||||
|
############################
|
||||||
|
Debug/
|
||||||
|
Release/
|
||||||
|
build/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Compiled source #
|
||||||
|
###################
|
||||||
|
*.com
|
||||||
|
*.class
|
||||||
|
*.dll
|
||||||
|
*.d
|
||||||
|
*.map
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Packages #
|
||||||
|
############
|
||||||
|
# it's better to unpack these files and commit the raw source
|
||||||
|
# git has its own built in compression methods
|
||||||
|
*.7z
|
||||||
|
*.dmg
|
||||||
|
*.gz
|
||||||
|
*.iso
|
||||||
|
*.jar
|
||||||
|
*.rar
|
||||||
|
*.tar
|
||||||
|
*.zip
|
||||||
|
|
||||||
|
# Logs and databases #
|
||||||
|
######################
|
||||||
|
*.log
|
||||||
|
*.sql
|
||||||
|
*.sqlite
|
||||||
|
|
||||||
|
# OS generated files #
|
||||||
|
######################
|
||||||
|
.DS_Store
|
||||||
|
.DS_Store?
|
||||||
|
._*
|
||||||
|
.Spotlight-V100
|
||||||
|
.Trashes
|
||||||
|
ehthumbs.db
|
||||||
|
Thumbs.db
|
||||||
|
*.nds
|
||||||
|
*.elf
|
||||||
|
*.a
|
||||||
|
.vscode/
|
||||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "libs/libtwl"]
|
||||||
|
path = libs/libtwl
|
||||||
|
url = https://github.com/Gericom/libtwl.git
|
||||||
17
LICENSE.txt
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
Copyright (c) 2025 LNH team
|
||||||
|
|
||||||
|
This software is provided 'as-is', without any express or implied
|
||||||
|
warranty. In no event will the authors be held liable for any damages
|
||||||
|
arising from the use of this software.
|
||||||
|
|
||||||
|
Permission is granted to anyone to use this software for any purpose,
|
||||||
|
including commercial applications, and to alter it and redistribute it
|
||||||
|
freely, subject to the following restrictions:
|
||||||
|
|
||||||
|
1. The origin of this software must not be misrepresented; you must not
|
||||||
|
claim that you wrote the original software. If you use this software
|
||||||
|
in a product, an acknowledgment in the product documentation would be
|
||||||
|
appreciated but is not required.
|
||||||
|
2. Altered source versions must be plainly marked as such, and must not be
|
||||||
|
misrepresented as being the original software.
|
||||||
|
3. This notice may not be removed or altered from any source distribution.
|
||||||
110
Makefile
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
#
|
||||||
|
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||||
|
|
||||||
|
BLOCKSDS ?= /opt/blocksds/core
|
||||||
|
BLOCKSDSEXT ?= /opt/blocksds/external
|
||||||
|
|
||||||
|
export LIBTWL ?= $(shell pwd)/libs/libtwl
|
||||||
|
|
||||||
|
# User config
|
||||||
|
# ===========
|
||||||
|
|
||||||
|
NAME := LAUNCHER
|
||||||
|
|
||||||
|
GAME_TITLE := Pico Launcher
|
||||||
|
GAME_AUTHOR := LNH team
|
||||||
|
GAME_ICON := icon.bmp
|
||||||
|
|
||||||
|
# DLDI and internal SD slot of DSi
|
||||||
|
# --------------------------------
|
||||||
|
|
||||||
|
# Root folder of the SD image
|
||||||
|
SDROOT := sdroot
|
||||||
|
# Name of the generated image it "DSi-1.sd" for no$gba in DSi mode
|
||||||
|
SDIMAGE := image.bin
|
||||||
|
|
||||||
|
# Source code paths
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
# A single directory that is the root of NitroFS:
|
||||||
|
NITROFSDIR :=
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
# -----
|
||||||
|
|
||||||
|
MAKE := make
|
||||||
|
RM := rm -rf
|
||||||
|
|
||||||
|
# Verbose flag
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
ifeq ($(VERBOSE),1)
|
||||||
|
V :=
|
||||||
|
else
|
||||||
|
V := @
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Directories
|
||||||
|
# -----------
|
||||||
|
|
||||||
|
ARM9DIR := arm9
|
||||||
|
ARM7DIR := arm7
|
||||||
|
|
||||||
|
# Build artfacts
|
||||||
|
# --------------
|
||||||
|
|
||||||
|
ROM := $(NAME).nds
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
# -------
|
||||||
|
|
||||||
|
.PHONY: all clean arm9 arm7 dldipatch sdimage checklibtwl
|
||||||
|
|
||||||
|
all: $(ROM)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo " CLEAN"
|
||||||
|
$(V)$(MAKE) -f Makefile.arm9 clean --no-print-directory
|
||||||
|
$(V)$(MAKE) -f Makefile.arm7 clean --no-print-directory
|
||||||
|
$(V)$(RM) $(ROM) build $(SDIMAGE)
|
||||||
|
|
||||||
|
arm9: checklibtwl
|
||||||
|
$(V)+$(MAKE) -f Makefile.arm9 --no-print-directory
|
||||||
|
|
||||||
|
arm7: checklibtwl
|
||||||
|
$(V)+$(MAKE) -f Makefile.arm7 --no-print-directory
|
||||||
|
|
||||||
|
checklibtwl:
|
||||||
|
$(MAKE) -C $(LIBTWL)
|
||||||
|
|
||||||
|
ifneq ($(strip $(NITROFSDIR)),)
|
||||||
|
# Additional arguments for ndstool
|
||||||
|
NDSTOOL_ARGS := -d $(NITROFSDIR)
|
||||||
|
|
||||||
|
# Make the NDS ROM depend on the filesystem only if it is needed
|
||||||
|
$(ROM): $(NITROFSDIR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Combine the title strings
|
||||||
|
ifeq ($(strip $(GAME_SUBTITLE)),)
|
||||||
|
GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_AUTHOR)
|
||||||
|
else
|
||||||
|
GAME_FULL_TITLE := $(GAME_TITLE);$(GAME_SUBTITLE);$(GAME_AUTHOR)
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(ROM): arm9 arm7
|
||||||
|
@echo " NDSTOOL $@"
|
||||||
|
$(V)$(BLOCKSDS)/tools/ndstool/ndstool -c $@ \
|
||||||
|
-7 build/arm7.elf -9 build/arm9.elf \
|
||||||
|
-b $(GAME_ICON) "$(GAME_FULL_TITLE)" \
|
||||||
|
$(NDSTOOL_ARGS)
|
||||||
|
|
||||||
|
sdimage:
|
||||||
|
@echo " MKFATIMG $(SDIMAGE) $(SDROOT)"
|
||||||
|
$(V)$(BLOCKSDS)/tools/mkfatimg/mkfatimg -t $(SDROOT) $(SDIMAGE)
|
||||||
|
|
||||||
|
dldipatch: $(ROM)
|
||||||
|
@echo " DLDIPATCH $(ROM)"
|
||||||
|
$(V)$(BLOCKSDS)/tools/dldipatch/dldipatch patch \
|
||||||
|
$(BLOCKSDS)/sys/dldi_r4/r4tf.dldi $(ROM)
|
||||||
189
Makefile.arm7
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
#
|
||||||
|
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||||
|
|
||||||
|
export BLOCKSDS ?= /opt/blocksds/core
|
||||||
|
export BLOCKSDSEXT ?= /opt/blocksds/external
|
||||||
|
|
||||||
|
export LIBTWL ?= $(shell pwd)/libs/libtwl
|
||||||
|
|
||||||
|
export WONDERFUL_TOOLCHAIN ?= /opt/wonderful
|
||||||
|
ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
|
||||||
|
|
||||||
|
# Source code paths
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
SOURCEDIRS := arm7/source common
|
||||||
|
INCLUDEDIRS := arm7/source common
|
||||||
|
BINDIRS :=
|
||||||
|
|
||||||
|
# Defines passed to all files
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
|
DEFINES := -DLIBTWL_ARM7
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
LIBS := -ltwl7 -lnds7
|
||||||
|
LIBDIRS := $(BLOCKSDS)/libs/libnds \
|
||||||
|
$(LIBTWL)/libtwl7 $(LIBTWL)/common $(LIBTWL)
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
NAME := arm7
|
||||||
|
BUILDDIR := build/$(NAME)
|
||||||
|
ELF := build/$(NAME).elf
|
||||||
|
DUMP := build/$(NAME).dump
|
||||||
|
MAP := build/$(NAME).map
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
# -----
|
||||||
|
|
||||||
|
PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi-
|
||||||
|
CC := $(PREFIX)gcc
|
||||||
|
CXX := $(PREFIX)g++
|
||||||
|
LD := $(PREFIX)gcc
|
||||||
|
OBJDUMP := $(PREFIX)objdump
|
||||||
|
MKDIR := mkdir
|
||||||
|
RM := rm -rf
|
||||||
|
|
||||||
|
# Verbose flag
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
ifeq ($(VERBOSE),1)
|
||||||
|
V :=
|
||||||
|
else
|
||||||
|
V := @
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Source files
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
ifneq ($(BINDIRS),)
|
||||||
|
SOURCES_BIN := $(shell find -L $(BINDIRS) -name "*.bin")
|
||||||
|
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BINDIRS))
|
||||||
|
endif
|
||||||
|
|
||||||
|
SOURCES_S := $(shell find -L $(SOURCEDIRS) -name "*.s")
|
||||||
|
SOURCES_C := $(shell find -L $(SOURCEDIRS) -name "*.c")
|
||||||
|
SOURCES_CPP := $(shell find -L $(SOURCEDIRS) -name "*.cpp")
|
||||||
|
|
||||||
|
# Compiler and linker flags
|
||||||
|
# -------------------------
|
||||||
|
|
||||||
|
ARCH := -marm -mthumb-interwork -mcpu=arm7tdmi
|
||||||
|
|
||||||
|
SPECS := arm7/dldi_ds_arm7.specs
|
||||||
|
|
||||||
|
WARNFLAGS := -Wall
|
||||||
|
|
||||||
|
ifeq ($(SOURCES_CPP),)
|
||||||
|
LIBS += -lc
|
||||||
|
else
|
||||||
|
LIBS += -lstdc++ -lc
|
||||||
|
endif
|
||||||
|
|
||||||
|
INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
|
||||||
|
$(foreach path,$(LIBDIRS),-I$(path)/include)
|
||||||
|
|
||||||
|
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
|
||||||
|
|
||||||
|
ASFLAGS += -x assembler-with-cpp $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -ffunction-sections -fdata-sections \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||||
|
-fno-exceptions -fno-rtti \
|
||||||
|
-fno-threadsafe-statics \
|
||||||
|
-Wsuggest-override -Werror=suggest-override \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP),--gc-sections $(DEFINES) \
|
||||||
|
-Wl,--start-group $(LIBS) -Wl,--end-group -specs=$(SPECS)
|
||||||
|
|
||||||
|
# Intermediate build files
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
OBJS_ASSETS := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN)))
|
||||||
|
|
||||||
|
HEADERS_ASSETS := $(patsubst %.bin,%_bin.h,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN)))
|
||||||
|
|
||||||
|
OBJS_SOURCES := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_S))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_C))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_CPP)))
|
||||||
|
|
||||||
|
OBJS := $(OBJS_ASSETS) $(OBJS_SOURCES)
|
||||||
|
|
||||||
|
DEPS := $(OBJS:.o=.d)
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
# -------
|
||||||
|
|
||||||
|
.PHONY: all clean dump
|
||||||
|
|
||||||
|
all: $(ELF)
|
||||||
|
|
||||||
|
$(ELF): $(OBJS)
|
||||||
|
@echo " LD.7 $@"
|
||||||
|
$(V)$(LD) -o $@ $(OBJS) $(LDFLAGS)
|
||||||
|
|
||||||
|
$(DUMP): $(ELF)
|
||||||
|
@echo " OBJDUMP.7 $@"
|
||||||
|
$(V)$(OBJDUMP) -h -C -S $< > $@
|
||||||
|
|
||||||
|
dump: $(DUMP)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo " CLEAN.7"
|
||||||
|
$(V)$(RM) $(ELF) $(DUMP) $(MAP) $(BUILDDIR)
|
||||||
|
|
||||||
|
# Rules
|
||||||
|
# -----
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.s.o : %.s
|
||||||
|
@echo " AS.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.c.o : %.c
|
||||||
|
@echo " CC.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.arm.c.o : %.arm.c
|
||||||
|
@echo " CC.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.cpp.o : %.cpp
|
||||||
|
@echo " CXX.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp
|
||||||
|
@echo " CXX.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin
|
||||||
|
@echo " BIN2C.7 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.bin.o $(BUILDDIR)/$*_bin.c
|
||||||
|
|
||||||
|
# All assets must be built before the source code
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
$(SOURCES_S) $(SOURCES_C) $(SOURCES_CPP): $(HEADERS_ASSETS)
|
||||||
|
|
||||||
|
# Include dependency files if they exist
|
||||||
|
# --------------------------------------
|
||||||
|
|
||||||
|
-include $(DEPS)
|
||||||
243
Makefile.arm9
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
#
|
||||||
|
# SPDX-FileContributor: Antonio Niño Díaz, 2023
|
||||||
|
|
||||||
|
export BLOCKSDS ?= /opt/blocksds/core
|
||||||
|
export BLOCKSDSEXT ?= /opt/blocksds/external
|
||||||
|
|
||||||
|
export LIBTWL ?= $(shell pwd)/libs/libtwl
|
||||||
|
|
||||||
|
export WONDERFUL_TOOLCHAIN ?= /opt/wonderful
|
||||||
|
ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/
|
||||||
|
|
||||||
|
# Source code paths
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
SOURCEDIRS := arm9/source common
|
||||||
|
INCLUDEDIRS := arm9/source common
|
||||||
|
GFXDIRS := arm9/gfx
|
||||||
|
BINDIRS := arm9/data
|
||||||
|
AUDIODIRS :=
|
||||||
|
|
||||||
|
# Defines passed to all files
|
||||||
|
# ---------------------------
|
||||||
|
|
||||||
|
DEFINES := -DLIBTWL_ARM9
|
||||||
|
|
||||||
|
# Libraries
|
||||||
|
# ---------
|
||||||
|
|
||||||
|
LIBS := -ltwl9 -lnds9
|
||||||
|
LIBDIRS := $(BLOCKSDS)/libs/libnds \
|
||||||
|
$(LIBTWL)/libtwl9 $(LIBTWL)/common $(LIBTWL)
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
# ---------------
|
||||||
|
|
||||||
|
NAME := arm9
|
||||||
|
BUILDDIR := build/$(NAME)
|
||||||
|
ELF := build/$(NAME).elf
|
||||||
|
DUMP := build/$(NAME).dump
|
||||||
|
MAP := build/$(NAME).map
|
||||||
|
SOUNDBANKDIR := $(BUILDDIR)/maxmod
|
||||||
|
|
||||||
|
# Tools
|
||||||
|
# -----
|
||||||
|
|
||||||
|
PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi-
|
||||||
|
CC := $(PREFIX)gcc
|
||||||
|
CXX := $(PREFIX)g++
|
||||||
|
LD := $(PREFIX)gcc
|
||||||
|
OBJDUMP := $(PREFIX)objdump
|
||||||
|
MKDIR := mkdir
|
||||||
|
RM := rm -rf
|
||||||
|
|
||||||
|
# Verbose flag
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
ifeq ($(VERBOSE),1)
|
||||||
|
V :=
|
||||||
|
else
|
||||||
|
V := @
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Source files
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
ifneq ($(BINDIRS),)
|
||||||
|
SOURCES_BIN := $(shell find -L $(BINDIRS) -name "*.bin")
|
||||||
|
SOURCES_NFT2 := $(shell find -L $(BINDIRS) -name "*.nft2")
|
||||||
|
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(BINDIRS))
|
||||||
|
endif
|
||||||
|
ifneq ($(GFXDIRS),)
|
||||||
|
SOURCES_PNG := $(shell find -L $(GFXDIRS) -name "*.png")
|
||||||
|
INCLUDEDIRS += $(addprefix $(BUILDDIR)/,$(GFXDIRS))
|
||||||
|
endif
|
||||||
|
ifneq ($(AUDIODIRS),)
|
||||||
|
SOURCES_AUDIO := $(shell find -L $(AUDIODIRS) -regex '.*\.\(it\|mod\|s3m\|wav\|xm\)')
|
||||||
|
ifneq ($(SOURCES_AUDIO),)
|
||||||
|
INCLUDEDIRS += $(SOUNDBANKDIR)
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
|
||||||
|
SOURCES_S := $(shell find -L $(SOURCEDIRS) -name "*.s")
|
||||||
|
SOURCES_C := $(shell find -L $(SOURCEDIRS) -name "*.c")
|
||||||
|
SOURCES_CPP := $(shell find -L $(SOURCEDIRS) -name "*.cpp")
|
||||||
|
|
||||||
|
# Compiler and linker flags
|
||||||
|
# -------------------------
|
||||||
|
|
||||||
|
ARCH := -marm -mthumb-interwork -mcpu=arm946e-s+nofp
|
||||||
|
|
||||||
|
SPECS := $(BLOCKSDS)/sys/crts/ds_arm9.specs
|
||||||
|
|
||||||
|
WARNFLAGS := -Wall
|
||||||
|
|
||||||
|
ifeq ($(SOURCES_CPP),)
|
||||||
|
LIBS += -lc
|
||||||
|
else
|
||||||
|
LIBS += -lstdc++ -lc
|
||||||
|
endif
|
||||||
|
|
||||||
|
INCLUDEFLAGS := $(foreach path,$(INCLUDEDIRS),-I$(path)) \
|
||||||
|
$(foreach path,$(LIBDIRS),-I$(path)/include)
|
||||||
|
|
||||||
|
LIBDIRSFLAGS := $(foreach path,$(LIBDIRS),-L$(path)/lib)
|
||||||
|
|
||||||
|
ASFLAGS += -x assembler-with-cpp $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -ffunction-sections -fdata-sections \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
CFLAGS += -std=gnu17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||||
|
-fno-devirtualize-speculatively \
|
||||||
|
-Werror=return-type \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
CXXFLAGS += -std=gnu++23 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \
|
||||||
|
$(ARCH) -O2 -ffunction-sections -fdata-sections \
|
||||||
|
-fno-exceptions -fno-rtti \
|
||||||
|
-fno-devirtualize-speculatively \
|
||||||
|
-Werror=return-type \
|
||||||
|
-fno-threadsafe-statics \
|
||||||
|
-Wno-volatile -Wsuggest-override -Werror=suggest-override \
|
||||||
|
-specs=$(SPECS)
|
||||||
|
|
||||||
|
LDFLAGS := $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP),--gc-sections,--use-blx $(DEFINES) \
|
||||||
|
-Wl,--start-group $(LIBS) -Wl,--end-group -specs=$(SPECS)
|
||||||
|
|
||||||
|
# Intermediate build files
|
||||||
|
# ------------------------
|
||||||
|
|
||||||
|
OBJS_ASSETS := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_NFT2))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
|
||||||
|
|
||||||
|
HEADERS_ASSETS := $(patsubst %.bin,%_bin.h,$(addprefix $(BUILDDIR)/,$(SOURCES_BIN))) \
|
||||||
|
$(patsubst %.nft2,%_nft2.h,$(addprefix $(BUILDDIR)/,$(SOURCES_NFT2))) \
|
||||||
|
$(patsubst %.png,%.h,$(addprefix $(BUILDDIR)/,$(SOURCES_PNG)))
|
||||||
|
|
||||||
|
ifneq ($(SOURCES_AUDIO),)
|
||||||
|
OBJS_ASSETS += $(SOUNDBANKDIR)/soundbank.c.o
|
||||||
|
HEADERS_ASSETS += $(SOUNDBANKDIR)/soundbank.h
|
||||||
|
endif
|
||||||
|
|
||||||
|
OBJS_SOURCES := $(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_S))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_C))) \
|
||||||
|
$(addsuffix .o,$(addprefix $(BUILDDIR)/,$(SOURCES_CPP)))
|
||||||
|
|
||||||
|
OBJS := $(OBJS_ASSETS) $(OBJS_SOURCES)
|
||||||
|
|
||||||
|
DEPS := $(OBJS:.o=.d)
|
||||||
|
|
||||||
|
# Targets
|
||||||
|
# -------
|
||||||
|
|
||||||
|
.PHONY: all clean dump
|
||||||
|
|
||||||
|
all: $(ELF)
|
||||||
|
|
||||||
|
$(ELF): $(OBJS)
|
||||||
|
@echo " LD.9 $@"
|
||||||
|
$(V)$(LD) -o $@ $(OBJS) $(LDFLAGS)
|
||||||
|
|
||||||
|
$(DUMP): $(ELF)
|
||||||
|
@echo " OBJDUMP.9 $@"
|
||||||
|
$(V)$(OBJDUMP) -h -C -S $< > $@
|
||||||
|
|
||||||
|
dump: $(DUMP)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@echo " CLEAN.9"
|
||||||
|
$(V)$(RM) $(ELF) $(DUMP) $(MAP) $(BUILDDIR)
|
||||||
|
|
||||||
|
# Rules
|
||||||
|
# -----
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.s.o : %.s
|
||||||
|
@echo " AS.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.c.o : %.c
|
||||||
|
@echo " CC.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.arm.c.o : %.arm.c
|
||||||
|
@echo " CC.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.cpp.o : %.cpp
|
||||||
|
@echo " CXX.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp
|
||||||
|
@echo " CXX.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $<
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin
|
||||||
|
@echo " BIN2C.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.bin.o $(BUILDDIR)/$*_bin.c
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.nft2.o $(BUILDDIR)/%_nft2.h : %.nft2
|
||||||
|
@echo " BIN2C.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $< $(@D)
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.nft2.o $(BUILDDIR)/$*_nft2.c
|
||||||
|
|
||||||
|
$(BUILDDIR)/%.png.o $(BUILDDIR)/%.h : %.png %.grit
|
||||||
|
@echo " GRIT.9 $<"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
$(V)$(BLOCKSDS)/tools/grit/grit $< -ftc -W1 -o$(BUILDDIR)/$*
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.c
|
||||||
|
$(V)touch $(BUILDDIR)/$*.png.o $(BUILDDIR)/$*.h
|
||||||
|
|
||||||
|
$(SOUNDBANKDIR)/soundbank.h: $(SOURCES_AUDIO)
|
||||||
|
@echo " MMUTIL $^"
|
||||||
|
@$(MKDIR) -p $(@D)
|
||||||
|
@$(BLOCKSDS)/tools/mmutil/mmutil $^ -d \
|
||||||
|
-o$(SOUNDBANKDIR)/soundbank.bin -h$(SOUNDBANKDIR)/soundbank.h
|
||||||
|
|
||||||
|
$(SOUNDBANKDIR)/soundbank.c.o: $(SOUNDBANKDIR)/soundbank.h
|
||||||
|
@echo " BIN2C soundbank.bin"
|
||||||
|
$(V)$(BLOCKSDS)/tools/bin2c/bin2c $(SOUNDBANKDIR)/soundbank.bin \
|
||||||
|
$(SOUNDBANKDIR)
|
||||||
|
@echo " CC.9 soundbank_bin.c"
|
||||||
|
$(V)$(CC) $(CFLAGS) -MMD -MP -c -o $(SOUNDBANKDIR)/soundbank.c.o \
|
||||||
|
$(SOUNDBANKDIR)/soundbank_bin.c
|
||||||
|
|
||||||
|
# All assets must be built before the source code
|
||||||
|
# -----------------------------------------------
|
||||||
|
|
||||||
|
$(SOURCES_S) $(SOURCES_C) $(SOURCES_CPP): $(HEADERS_ASSETS)
|
||||||
|
|
||||||
|
# Include dependency files if they exist
|
||||||
|
# --------------------------------------
|
||||||
|
|
||||||
|
-include $(DEPS)
|
||||||
81
README.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Pico Launcher
|
||||||
|
This repository contains Pico Launcher, which is a front-end for [Pico Loader](https://github.com/LNH-team/pico-loader).
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Can load homebrew and retail games using [Pico Loader](https://github.com/LNH-team/pico-loader).
|
||||||
|
- Various display modes
|
||||||
|
- Horizontal and vertical icon grid
|
||||||
|
- Banner list
|
||||||
|
- Coverflow
|
||||||
|
- [File associations](docs/FileAssociations.md)
|
||||||
|
- [Covers](docs/Covers.md)
|
||||||
|
- [Material Design 3 and custom themes](docs/Themes.md)
|
||||||
|
- Support for background music (see [Themes](docs/Themes.md))
|
||||||
|
|
||||||
|
General usage documentation can be found here: [Usage](docs/Usage.md).
|
||||||
|
|
||||||
|
## Setup & Configuration
|
||||||
|
We recommend using WSL (Windows Subsystem for Linux), or MSYS2 to compile this repository.
|
||||||
|
The steps provided will assume you already have one of those environments set up.
|
||||||
|
|
||||||
|
1. Install [BlocksDS](https://blocksds.skylyrac.net/docs/setup/options/)
|
||||||
|
|
||||||
|
## Compiling
|
||||||
|
|
||||||
|
1. Run `make`
|
||||||
|
|
||||||
|
The launcher can be found in the root directory under the name `LAUNCHER.nds`.
|
||||||
|
|
||||||
|
2. Copy `LAUNCHER.nds` to your SD card.
|
||||||
|
- If you are using DSpico, rename to `_picoboot.nds` and place it in the root of your SD card.
|
||||||
|
3. Copy the `_pico` pico folder to the root of your SD card.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> To use Pico Launcher, the Pico Loader files (`aplist.bin`, `savelist.bin`, `picoLoader7.bin` and `picoLoader9.bin`) must also be present in the `/_pico` folder on your SD card.
|
||||||
|
|
||||||
|
For DSpico the final directory structure will look like this:
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── _pico
|
||||||
|
│ ├── themes
|
||||||
|
│ │ ├── material
|
||||||
|
│ │ │ └── theme.json
|
||||||
|
│ │ └── raspberry
|
||||||
|
│ │ ├── bannerListCell.bin
|
||||||
|
│ │ ├── bannerListCellPltt.bin
|
||||||
|
│ │ ├── bannerListCellSelected.bin
|
||||||
|
│ │ ├── bannerListCellSelectedPltt.bin
|
||||||
|
│ │ ├── bottombg.bin
|
||||||
|
│ │ ├── gridcell.bin
|
||||||
|
│ │ ├── gridcellPltt.bin
|
||||||
|
│ │ ├── gridcellSelected.bin
|
||||||
|
│ │ ├── gridcellSelectedPltt.bin
|
||||||
|
│ │ ├── scrim.bin
|
||||||
|
│ │ ├── scrimPltt.bin
|
||||||
|
│ │ ├── theme.json
|
||||||
|
│ │ └── topbg.bin
|
||||||
|
│ ├── aplist.bin
|
||||||
|
│ ├── savelist.bin
|
||||||
|
│ ├── picoLoader7.bin
|
||||||
|
│ └── picoLoader9.bin
|
||||||
|
└── _picoboot.nds
|
||||||
|
```
|
||||||
|
Note: If you want to play DSiWare on the DSpico, additional files are required. See the [Pico Loader](https://github.com/LNH-team/pico-loader) readme for more information.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Icons by [icons8](https://icons8.com/)
|
||||||
|
|
||||||
|
This project is licensed under the Zlib license. For details, see `LICENSE.txt`.
|
||||||
|
|
||||||
|
Additional licenses may apply to the project. For details, see the `license` directory.
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
- [@Gericom](https://github.com/Gericom)
|
||||||
|
- [@XLuma](https://github.com/XLuma)
|
||||||
|
- [@Dartz150](https://github.com/Dartz150)
|
||||||
|
- [@lifehackerhansol](https://github.com/lifehackerhansol)
|
||||||
12
_pico/themes/material/theme.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "material",
|
||||||
|
"name": "Material Design 3",
|
||||||
|
"description": "Theme based on Google's Material Design 3.",
|
||||||
|
"author": "Gericom",
|
||||||
|
"primaryColor": {
|
||||||
|
"r": 138,
|
||||||
|
"g": 217,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"darkTheme": false
|
||||||
|
}
|
||||||
BIN
_pico/themes/raspberry/bannerListCell.bin
Normal file
BIN
_pico/themes/raspberry/bannerListCellPltt.bin
Normal file
BIN
_pico/themes/raspberry/bannerListCellSelected.bin
Normal file
BIN
_pico/themes/raspberry/bannerListCellSelectedPltt.bin
Normal file
641
_pico/themes/raspberry/bottombg.bin
Normal file
BIN
_pico/themes/raspberry/gridcell.bin
Normal file
BIN
_pico/themes/raspberry/gridcellPltt.bin
Normal file
BIN
_pico/themes/raspberry/gridcellSelected.bin
Normal file
BIN
_pico/themes/raspberry/gridcellSelectedPltt.bin
Normal file
BIN
_pico/themes/raspberry/scrim.bin
Normal file
BIN
_pico/themes/raspberry/scrimPltt.bin
Normal file
12
_pico/themes/raspberry/theme.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "custom",
|
||||||
|
"name": "Raspberry",
|
||||||
|
"description": "Theme based on raspberries.",
|
||||||
|
"author": "Gericom",
|
||||||
|
"primaryColor": {
|
||||||
|
"r": 138,
|
||||||
|
"g": 217,
|
||||||
|
"b": 255
|
||||||
|
},
|
||||||
|
"darkTheme": false
|
||||||
|
}
|
||||||
20
_pico/themes/raspberry/topbg.bin
Normal file
340
arm7/dldi_ds_arm7.ld
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
/* Copyright (C) 2014-2024 Free Software Foundation, Inc.
|
||||||
|
Copying and distribution of this script, with or without modification,
|
||||||
|
are permitted in any medium without royalty provided the copyright
|
||||||
|
notice and this notice are preserved. */
|
||||||
|
|
||||||
|
/* SPDX-License-Identifier: MPL-2.0 AND FSFAP */
|
||||||
|
|
||||||
|
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
|
||||||
|
OUTPUT_ARCH(arm)
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
/* User-configurable symbols. */
|
||||||
|
|
||||||
|
/* The size, in bytes, of the amount of shared WRAM used by ARM7 code. */
|
||||||
|
/* Only 0 and 32768 are valid values. */
|
||||||
|
PROVIDE(__shared_wram_size = 16384);
|
||||||
|
ASSERT(__shared_wram_size == 0 || __shared_wram_size == 16384, "ARM7 shared WRAM size must be 0 KB or 32 KB");
|
||||||
|
|
||||||
|
/* The size, in bytes, of the reserved section at the end of IWRAM. */
|
||||||
|
/* Traditionally, ARM7 reserves 0x40 bytes here. */
|
||||||
|
PROVIDE(__iwram_reserved_size = 0x40);
|
||||||
|
ASSERT((__iwram_reserved_size & 3) == 0, "__iwram_reserved_size must be a multiple of 4");
|
||||||
|
|
||||||
|
/* ARM supervisor (SWI calls) stack size. */
|
||||||
|
PROVIDE(__svc_stack_size = 0x100);
|
||||||
|
ASSERT((__svc_stack_size & 3) == 0, "__svc_stack_size must be a multiple of 4");
|
||||||
|
|
||||||
|
/* ARM interrupt handler stack size. */
|
||||||
|
PROVIDE(__irq_stack_size = 0x100);
|
||||||
|
ASSERT((__irq_stack_size & 3) == 0, "__irq_stack_size must be a multiple of 4");
|
||||||
|
|
||||||
|
PHDRS {
|
||||||
|
crt0 PT_LOAD FLAGS(7);
|
||||||
|
arm7 PT_LOAD FLAGS(7);
|
||||||
|
arm7i PT_LOAD FLAGS(0x100007); /* (DSi flag for ndstool | 7) */
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMORY {
|
||||||
|
ewram : ORIGIN = 0x02380000, LENGTH = 12M - 512K
|
||||||
|
iwram : ORIGIN = 0x03800000 - __shared_wram_size, LENGTH = 65536 + __shared_wram_size
|
||||||
|
|
||||||
|
twl_ewram : ORIGIN = 0x02e80000, LENGTH = 512K - 64K
|
||||||
|
twl_iwram : ORIGIN = 0x03000000, LENGTH = 256K
|
||||||
|
}
|
||||||
|
|
||||||
|
__iwram_start = ORIGIN(iwram);
|
||||||
|
__iwram_top = ORIGIN(iwram) + LENGTH(iwram);
|
||||||
|
|
||||||
|
__sp_irq = __iwram_top - __iwram_reserved_size;
|
||||||
|
__sp_svc = __sp_irq - __irq_stack_size;
|
||||||
|
__sp_usr = __sp_svc - __svc_stack_size;
|
||||||
|
|
||||||
|
__irq_flags = 0x04000000 - 8;
|
||||||
|
__irq_flagsaux = 0x04000000 - 0x40;
|
||||||
|
__irq_vector = 0x04000000 - 4;
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
|
||||||
|
.twl :
|
||||||
|
{
|
||||||
|
__arm7i_lma__ = LOADADDR(.twl);
|
||||||
|
__arm7i_start__ = .;
|
||||||
|
*(.twl)
|
||||||
|
*(.twl.text .twl.text.*)
|
||||||
|
*(.twl.rodata .twl.rodata.*)
|
||||||
|
*(.twl.data .twl.data.*)
|
||||||
|
*.twl*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||||
|
*.twl*(.rodata)
|
||||||
|
*.twl*(.roda)
|
||||||
|
*.twl*(.rodata.*)
|
||||||
|
*.twl*(.data)
|
||||||
|
*.twl*(.data.*)
|
||||||
|
*.twl*(.gnu.linkonce.d*)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__arm7i_end__ = .;
|
||||||
|
} >twl_iwram AT>twl_ewram :arm7i
|
||||||
|
|
||||||
|
.twl_bss ALIGN(4) (NOLOAD) :
|
||||||
|
{
|
||||||
|
__twl_bss_start__ = .;
|
||||||
|
*(.twl_bss .twl_bss.*)
|
||||||
|
*(.twl.bss .twl.bss.*)
|
||||||
|
*.twl.*(.dynbss)
|
||||||
|
*.twl.*(.gnu.linkonce.b*)
|
||||||
|
*.twl.*(.bss*)
|
||||||
|
*.twl.*(COMMON)
|
||||||
|
. = ALIGN(4);
|
||||||
|
__twl_bss_end__ = .;
|
||||||
|
} >twl_iwram :NONE
|
||||||
|
|
||||||
|
.twl_noinit ALIGN(4) (NOLOAD):
|
||||||
|
{
|
||||||
|
__twl_noinit_start__ = ABSOLUTE(.);
|
||||||
|
*(.twl_noinit .twl_noinit.*)
|
||||||
|
*(.twl.noinit .twl.noinit.*)
|
||||||
|
*.twl*(.noinit)
|
||||||
|
*.twl*(.noinit.*)
|
||||||
|
*.twl*(.gnu.linkonce.n.*)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
__twl_noinit_end__ = ABSOLUTE(.);
|
||||||
|
__twl_end__ = ABSOLUTE(.);
|
||||||
|
} >twl_iwram :NONE
|
||||||
|
|
||||||
|
.crt0 :
|
||||||
|
{
|
||||||
|
KEEP (*(.crt0))
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >ewram :crt0
|
||||||
|
|
||||||
|
.text :
|
||||||
|
{
|
||||||
|
__arm7_lma__ = LOADADDR(.text);
|
||||||
|
__arm7_start__ = .;
|
||||||
|
KEEP (*(SORT_NONE(.init)))
|
||||||
|
*(.plt)
|
||||||
|
*(.text .stub .text.* .gnu.linkonce.t.*)
|
||||||
|
KEEP (*(.text.*personality*))
|
||||||
|
/* .gnu.warning sections are handled specially by elf32.em. */
|
||||||
|
*(.gnu.warning)
|
||||||
|
*(.glue_7t) *(.glue_7) *(.v4_bx)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram :arm7
|
||||||
|
|
||||||
|
.fini :
|
||||||
|
{
|
||||||
|
KEEP (*(.fini))
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.rodata :
|
||||||
|
{
|
||||||
|
*(.rodata)
|
||||||
|
*all.rodata*(*)
|
||||||
|
*(.roda)
|
||||||
|
*(.rodata.*)
|
||||||
|
*(.gnu.linkonce.r*)
|
||||||
|
SORT(CONSTRUCTORS)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >iwram AT>ewram
|
||||||
|
|
||||||
|
.ARM.exidx : {
|
||||||
|
__exidx_start = .;
|
||||||
|
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
|
||||||
|
__exidx_end = .;
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
/* Ensure the __preinit_array_start label is properly aligned. We
|
||||||
|
could instead move the label definition inside the section, but
|
||||||
|
the linker would then create the section even if it turns out to
|
||||||
|
be empty, which isn't pretty. */
|
||||||
|
. = ALIGN(32 / 8);
|
||||||
|
.init_array :
|
||||||
|
{
|
||||||
|
PROVIDE (__preinit_array_start = .);
|
||||||
|
PROVIDE (__bothinit_array_start = .);
|
||||||
|
KEEP (*(.preinit_array))
|
||||||
|
PROVIDE (__preinit_array_end = .);
|
||||||
|
|
||||||
|
PROVIDE (__init_array_start = .);
|
||||||
|
KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*)))
|
||||||
|
KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors))
|
||||||
|
PROVIDE (__init_array_end = .);
|
||||||
|
PROVIDE (__bothinit_array_end = .);
|
||||||
|
} >iwram AT>ewram
|
||||||
|
.fini_array :
|
||||||
|
{
|
||||||
|
PROVIDE (__fini_array_start = .);
|
||||||
|
KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*)))
|
||||||
|
KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors))
|
||||||
|
/* Required by pico-exitprocs.c. */
|
||||||
|
KEEP (*(.fini_array*))
|
||||||
|
PROVIDE (__fini_array_end = .);
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.ctors :
|
||||||
|
{
|
||||||
|
/* gcc uses crtbegin.o to find the start of
|
||||||
|
the constructors, so we make sure it is
|
||||||
|
first. Because this is a wildcard, it
|
||||||
|
doesn't matter if the user does not
|
||||||
|
actually link against crtbegin.o; the
|
||||||
|
linker won't look for a file to match a
|
||||||
|
wildcard. The wildcard also means that it
|
||||||
|
doesn't matter which directory crtbegin.o
|
||||||
|
is in. */
|
||||||
|
KEEP (*crtbegin.o(.ctors))
|
||||||
|
KEEP (*crtbegin?.o(.ctors))
|
||||||
|
/* We don't want to include the .ctor section from
|
||||||
|
the crtend.o file until after the sorted ctors.
|
||||||
|
The .ctor section from the crtend file contains the
|
||||||
|
end of ctors marker and it must be last */
|
||||||
|
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
|
||||||
|
KEEP (*(SORT(.ctors.*)))
|
||||||
|
KEEP (*(.ctors))
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.dtors :
|
||||||
|
{
|
||||||
|
KEEP (*crtbegin.o(.dtors))
|
||||||
|
KEEP (*crtbegin?.o(.dtors))
|
||||||
|
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
|
||||||
|
KEEP (*(SORT(.dtors.*)))
|
||||||
|
KEEP (*(.dtors))
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.eh_frame :
|
||||||
|
{
|
||||||
|
KEEP (*(.eh_frame))
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.gcc_except_table :
|
||||||
|
{
|
||||||
|
*(.gcc_except_table .gcc_except_table.*)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
} >iwram AT>ewram
|
||||||
|
.got : { *(.got.plt) *(.got) } >iwram AT>ewram
|
||||||
|
|
||||||
|
.data ALIGN(4) : {
|
||||||
|
__data_start = ABSOLUTE(.);
|
||||||
|
*(.data)
|
||||||
|
*(.data.*)
|
||||||
|
*(.gnu.linkonce.d*)
|
||||||
|
CONSTRUCTORS
|
||||||
|
. = ALIGN(4);
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
.tdata ALIGN(4) :
|
||||||
|
{
|
||||||
|
__tdata_start = ABSOLUTE(.);
|
||||||
|
*(.tdata .tdata.* .gnu.linkonce.td.*)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
__tdata_end = ABSOLUTE(.);
|
||||||
|
__data_end = . ;
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
__tdata_size = __tdata_end - __tdata_start ;
|
||||||
|
|
||||||
|
.tbss ALIGN(4) (NOLOAD) :
|
||||||
|
{
|
||||||
|
__tbss_start = ABSOLUTE(.);
|
||||||
|
*(.tbss .tbss.* .gnu.linkonce.tb.*)
|
||||||
|
*(.tcommon)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
__tbss_end = ABSOLUTE(.);
|
||||||
|
} >iwram AT>ewram
|
||||||
|
|
||||||
|
__tbss_size = __tbss_end - __tbss_start ;
|
||||||
|
|
||||||
|
.bss ALIGN(4) (NOLOAD) :
|
||||||
|
{
|
||||||
|
__arm7_end__ = .;
|
||||||
|
__bss_start = ABSOLUTE(.);
|
||||||
|
__bss_start__ = ABSOLUTE(.);
|
||||||
|
*(.dynbss)
|
||||||
|
*(.gnu.linkonce.b*)
|
||||||
|
*(.bss*)
|
||||||
|
*(COMMON)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
__bss_end__ = ABSOLUTE(.);
|
||||||
|
} >iwram
|
||||||
|
|
||||||
|
.noinit (NOLOAD):
|
||||||
|
{
|
||||||
|
__noinit_start = ABSOLUTE(.);
|
||||||
|
*(.noinit .noinit.* .gnu.linkonce.n.*)
|
||||||
|
. = ALIGN(4); /* REQUIRED. LD is flaky without it. */
|
||||||
|
__noinit_end = ABSOLUTE(.);
|
||||||
|
} >iwram
|
||||||
|
|
||||||
|
/* Space reserved for the thread local storage of main() */
|
||||||
|
.tls ALIGN(4) (NOLOAD) :
|
||||||
|
{
|
||||||
|
__tls_start = ABSOLUTE(.);
|
||||||
|
. = . + __tdata_size + __tbss_size;
|
||||||
|
__tls_end = ABSOLUTE(.);
|
||||||
|
__end__ = ABSOLUTE(.);
|
||||||
|
} >iwram
|
||||||
|
|
||||||
|
__tls_size = __tls_end - __tls_start;
|
||||||
|
|
||||||
|
HIDDEN(__arm7_size__ = __arm7_end__ - __arm7_start__);
|
||||||
|
HIDDEN(__arm7i_size__ = __arm7i_end__ - __arm7i_start__);
|
||||||
|
HIDDEN(__bss_size__ = __bss_end__ - __bss_start__);
|
||||||
|
HIDDEN(__twl_bss_size__ = __twl_bss_end__ - __twl_bss_start__);
|
||||||
|
|
||||||
|
/* 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) }
|
||||||
|
.comment 0 (INFO) : { *(.comment); LINKER_VERSION; }
|
||||||
|
.gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) }
|
||||||
|
/* 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 .gnu.linkonce.wi.*) }
|
||||||
|
.debug_abbrev 0 : { *(.debug_abbrev) }
|
||||||
|
.debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) }
|
||||||
|
.debug_frame 0 : { *(.debug_frame) }
|
||||||
|
.debug_str 0 : { *(.debug_str) }
|
||||||
|
.debug_loc 0 : { *(.debug_loc) }
|
||||||
|
.debug_macinfo 0 : { *(.debug_macinfo) }
|
||||||
|
/* SGI/MIPS DWARF 2 extensions. */
|
||||||
|
.debug_weaknames 0 : { *(.debug_weaknames) }
|
||||||
|
.debug_funcnames 0 : { *(.debug_funcnames) }
|
||||||
|
.debug_typenames 0 : { *(.debug_typenames) }
|
||||||
|
.debug_varnames 0 : { *(.debug_varnames) }
|
||||||
|
/* DWARF 3. */
|
||||||
|
.debug_pubtypes 0 : { *(.debug_pubtypes) }
|
||||||
|
.debug_ranges 0 : { *(.debug_ranges) }
|
||||||
|
/* DWARF 5. */
|
||||||
|
.debug_addr 0 : { *(.debug_addr) }
|
||||||
|
.debug_line_str 0 : { *(.debug_line_str) }
|
||||||
|
.debug_loclists 0 : { *(.debug_loclists) }
|
||||||
|
.debug_macro 0 : { *(.debug_macro) }
|
||||||
|
.debug_names 0 : { *(.debug_names) }
|
||||||
|
.debug_rnglists 0 : { *(.debug_rnglists) }
|
||||||
|
.debug_str_offsets 0 : { *(.debug_str_offsets) }
|
||||||
|
.debug_sup 0 : { *(.debug_sup) }
|
||||||
|
.ARM.attributes 0 : { KEEP (*(.ARM.attributes)) KEEP (*(.gnu.attributes)) }
|
||||||
|
.note.gnu.arm.ident 0 : { KEEP (*(.note.gnu.arm.ident)) }
|
||||||
|
/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) }
|
||||||
|
}
|
||||||
23
arm7/dldi_ds_arm7.specs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
%include <picolibc.specs>
|
||||||
|
|
||||||
|
%rename cc1plus blocksds_cc1plus
|
||||||
|
%rename cpp blocksds_cpp
|
||||||
|
%rename link blocksds_link
|
||||||
|
|
||||||
|
*cpp:
|
||||||
|
-D__NDS__ -D__BLOCKSDS__ -DARM7 %(blocksds_cpp)
|
||||||
|
|
||||||
|
*cc1_cpu:
|
||||||
|
-mcpu=arm7tdmi
|
||||||
|
|
||||||
|
*cc1plus:
|
||||||
|
%(cpp) %(blocksds_cc1plus)
|
||||||
|
|
||||||
|
*link:
|
||||||
|
%(blocksds_link) -T arm7/dldi_ds_arm7.ld --gc-sections --no-warn-rwx-segments
|
||||||
|
|
||||||
|
*startfile:
|
||||||
|
%:getenv(BLOCKSDS /sys/crts/ds_arm7_crt0%O)
|
||||||
|
|
||||||
|
*lib:
|
||||||
|
%(libgcc)
|
||||||
8
arm7/source/Arm7State.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// @brief Enum representing the arm7 state.
|
||||||
|
enum class Arm7State
|
||||||
|
{
|
||||||
|
Idle,
|
||||||
|
ExitRequested
|
||||||
|
};
|
||||||
12
arm7/source/ExitMode.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/// @brief Enum representing the exit mode of launcher.
|
||||||
|
enum class ExitMode
|
||||||
|
{
|
||||||
|
/// @brief Reset the system (DSi mode only).
|
||||||
|
Reset,
|
||||||
|
/// @brief Power off the system.
|
||||||
|
PowerOff,
|
||||||
|
/// @brief Launch an application through pico loader.
|
||||||
|
PicoLoader
|
||||||
|
};
|
||||||
2
arm7/source/common.h
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <nds/ndstypes.h>
|
||||||
16
arm7/source/dldi.s
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.global _dldi_start
|
||||||
|
.equ _dldi_start, 0x037F8000
|
||||||
|
.global _io_dldi
|
||||||
|
.equ _io_dldi, (_dldi_start + 0x60)
|
||||||
|
.global _DLDI_startup_ptr
|
||||||
|
.equ _DLDI_startup_ptr, (_io_dldi + 0x8)
|
||||||
|
.global _DLDI_isInserted_ptr
|
||||||
|
.equ _DLDI_isInserted_ptr, (_io_dldi + 0xC)
|
||||||
|
.global _DLDI_readSectors_ptr
|
||||||
|
.equ _DLDI_readSectors_ptr, (_io_dldi + 0x10)
|
||||||
|
.global _DLDI_writeSectors_ptr
|
||||||
|
.equ _DLDI_writeSectors_ptr, (_io_dldi + 0x14)
|
||||||
|
.global _DLDI_clearStatus_ptr
|
||||||
|
.equ _DLDI_clearStatus_ptr, (_io_dldi + 0x18)
|
||||||
|
.global _DLDI_shutdown_ptr
|
||||||
|
.equ _DLDI_shutdown_ptr, (_io_dldi + 0x1C)
|
||||||
46
arm7/source/ipcServices/DldiIpcService.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <nds/disc_io.h>
|
||||||
|
#include "DldiIpcService.h"
|
||||||
|
|
||||||
|
extern FN_MEDIUM_STARTUP _DLDI_startup_ptr;
|
||||||
|
extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr;
|
||||||
|
extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr;
|
||||||
|
|
||||||
|
void DldiIpcService::HandleMessage(u32 data)
|
||||||
|
{
|
||||||
|
auto cmd = reinterpret_cast<const dldi_ipc_cmd_t*>(data << 2);
|
||||||
|
switch (cmd->cmd)
|
||||||
|
{
|
||||||
|
case DLDI_IPC_CMD_SETUP:
|
||||||
|
SetupDldi(cmd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DLDI_IPC_CMD_READ_SECTORS:
|
||||||
|
ReadSectors(cmd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DLDI_IPC_CMD_WRITE_SECTORS:
|
||||||
|
WriteSectors(cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DldiIpcService::SetupDldi(const dldi_ipc_cmd_t* cmd) const
|
||||||
|
{
|
||||||
|
memcpy((void*)0x037F8000, cmd->buffer, 16 * 1024);
|
||||||
|
bool result = _DLDI_startup_ptr();
|
||||||
|
SendResponseMessage(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DldiIpcService::ReadSectors(const dldi_ipc_cmd_t* cmd) const
|
||||||
|
{
|
||||||
|
_DLDI_readSectors_ptr(cmd->sector, cmd->count, cmd->buffer);
|
||||||
|
SendResponseMessage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DldiIpcService::WriteSectors(const dldi_ipc_cmd_t* cmd) const
|
||||||
|
{
|
||||||
|
_DLDI_writeSectors_ptr(cmd->sector, cmd->count, cmd->buffer);
|
||||||
|
SendResponseMessage(0);
|
||||||
|
}
|
||||||
19
arm7/source/ipcServices/DldiIpcService.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ipc/ThreadIpcService.h"
|
||||||
|
#include "dldiIpcCommand.h"
|
||||||
|
#include "ipcChannels.h"
|
||||||
|
|
||||||
|
class DldiIpcService : public ThreadIpcService
|
||||||
|
{
|
||||||
|
u32 _threadStack[128];
|
||||||
|
|
||||||
|
void SetupDldi(const dldi_ipc_cmd_t* cmd) const;
|
||||||
|
void ReadSectors(const dldi_ipc_cmd_t* cmd) const;
|
||||||
|
void WriteSectors(const dldi_ipc_cmd_t* cmd) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DldiIpcService()
|
||||||
|
: ThreadIpcService(IPC_CHANNEL_DLDI, 6, _threadStack, sizeof(_threadStack)) { }
|
||||||
|
|
||||||
|
void HandleMessage(u32 data) override;
|
||||||
|
};
|
||||||
19
arm7/source/ipcServices/DsiSdIpcService.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ipc/ThreadIpcService.h"
|
||||||
|
#include "dsiSdIpcCommand.h"
|
||||||
|
#include "ipcChannels.h"
|
||||||
|
|
||||||
|
class DsiSdIpcService : public ThreadIpcService
|
||||||
|
{
|
||||||
|
u32 _threadStack[128];
|
||||||
|
|
||||||
|
void ReadSectors(const dsisd_ipc_cmd_t* cmd) const;
|
||||||
|
void WriteSectors(const dsisd_ipc_cmd_t* cmd) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DsiSdIpcService()
|
||||||
|
: ThreadIpcService(IPC_CHANNEL_DSI_SD, 5, _threadStack, sizeof(_threadStack)) { }
|
||||||
|
|
||||||
|
void Start() override;
|
||||||
|
void HandleMessage(u32 data) override;
|
||||||
|
};
|
||||||
36
arm7/source/ipcServices/DsiSdIpcService.twl.cpp
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#include <nds/ndstypes.h>
|
||||||
|
#include "../mmc/sdmmc.h"
|
||||||
|
#include "DsiSdIpcService.h"
|
||||||
|
|
||||||
|
void DsiSdIpcService::Start()
|
||||||
|
{
|
||||||
|
pico_SDMMC_init(SDMMC_DEV_CARD);
|
||||||
|
ThreadIpcService::Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DsiSdIpcService::HandleMessage(u32 data)
|
||||||
|
{
|
||||||
|
auto cmd = reinterpret_cast<const dsisd_ipc_cmd_t*>(data << 2);
|
||||||
|
switch (cmd->cmd)
|
||||||
|
{
|
||||||
|
case DSI_SD_IPC_CMD_READ_SECTORS:
|
||||||
|
ReadSectors(cmd);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DSI_SD_IPC_CMD_WRITE_SECTORS:
|
||||||
|
WriteSectors(cmd);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DsiSdIpcService::ReadSectors(const dsisd_ipc_cmd_t* cmd) const
|
||||||
|
{
|
||||||
|
pico_SDMMC_readSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count);
|
||||||
|
SendResponseMessage(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DsiSdIpcService::WriteSectors(const dsisd_ipc_cmd_t* cmd) const
|
||||||
|
{
|
||||||
|
pico_SDMMC_writeSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count);
|
||||||
|
SendResponseMessage(0);
|
||||||
|
}
|
||||||
9
arm7/source/ipcServices/RtcIpcService.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include <libtwl/sio/sioRtc.h>
|
||||||
|
#include "RtcIpcService.h"
|
||||||
|
|
||||||
|
void RtcIpcService::HandleMessage(u32 data)
|
||||||
|
{
|
||||||
|
rtc_readDateTime(reinterpret_cast<rtc_datetime_t*>(data << 2));
|
||||||
|
SendResponseMessage(1);
|
||||||
|
}
|
||||||
14
arm7/source/ipcServices/RtcIpcService.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ipc/ThreadIpcService.h"
|
||||||
|
#include "ipcChannels.h"
|
||||||
|
|
||||||
|
class RtcIpcService : public ThreadIpcService
|
||||||
|
{
|
||||||
|
u32 _threadStack[128];
|
||||||
|
|
||||||
|
public:
|
||||||
|
RtcIpcService()
|
||||||
|
: ThreadIpcService(IPC_CHANNEL_RTC, 10, _threadStack, sizeof(_threadStack)) { }
|
||||||
|
|
||||||
|
void HandleMessage(u32 data) override;
|
||||||
|
};
|
||||||
49
arm7/source/ipcServices/SoundIpcService.cpp
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include <libtwl/sound/soundChannel.h>
|
||||||
|
#include "soundIpcCommand.h"
|
||||||
|
#include "SoundIpcService.h"
|
||||||
|
|
||||||
|
void SoundIpcService::OnMessageReceived(u32 data)
|
||||||
|
{
|
||||||
|
const u32* commandList = reinterpret_cast<const u32*>(data);
|
||||||
|
u32 cmdCount = *commandList++;
|
||||||
|
for (u32 i = 0; i < cmdCount; i++)
|
||||||
|
{
|
||||||
|
u32 cmdValue = *commandList++;
|
||||||
|
u32 cmd = cmdValue & 0xFF;
|
||||||
|
u32 cmdArg = cmdValue >> 8;
|
||||||
|
switch (cmd)
|
||||||
|
{
|
||||||
|
case SND_IPC_CMD_START_CHANNELS:
|
||||||
|
{
|
||||||
|
u32 channelsMask = cmdArg;
|
||||||
|
for (u32 j = 0; j < 16; j++)
|
||||||
|
{
|
||||||
|
if (channelsMask & (1u << j))
|
||||||
|
snd_startChannel(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SND_IPC_CMD_STOP_CHANNELS:
|
||||||
|
{
|
||||||
|
u32 channelsMask = cmdArg;
|
||||||
|
for (u32 j = 0; j < 16; j++)
|
||||||
|
{
|
||||||
|
if (channelsMask & (1u << j))
|
||||||
|
snd_stopChannel(j);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SND_IPC_CMD_SETUP_CHANNEL:
|
||||||
|
{
|
||||||
|
u32 channel = cmdArg;
|
||||||
|
REG_SOUNDxSAD(channel) = *commandList++;
|
||||||
|
REG_SOUNDxTMR(channel) = *commandList++;
|
||||||
|
REG_SOUNDxPNT(channel) = *commandList++;
|
||||||
|
REG_SOUNDxLEN(channel) = *commandList++;
|
||||||
|
REG_SOUNDxCNT(channel) = *commandList++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
arm7/source/ipcServices/SoundIpcService.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "ipc/IpcService.h"
|
||||||
|
#include "ipcChannels.h"
|
||||||
|
|
||||||
|
class SoundIpcService : public IpcService
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SoundIpcService()
|
||||||
|
: IpcService(IPC_CHANNEL_SOUND) { }
|
||||||
|
|
||||||
|
void OnMessageReceived(u32 data) override;
|
||||||
|
};
|
||||||
241
arm7/source/main.cpp
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include <nds/system.h>
|
||||||
|
#include <libtwl/sound/sound.h>
|
||||||
|
#include <libtwl/sound/soundChannel.h>
|
||||||
|
#include <libtwl/sound/soundCapture.h>
|
||||||
|
#include <libtwl/rtos/rtosIrq.h>
|
||||||
|
#include <libtwl/rtos/rtosThread.h>
|
||||||
|
#include <libtwl/rtos/rtosEvent.h>
|
||||||
|
#include <libtwl/timer/timer.h>
|
||||||
|
#include <libtwl/sound/sound.h>
|
||||||
|
#include <libtwl/ipc/ipcSync.h>
|
||||||
|
#include <libtwl/ipc/ipcFifoSystem.h>
|
||||||
|
#include <libtwl/sys/sysPower.h>
|
||||||
|
#include <libtwl/sio/sioRtc.h>
|
||||||
|
#include <libtwl/sio/sio.h>
|
||||||
|
#include <libtwl/gfx/gfxStatus.h>
|
||||||
|
#include <libtwl/mem/memSwap.h>
|
||||||
|
#include <libtwl/i2c/i2cMcu.h>
|
||||||
|
#include <libtwl/spi/spiPmic.h>
|
||||||
|
#include "logger/PlainLogger.h"
|
||||||
|
#include "logger/NocashOutputStream.h"
|
||||||
|
#include "logger/NullLogger.h"
|
||||||
|
#include "logger/ThreadSafeLogger.h"
|
||||||
|
#include "picoLoaderBootstrap.h"
|
||||||
|
#include "sharedMemory.h"
|
||||||
|
#include "ipcServices/DsiSdIpcService.h"
|
||||||
|
#include "ipcServices/DldiIpcService.h"
|
||||||
|
#include "ipcServices/SoundIpcService.h"
|
||||||
|
#include "ipcServices/RtcIpcService.h"
|
||||||
|
#include "ExitMode.h"
|
||||||
|
#include "Arm7State.h"
|
||||||
|
#include "mmc/tmio.h"
|
||||||
|
|
||||||
|
static NocashOutputStream sNocashOutputStream;
|
||||||
|
static PlainLogger sPlainLogger = PlainLogger(LogLevel::All, std::unique_ptr<IOutputStream>(&sNocashOutputStream));
|
||||||
|
static ThreadSafeLogger sThreadSafeLogger = ThreadSafeLogger(std::unique_ptr<ILogger>(&sPlainLogger));
|
||||||
|
|
||||||
|
static DsiSdIpcService sDsiSdIpcService;
|
||||||
|
static DldiIpcService sDldiIpcService;
|
||||||
|
static SoundIpcService sSoundIpcService;
|
||||||
|
static RtcIpcService sRtcIpcService;
|
||||||
|
|
||||||
|
ILogger* gLogger = &sThreadSafeLogger;
|
||||||
|
|
||||||
|
static rtos_event_t sVBlankEvent;
|
||||||
|
static ExitMode sExitMode;
|
||||||
|
static Arm7State sState;
|
||||||
|
static volatile u8 sMcuIrqFlag = false;
|
||||||
|
|
||||||
|
static void vblankIrq(u32 irqMask)
|
||||||
|
{
|
||||||
|
rtos_signalEvent(&sVBlankEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void vcountIrq(u32 irqMask)
|
||||||
|
{
|
||||||
|
SHARED_KEY_XY = REG_RCNT0_H;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mcuIrq(u32 irq2Mask)
|
||||||
|
{
|
||||||
|
sMcuIrqFlag = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void checkMcuIrq(void)
|
||||||
|
{
|
||||||
|
// mcu only exists in DSi mode
|
||||||
|
if (isDSiMode())
|
||||||
|
{
|
||||||
|
// check and ack the flag atomically
|
||||||
|
if (mem_swapByte(false, &sMcuIrqFlag))
|
||||||
|
{
|
||||||
|
// check the irq mask
|
||||||
|
u32 irqMask = mcu_getIrqMask();
|
||||||
|
if (irqMask & MCU_IRQ_RESET)
|
||||||
|
{
|
||||||
|
// power button was released
|
||||||
|
sExitMode = ExitMode::Reset;
|
||||||
|
sState = Arm7State::ExitRequested;
|
||||||
|
}
|
||||||
|
else if (irqMask & MCU_IRQ_POWER_OFF)
|
||||||
|
{
|
||||||
|
// power button was held long to trigger a power off
|
||||||
|
sExitMode = ExitMode::PowerOff;
|
||||||
|
sState = Arm7State::ExitRequested;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initializeVBlankIrq()
|
||||||
|
{
|
||||||
|
rtos_createEvent(&sVBlankEvent);
|
||||||
|
rtos_setIrqFunc(RTOS_IRQ_VBLANK, vblankIrq);
|
||||||
|
rtos_enableIrqMask(RTOS_IRQ_VBLANK);
|
||||||
|
gfx_setVBlankIrqEnabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void clearSoundRegisters()
|
||||||
|
{
|
||||||
|
REG_SOUNDCNT = 0;
|
||||||
|
REG_SNDCAP0CNT = 0;
|
||||||
|
REG_SNDCAP1CNT = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
REG_SOUNDxCNT(i) = 0;
|
||||||
|
REG_SOUNDxSAD(i) = 0;
|
||||||
|
REG_SOUNDxTMR(i) = 0;
|
||||||
|
REG_SOUNDxPNT(i) = 0;
|
||||||
|
REG_SOUNDxLEN(i) = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initializeArm7()
|
||||||
|
{
|
||||||
|
rtos_initIrq();
|
||||||
|
rtos_startMainThread();
|
||||||
|
ipc_initFifoSystem();
|
||||||
|
|
||||||
|
clearSoundRegisters();
|
||||||
|
|
||||||
|
pmic_setAmplifierEnable(true);
|
||||||
|
sys_setSoundPower(true);
|
||||||
|
|
||||||
|
readUserSettings();
|
||||||
|
pmic_setPowerLedBlink(PMIC_CONTROL_POWER_LED_BLINK_NONE);
|
||||||
|
|
||||||
|
sio_setGpioSiIrq(false);
|
||||||
|
sio_setGpioMode(RCNT0_L_MODE_GPIO);
|
||||||
|
|
||||||
|
rtc_init();
|
||||||
|
|
||||||
|
if (isDSiMode())
|
||||||
|
{
|
||||||
|
TMIO_init();
|
||||||
|
sDsiSdIpcService.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
sDldiIpcService.Start();
|
||||||
|
pload_init();
|
||||||
|
|
||||||
|
snd_setMasterVolume(127);
|
||||||
|
snd_setMasterEnable(true);
|
||||||
|
sSoundIpcService.Start();
|
||||||
|
sRtcIpcService.Start();
|
||||||
|
|
||||||
|
gfx_setVCountMatchLine(96);
|
||||||
|
rtos_setIrqFunc(RTOS_IRQ_VCOUNT, vcountIrq);
|
||||||
|
rtos_enableIrqMask(RTOS_IRQ_VCOUNT);
|
||||||
|
gfx_setVCountMatchIrqEnabled(true);
|
||||||
|
|
||||||
|
initializeVBlankIrq();
|
||||||
|
|
||||||
|
if (isDSiMode())
|
||||||
|
{
|
||||||
|
rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq);
|
||||||
|
rtos_enableIrq2Mask(RTOS_IRQ2_MCU);
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc_setArm7SyncBits(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateArm7IdleState()
|
||||||
|
{
|
||||||
|
if (pload_shouldStart())
|
||||||
|
{
|
||||||
|
sExitMode = ExitMode::PicoLoader;
|
||||||
|
sState = Arm7State::ExitRequested;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
checkMcuIrq();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sState == Arm7State::ExitRequested)
|
||||||
|
{
|
||||||
|
snd_setMasterVolume(0); // mute sound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool performExit(ExitMode exitMode)
|
||||||
|
{
|
||||||
|
switch (exitMode)
|
||||||
|
{
|
||||||
|
case ExitMode::Reset:
|
||||||
|
{
|
||||||
|
mcu_setWarmBootFlag(true);
|
||||||
|
mcu_hardReset();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExitMode::PowerOff:
|
||||||
|
{
|
||||||
|
pmic_shutdown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ExitMode::PicoLoader:
|
||||||
|
{
|
||||||
|
pload_start();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true); // wait infinitely for exit
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateArm7ExitRequestedState()
|
||||||
|
{
|
||||||
|
performExit(sExitMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void updateArm7()
|
||||||
|
{
|
||||||
|
switch (sState)
|
||||||
|
{
|
||||||
|
case Arm7State::Idle:
|
||||||
|
{
|
||||||
|
updateArm7IdleState();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Arm7State::ExitRequested:
|
||||||
|
{
|
||||||
|
updateArm7ExitRequestedState();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
sState = Arm7State::Idle;
|
||||||
|
initializeArm7();
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
rtos_waitEvent(&sVBlankEvent, true, true);
|
||||||
|
updateArm7();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
235
arm7/source/mmc/mmc_spec.h
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
// Based on JEDEC eMMC Card Product Standard V4.41.
|
||||||
|
|
||||||
|
#include "tmio.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Controller specific macros. Add controller specific bits here.
|
||||||
|
// MMC_CMD_[response type]_[transfer type]
|
||||||
|
// Transfer type: R = read, W = write.
|
||||||
|
#define MMC_CMD_NONE(id) (CMD_RESP_NONE | (id))
|
||||||
|
#define MMC_CMD_R1(id) (CMD_RESP_R1 | (id))
|
||||||
|
#define MMC_CMD_R1b(id) (CMD_RESP_R1b | (id))
|
||||||
|
#define MMC_CMD_R2(id) (CMD_RESP_R2 | (id))
|
||||||
|
#define MMC_CMD_R3(id) (CMD_RESP_R3 | (id))
|
||||||
|
#define MMC_CMD_R4(id) (CMD_RESP_R4 | (id))
|
||||||
|
#define MMC_CMD_R5(id) (CMD_RESP_R5 | (id))
|
||||||
|
#define MMC_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
|
||||||
|
#define MMC_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
|
||||||
|
|
||||||
|
|
||||||
|
// Basic commands and read-stream command (class 0 and class 1).
|
||||||
|
#define MMC_GO_IDLE_STATE MMC_CMD_NONE(0u) // -, [31:0] 0x00000000 GO_IDLE_STATE, 0xF0F0F0F0 GO_PRE_IDLE_STATE, 0xFFFFFFFA BOOT_INITIATION.
|
||||||
|
#define MMC_SEND_OP_COND MMC_CMD_R3(1u) // R3, [31:0] OCR with-out busy.
|
||||||
|
#define MMC_ALL_SEND_CID MMC_CMD_R2(2u) // R2, [31:0] stuff bits.
|
||||||
|
#define MMC_SET_RELATIVE_ADDR MMC_CMD_R1(3u) // R1, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_SET_DSR MMC_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
|
||||||
|
#define MMC_SLEEP_AWAKE MMC_CMD_R1b(5u) // R1b, [31:16] RCA [15] Sleep/Awake [14:0] stuff bits.
|
||||||
|
#define MMC_SWITCH MMC_CMD_R1b(6u) // R1b, [31:26] Set to 0 [25:24] Access [23:16] Index [15:8] Value [7:3] Set to 0 [2:0] Cmd Set.
|
||||||
|
#define MMC_SELECT_CARD MMC_CMD_R1b(7u) // R1/R1b, [31:16] RCA [15:0] stuff bits. Note: "R1b while selecting from Disconnected State to Programming State."
|
||||||
|
#define MMC_DESELECT_CARD MMC_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_SEND_EXT_CSD MMC_CMD_R1_R(8u) // R1, [31:0] stuff bits.
|
||||||
|
#define MMC_SEND_CSD MMC_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_SEND_CID MMC_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_READ_DAT_UNTIL_STOP MMC_CMD_R1_R(11u) // R1, [31:0] data address.
|
||||||
|
#define MMC_STOP_TRANSMISSION MMC_CMD_R1b(12u) // R1/R1b, [31:16] RCA [15:1] stuff bits [0] HPI. Note: "RCA in CMD12 is used only if HPI bit is set." Note 2: "R1 for read cases and R1b for write cases."
|
||||||
|
#define MMC_SEND_STATUS MMC_CMD_R1(13u) // R1, [31:16] RCA [15:1] stuff bits [0] HPI.
|
||||||
|
#define MMC_BUSTEST_R MMC_CMD_R1_R(14u) // R1, [31:0] stuff bits.
|
||||||
|
#define MMC_GO_INACTIVE_STATE MMC_CMD_NONE(15u) // -, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_BUSTEST_W MMC_CMD_R1_W(19u) // R1, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Block-oriented read commands (class 2).
|
||||||
|
#define MMC_SET_BLOCKLEN MMC_CMD_R1(16u) // R1, [31:0] block length.
|
||||||
|
#define MMC_READ_SINGLE_BLOCK MMC_CMD_R1_R(17u) // R1, [31:0] data address.
|
||||||
|
#define MMC_READ_MULTIPLE_BLOCK MMC_CMD_R1_R(18u) // R1, [31:0] data address.
|
||||||
|
|
||||||
|
// Stream write commands (class 3).
|
||||||
|
#define MMC_WRITE_DAT_UNTIL_STOP MMC_CMD_R1_W(20u) // R1, [31:0] data address.
|
||||||
|
|
||||||
|
// Block-oriented write commands (class 4).
|
||||||
|
#define MMC_SET_BLOCK_COUNT MMC_CMD_R1(23u) // R1, [31] Reliable Write Request [30:16] set to 0 [15:0] number of blocks.
|
||||||
|
#define MMC_WRITE_BLOCK MMC_CMD_R1_W(24u) // R1, [31:0] data address.
|
||||||
|
#define MMC_WRITE_MULTIPLE_BLOCK MMC_CMD_R1_W(25u) // R1, [31:0] data address.
|
||||||
|
#define MMC_PROGRAM_CID MMC_CMD_R1_W(26u) // R1, [31:0] stuff bits.
|
||||||
|
#define MMC_PROGRAM_CSD MMC_CMD_R1_W(27u) // R1, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Block-oriented write protection commands (class 6).
|
||||||
|
#define MMC_SET_WRITE_PROT MMC_CMD_R1b(28u) // R1b, [31:0] data address.
|
||||||
|
#define MMC_CLR_WRITE_PROT MMC_CMD_R1b(29u) // R1b, [31:0] data address.
|
||||||
|
#define MMC_SEND_WRITE_PROT MMC_CMD_R1_R(30u) // R1, [31:0] write protect data address.
|
||||||
|
#define MMC_SEND_WRITE_PROT_TYPE MMC_CMD_R1_R(31u) // R1, [31:0] write protect data address.
|
||||||
|
|
||||||
|
// Erase commands (class 5).
|
||||||
|
#define MMC_ERASE_GROUP_START MMC_CMD_R1(35u) // R1, [31:0] data address.
|
||||||
|
#define MMC_ERASE_GROUP_END MMC_CMD_R1(36u) // R1, [31:0] data address.
|
||||||
|
#define MMC_ERASE MMC_CMD_R1b(38u) // R1b, [31] Secure request [30:16] set to 0 [15] Force Garbage Collect request [14:1] set to 0 [0] Identify Write block for Erase.
|
||||||
|
|
||||||
|
// I/O mode commands (class 9).
|
||||||
|
#define MMC_FAST_IO MMC_CMD_R4(39u) // R4, [31:16] RCA [15:15] register write flag [14:8] register address [7:0] register data.
|
||||||
|
#define MMC_GO_IRQ_STATE MMC_CMD_R5(40u) // R5, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Lock card commands (class 7).
|
||||||
|
#define MMC_LOCK_UNLOCK MMC_CMD_R1_W(42u) // R1, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Application-specific commands (class 8).
|
||||||
|
#define MMC_APP_CMD MMC_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define MMC_GEN_CMD_R MMC_CMD_R1_R(56u) // R1, [31:1] stuff bits [0] RD/WR = 1.
|
||||||
|
#define MMC_GEN_CMD_W MMC_CMD_R1_W(56u) // R1, [31:1] stuff bits [0] RD/WR = 0.
|
||||||
|
|
||||||
|
|
||||||
|
// 7.13 Card status.
|
||||||
|
// Type:
|
||||||
|
// E: Error bit.
|
||||||
|
// S: Status bit.
|
||||||
|
// R: Detected and set for the actual command response.
|
||||||
|
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
|
||||||
|
//
|
||||||
|
// Clear Condition:
|
||||||
|
// A: These bits are persistent, they are set and cleared in accordance with the card status.
|
||||||
|
// B: These bits are cleared as soon as the response (reporting the error) is sent out.
|
||||||
|
#define MMC_R1_APP_CMD (1u<<5) // S R A, The card will expect ACMD, or indication that the command has been interpreted as ACMD.
|
||||||
|
#define MMC_R1_URGENT_BKOPS (1u<<6) // S R A, If set, device needs to perform backgroundoperations urgently. Host can check EXT_CSD field BKOPS_STATUS for the detailed level.
|
||||||
|
#define MMC_R1_SWITCH_ERROR (1u<<7) // E X B, If set, the card did not switch to the expected mode as requested by the SWITCH command.
|
||||||
|
#define MMC_R1_READY_FOR_DATA (1u<<8) // S R A, Corresponds to buffer empty signalling on the bus.
|
||||||
|
#define MMC_R1_STATE_IDLE (0u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_READY (1u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_IDENT (2u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_STBY (3u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_TRAN (4u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_DATA (5u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_RCV (6u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_PRG (7u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_DIS (8u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_BTST (9u<<9) // S R A
|
||||||
|
#define MMC_R1_STATE_SLP (10u<<9) // S R A
|
||||||
|
#define MMC_R1_ERASE_RESET (1u<<13) // E R B, An erase sequence was cleared before executing because an out of erase sequence command was received (commands other than CMD35, CMD36, CMD38 or CMD13.
|
||||||
|
#define MMC_R1_WP_ERASE_SKIP (1u<<15) // E X B, Only partial address space was erased due to existing write protected blocks.
|
||||||
|
#define MMC_R1_CXD_OVERWRITE (1u<<16) // E X B, Can be either one of the following errors: - The CID register has been already written and can not be overwritten - The read only section of the CSD does not match the card content. - An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
|
||||||
|
#define MMC_R1_OVERRUN (1u<<17) // E X B, The card could not sustain data programming in stream write mode.
|
||||||
|
#define MMC_R1_UNDERRUN (1u<<18) // E X B, The card could not sustain data transfer in stream read mode.
|
||||||
|
#define MMC_R1_ERROR (1u<<19) // E X B, (Undefined by the standard) A generic card error related to the (and detected during) execution of the last host command (e.g. read or write failures).
|
||||||
|
#define MMC_R1_CC_ERROR (1u<<20) // E R B, (Undefined by the standard) A card error occurred, which is not related to the host command.
|
||||||
|
#define MMC_R1_CARD_ECC_FAILED (1u<<21) // E X B, Card internal ECC was applied but failed to correct the data.
|
||||||
|
#define MMC_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
|
||||||
|
#define MMC_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
|
||||||
|
#define MMC_R1_LOCK_UNLOCK_FAILED (1u<<24) // E X B, Set when a sequence or password error has been detected in lock/unlock card command.
|
||||||
|
#define MMC_R1_CARD_IS_LOCKED (1u<<25) // S R A, When set, signals that the card is locked by the host.
|
||||||
|
#define MMC_R1_WP_VIOLATION (1u<<26) // E X B, Attempt to program a write protected block.
|
||||||
|
#define MMC_R1_ERASE_PARAM (1u<<27) // E X B, An invalid selection of erase groups for erase occurred.
|
||||||
|
#define MMC_R1_ERASE_SEQ_ERROR (1u<<28) // E R B, An error in the sequence of erase commands occurred.
|
||||||
|
#define MMC_R1_BLOCK_LEN_ERROR (1u<<29) // E R B, Either the argument of a SET_BLOCKLEN command exceeds the maximum value allowed for the card, or the previously defined block length is illegal for the current command (e.g. the host issues a write command, the current block length is smaller than the card’s maximum and write partial blocks is not allowed).
|
||||||
|
#define MMC_R1_ADDRESS_MISALIGN (1u<<30) // E R/X B, The command’ s address argument (in accordance with the currently set block length) positions the first data block misaligned to the card physical blocks. A multiple block read/write operation (although started with a valid address/blocklength combination) is attempting to read or write a data block which does not align with the physical blocks of the card.
|
||||||
|
#define MMC_R1_ADDRESS_OUT_OF_RANGE (1u<<31) // E R/X B, The command’s address argument was out of the allowed range for this card. A multiple block or stream read/write operation is (although started in a valid address) attempting to read or write beyond the card capacity.
|
||||||
|
|
||||||
|
#define MMC_R1_ERR_ALL (MMC_R1_ADDRESS_OUT_OF_RANGE | MMC_R1_ADDRESS_MISALIGN | \
|
||||||
|
MMC_R1_BLOCK_LEN_ERROR | MMC_R1_ERASE_SEQ_ERROR | \
|
||||||
|
MMC_R1_ERASE_PARAM | MMC_R1_WP_VIOLATION | MMC_R1_LOCK_UNLOCK_FAILED | \
|
||||||
|
MMC_R1_COM_CRC_ERROR | MMC_R1_ILLEGAL_COMMAND | MMC_R1_CARD_ECC_FAILED | \
|
||||||
|
MMC_R1_CC_ERROR | MMC_R1_ERROR | MMC_R1_UNDERRUN | MMC_R1_OVERRUN | \
|
||||||
|
MMC_R1_CXD_OVERWRITE | MMC_R1_WP_ERASE_SKIP | MMC_R1_ERASE_RESET | \
|
||||||
|
MMC_R1_SWITCH_ERROR)
|
||||||
|
|
||||||
|
// 8.1 OCR register.
|
||||||
|
// Same bits for CMD1 argument.
|
||||||
|
#define MMC_OCR_1_7_1_95V (1u<<7) // 1.70–1.95V.
|
||||||
|
#define MMC_OCR_2_0_2_1V (1u<<8) // 2.0-2.1V.
|
||||||
|
#define MMC_OCR_2_1_2_2V (1u<<9) // 2.1-2.2V.
|
||||||
|
#define MMC_OCR_2_2_2_3V (1u<<10) // 2.2-2.3V.
|
||||||
|
#define MMC_OCR_2_3_2_4V (1u<<11) // 2.3-2.4V.
|
||||||
|
#define MMC_OCR_2_4_2_5V (1u<<12) // 2.4-2.5V.
|
||||||
|
#define MMC_OCR_2_5_2_6V (1u<<13) // 2.5-2.6V.
|
||||||
|
#define MMC_OCR_2_6_2_7V (1u<<14) // 2.6-2.7V.
|
||||||
|
#define MMC_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
|
||||||
|
#define MMC_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
|
||||||
|
#define MMC_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
|
||||||
|
#define MMC_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
|
||||||
|
#define MMC_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
|
||||||
|
#define MMC_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
|
||||||
|
#define MMC_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
|
||||||
|
#define MMC_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
|
||||||
|
#define MMC_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
|
||||||
|
#define MMC_OCR_BYTE_MODE (0u<<29) // Access mode = byte mode.
|
||||||
|
#define MMC_OCR_SECT_MODE (2u<<29) // Access mode = sector mode.
|
||||||
|
#define MMC_OCR_READY (1u<<31) // Card power up status bit (busy). 0 = busy.
|
||||||
|
|
||||||
|
// 7.6.1 Command sets and extended settings.
|
||||||
|
#define MMC_SWITCH_ACC_CMD_SET (0u)
|
||||||
|
#define MMC_SWITCH_ACC_SET_BITS (1u)
|
||||||
|
#define MMC_SWITCH_ACC_CLR_BITS (2u)
|
||||||
|
#define MMC_SWITCH_ACC_WR_BYTE (3u)
|
||||||
|
#define MMC_SWITCH_ARG(acc, idx, val, cmdSet) (((acc)&3u)<<24 | ((idx)&0xFFu)<<16 | ((val)&0xFFu)<<8 | ((cmdSet)&7u))
|
||||||
|
|
||||||
|
// 8.4 Extended CSD register.
|
||||||
|
// size in bytes, access, description.
|
||||||
|
#define EXT_CSD_SEC_BAD_BLK_MGMNT (134u) // 1, R/W, Bad Block Management mode.
|
||||||
|
#define EXT_CSD_ENH_START_ADDR (136u) // 4, R/W, Enhanced User Data Start Address.
|
||||||
|
#define EXT_CSD_ENH_SIZE_MULT (140u) // 3, R/W, Enhanced User Data Area Size.
|
||||||
|
#define EXT_CSD_GP_SIZE_MULT (143u) // 12, R/W, General Purpose Partition Size.
|
||||||
|
#define EXT_CSD_PARTITION_SETTING_COMPLETED (155u) // 1, R/W, Paritioning Setting.
|
||||||
|
#define EXT_CSD_PARTITIONS_ATTRIBUTE (156u) // 1, R/W, Partitions attribute.
|
||||||
|
#define EXT_CSD_MAX_ENH_SIZE_MULT (157u) // 3, R, Max Enhanced Area Size.
|
||||||
|
#define EXT_CSD_PARTITIONING_SUPPORT (160u) // 1, R, Partitioning Support.
|
||||||
|
#define EXT_CSD_HPI_MGMT (161u) // 1, R/W/E_P, HPI management.
|
||||||
|
#define EXT_CSD_RST_n_FUNCTION (162u) // 1, R/W, H/W reset function.
|
||||||
|
#define EXT_CSD_BKOPS_EN (163u) // 1, R/W, Enable background operations handshake.
|
||||||
|
#define EXT_CSD_BKOPS_START (164u) // 1, W/E_P, Manually start background operations.
|
||||||
|
#define EXT_CSD_WR_REL_PARAM (166u) // 1, R, Write reliability parameter register.
|
||||||
|
#define EXT_CSD_WR_REL_SET (167u) // 1, R/W, Write reliability setting register.
|
||||||
|
#define EXT_CSD_RPMB_SIZE_MULT (168u) // 1, R, RPMB Size.
|
||||||
|
#define EXT_CSD_FW_CONFIG (169u) // 1, R/W, FW configuration.
|
||||||
|
#define EXT_CSD_USER_WP (171u) // 1, R/W, R/W/C_P & R/W/E_P, User area write protection register.
|
||||||
|
#define EXT_CSD_BOOT_WP (173u) // 1, R/W & R/W/C_P, Boot area write protection register.
|
||||||
|
#define EXT_CSD_ERASE_GROUP_DEF (175u) // 1, R/W/E_P, High-density erase group definition.
|
||||||
|
#define EXT_CSD_BOOT_BUS_WIDTH (177u) // 1, R/W/E, Boot bus width1.
|
||||||
|
#define EXT_CSD_BOOT_CONFIG_PROT (178u) // 1, R/W & R/W/C_P, Boot config protection.
|
||||||
|
#define EXT_CSD_PARTITION_CONFIG (179u) // 1, R/W/E & R/W/E_P, Partition configuration.
|
||||||
|
#define EXT_CSD_ERASED_MEM_CONT (181u) // 1, R, Erased memory content.
|
||||||
|
#define EXT_CSD_BUS_WIDTH (183u) // 1, W/E_P, Bus width mode.
|
||||||
|
#define EXT_CSD_HS_TIMING (185u) // 1, R/W/E_P, High-speed interface timing.
|
||||||
|
#define EXT_CSD_POWER_CLASS (187u) // 1, R/W/E_P, Power class.
|
||||||
|
#define EXT_CSD_CMD_SET_REV (189u) // 1, R, Command set revision.
|
||||||
|
#define EXT_CSD_CMD_SET (191u) // 1, R/W/E_P, Command set.
|
||||||
|
#define EXT_CSD_EXT_CSD_REV (192u) // 1, R, Extended CSD revision.
|
||||||
|
#define EXT_CSD_CSD_STRUCTURE (194u) // 1, R, CSD structure version.
|
||||||
|
#define EXT_CSD_CARD_TYPE (196u) // 1, R, Card type.
|
||||||
|
#define EXT_CSD_OUT_OF_INTERRUPT_TIME (198u) // 1, R, Out-of-interrupt busy timing.
|
||||||
|
#define EXT_CSD_PARTITION_SWITCH_TIME (199u) // 1, R, Partition switching timing.
|
||||||
|
#define EXT_CSD_PWR_CL_52_195 (200u) // 1, R, Power class for 52MHz at 1.95V.
|
||||||
|
#define EXT_CSD_PWR_CL_26_195 (201u) // 1, R, Power class for 26MHz at 1.95V.
|
||||||
|
#define EXT_CSD_PWR_CL_52_360 (202u) // 1, R, Power class for 52MHz at 3.6V.
|
||||||
|
#define EXT_CSD_PWR_CL_26_360 (203u) // 1, R, Power class for 26MHz at 3.6V.
|
||||||
|
#define EXT_CSD_MIN_PERF_R_4_26 (205u) // 1, R, Minimum Read Performance for 4bit at 26MHz.
|
||||||
|
#define EXT_CSD_MIN_PERF_W_4_26 (206u) // 1, R, Minimum Write Performance for 4bit at 26MHz.
|
||||||
|
#define EXT_CSD_MIN_PERF_R_8_26_4_52 (207u) // 1, R, Minimum Read Performance for 8bit at 26MHz, for 4bit at 52MHz.
|
||||||
|
#define EXT_CSD_MIN_PERF_W_8_26_4_52 (208u) // 1, R, Minimum Write Performance for 8bit at 26MHz, for 4bit at 52MHz.
|
||||||
|
#define EXT_CSD_MIN_PERF_R_8_52 (209u) // 1, R, Minimum Read Performance for 8bit at 52MHz.
|
||||||
|
#define EXT_CSD_MIN_PERF_W_8_52 (210u) // 1, R, Minimum Write Performance for 8bit at 52MHz.
|
||||||
|
#define EXT_CSD_SEC_COUNT (212u) // 4, R, Sector Count.
|
||||||
|
#define EXT_CSD_S_A_TIMEOUT (217u) // 1, R, Sleep/awake timeout.
|
||||||
|
#define EXT_CSD_S_C_VCCQ (219u) // 1, R, Sleep current (VCCQ).
|
||||||
|
#define EXT_CSD_S_C_VCC (220u) // 1, R, Sleep current (VCC).
|
||||||
|
#define EXT_CSD_HC_WP_GRP_SIZE (221u) // 1, R, High-capacity write protect group size.
|
||||||
|
#define EXT_CSD_REL_WR_SEC_C (222u) // 1, R, Reliable write sector count.
|
||||||
|
#define EXT_CSD_ERASE_TIMEOUT_MULT (223u) // 1, R, High-capacity erase timeout.
|
||||||
|
#define EXT_CSD_HC_ERASE_GRP_SIZE (224u) // 1, R, High-capacity erase unit size.
|
||||||
|
#define EXT_CSD_ACC_SIZE (225u) // 1, R, Access size.
|
||||||
|
#define EXT_CSD_BOOT_SIZE_MULTI (226u) // 1, R, Boot partition size.
|
||||||
|
#define EXT_CSD_BOOT_INFO (228u) // 1, R, Boot information.
|
||||||
|
#define EXT_CSD_SEC_TRIM_MULT (229u) // 1, R, Secure TRIM Multiplier.
|
||||||
|
#define EXT_CSD_SEC_ERASE_MULT (230u) // 1, R, Secure Erase Multiplier.
|
||||||
|
#define EXT_CSD_SEC_FEATURE_SUPPORT (231u) // 1, R, Secure Feature support.
|
||||||
|
#define EXT_CSD_TRIM_MULT (232u) // 1, R, TRIM Multiplier.
|
||||||
|
#define EXT_CSD_MIN_PERF_DDR_R_8_52 (234u) // 1, R, Minimum Read Performance for 8bit at 52MHz in DDR mode.
|
||||||
|
#define EXT_CSD_MIN_PERF_DDR_W_8_52 (235u) // 1, R, Minimum Write Performance for 8bit at 52MHz in DDR mode.
|
||||||
|
#define EXT_CSD_PWR_CL_DDR_52_195 (238u) // 1, R, Power class for 52MHz, DDR at 1.95V.
|
||||||
|
#define EXT_CSD_PWR_CL_DDR_52_360 (239u) // 1, R, Power class for 52MHz, DDR at 3.6V.
|
||||||
|
#define EXT_CSD_INI_TIMEOUT_AP (241u) // 1, R, 1st initialization time after partitioning.
|
||||||
|
#define EXT_CSD_CORRECTLY_PRG_SECTORS_NUM (242u) // 4, R, Number of correctly programmed sectors.
|
||||||
|
#define EXT_CSD_BKOPS_STATUS (246u) // 1, R, Background operations status.
|
||||||
|
#define EXT_CSD_BKOPS_SUPPORT (502u) // 1, R, Background operations support.
|
||||||
|
#define EXT_CSD_HPI_FEATURES (503u) // 1, R, HPI features.
|
||||||
|
#define EXT_CSD_S_CMD_SET (504u) // 1, R, Supported Command Sets.
|
||||||
192
arm7/source/mmc/sd_spec.h
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
// Based on SD specification version 8.00.
|
||||||
|
|
||||||
|
#include "tmio.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Controller specific macros. Add controller specific bits here.
|
||||||
|
// SD_[command type]_[response type]_[transfer type]
|
||||||
|
// Command type: CMD = regular command, ACMD = Application-Specific Command.
|
||||||
|
// Transfer type: R = read, W = write.
|
||||||
|
#define SD_CMD_NONE(id) (CMD_RESP_NONE | (id))
|
||||||
|
#define SD_CMD_R1(id) (CMD_RESP_R1 | (id))
|
||||||
|
#define SD_CMD_R1b(id) (CMD_RESP_R1b | (id))
|
||||||
|
#define SD_CMD_R2(id) (CMD_RESP_R2 | (id))
|
||||||
|
#define SD_CMD_R6(id) (CMD_RESP_R6 | (id))
|
||||||
|
#define SD_CMD_R7(id) (CMD_RESP_R7 | (id))
|
||||||
|
#define SD_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
|
||||||
|
#define SD_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
|
||||||
|
#define SD_ACMD_R1(id) (CMD_RESP_R1 | CMD_ACMD | (id))
|
||||||
|
#define SD_ACMD_R3(id) (CMD_RESP_R3 | CMD_ACMD | (id))
|
||||||
|
#define SD_ACMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | CMD_ACMD | (id))
|
||||||
|
|
||||||
|
|
||||||
|
// Basic Commands (class 0).
|
||||||
|
#define SD_GO_IDLE_STATE SD_CMD_NONE(0u) // -, [31:0] stuff bits.
|
||||||
|
#define SD_ALL_SEND_CID SD_CMD_R2(2u) // R2, [31:0] stuff bits.
|
||||||
|
#define SD_SEND_RELATIVE_ADDR SD_CMD_R6(3u) // R6, [31:0] stuff bits.
|
||||||
|
#define SD_SET_DSR SD_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
|
||||||
|
#define SD_SELECT_CARD SD_CMD_R1b(7u) // R1b, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define SD_DESELECT_CARD SD_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define SD_SEND_IF_COND SD_CMD_R7(8u) // R7, [31:12] reserved bits [11:8] supply voltage (VHS) [7:0] check pattern.
|
||||||
|
#define SD_SEND_CSD SD_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define SD_SEND_CID SD_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define SD_VOLTAGE_SWITCH SD_CMD_R1(11u) // R1, [31:0] reserved bits (all 0).
|
||||||
|
#define SD_STOP_TRANSMISSION SD_CMD_R1b(12u) // R1b, [31:0] stuff bits.
|
||||||
|
#define SD_SEND_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
|
||||||
|
#define SD_SEND_TASK_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
|
||||||
|
#define SD_GO_INACTIVE_STATE SD_CMD_NONE(15u) // -, [31:16] RCA [15:0] reserved bits.
|
||||||
|
|
||||||
|
// Block-Oriented Read Commands (class 2).
|
||||||
|
#define SD_SET_BLOCKLEN SD_CMD_R1(16u) // R1, [31:0] block length.
|
||||||
|
#define SD_READ_SINGLE_BLOCK SD_CMD_R1_R(17u) // R1, [31:0] data address.
|
||||||
|
#define SD_READ_MULTIPLE_BLOCK SD_CMD_R1_R(18u) // R1, [31:0] data address.
|
||||||
|
#define SD_SEND_TUNING_BLOCK SD_CMD_R1_R(19u) // R1, [31:0] reserved bits (all 0).
|
||||||
|
#define SD_SPEED_CLASS_CONTROL SD_CMD_R1b(20u) // R1b, [31:28] Speed Class Control [27:0] See command description.
|
||||||
|
#define SD_ADDRESS_EXTENSION SD_CMD_R1(22u) // R1, [31:6] reserved bits (all 0) [5:0] extended address.
|
||||||
|
#define SD_SET_BLOCK_COUNT SD_CMD_R1(23u) // R1, [31:0] Block Count.
|
||||||
|
|
||||||
|
// Block-Oriented Write Commands (class 4).
|
||||||
|
// SET_BLOCKLEN
|
||||||
|
// SPEED_CLASS_CONTROL
|
||||||
|
// ADDRESS_EXTENSION
|
||||||
|
// SET_BLOCK_COUNT
|
||||||
|
#define SD_WRITE_BLOCK SD_CMD_R1_W(24u) // R1, [31:0] data address.
|
||||||
|
#define SD_WRITE_MULTIPLE_BLOCK SD_CMD_R1_W(25u) // R1, [31:0] data address.
|
||||||
|
#define SD_PROGRAM_CSD SD_CMD_R1_W(27u) // R1, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Block Oriented Write Protection Commands (class 6).
|
||||||
|
#define SD_SET_WRITE_PROT SD_CMD_R1b(28u) // R1b, [31:0] data address.
|
||||||
|
#define SD_CLR_WRITE_PROT SD_CMD_R1b(29u) // R1b, [31:0] data address.
|
||||||
|
#define SD_SEND_WRITE_PROT SD_CMD_R1_R(30u) // R1, [31:0] write protect data address.
|
||||||
|
|
||||||
|
// Erase Commands (class 5).
|
||||||
|
#define SD_ERASE_WR_BLK_START SD_CMD_R1(32u) // R1, [31:0] data address.
|
||||||
|
#define SD_ERASE_WR_BLK_END SD_CMD_R1(33u) // R1, [31:0] data address.
|
||||||
|
#define SD_ERASE SD_CMD_R1b(38u) // R1b, [31:0] Erase Function.
|
||||||
|
|
||||||
|
// Lock Card (class 7).
|
||||||
|
// SET_BLOCKLEN
|
||||||
|
// Command 40 "Defined by DPS Spec.".
|
||||||
|
#define SD_LOCK_UNLOCK SD_CMD_R1_W(42u) // R1, [31:0] Reserved bits (Set all 0).
|
||||||
|
|
||||||
|
// Application-Specific Commands (class 8).
|
||||||
|
#define SD_APP_CMD SD_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
|
||||||
|
#define SD_GEN_CMD_R SD_CMD_R1_R(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 1.
|
||||||
|
#define SD_GEN_CMD_W SD_CMD_R1_W(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 0.
|
||||||
|
|
||||||
|
// Application Specific Commands used/reserved by SD Memory Card.
|
||||||
|
#define SD_APP_SET_BUS_WIDTH SD_ACMD_R1(6u) // R1, [31:2] stuff bits [1:0] bus width.
|
||||||
|
#define SD_APP_SD_STATUS SD_ACMD_R1_R(13u) // R1, [31:0] stuff bits.
|
||||||
|
#define SD_APP_SEND_NUM_WR_BLOCKS SD_ACMD_R1_R(22u) // R1, [31:0] stuff bits.
|
||||||
|
#define SD_APP_SET_WR_BLK_ERASE_COUNT SD_ACMD_R1(23u) // R1, [31:23] stuff bits [22:0] Number of blocks.
|
||||||
|
#define SD_APP_SD_SEND_OP_COND SD_ACMD_R3(41u) // R3, [31] reserved bit [30] HCS (OCR[30]) [29] reserved for eSD [28] XPC [27:25] reserved bits [24] S18R [23:0] VDD Voltage Window (OCR[23:0]).
|
||||||
|
#define SD_APP_SET_CLR_CARD_DETECT SD_ACMD_R1(42u) // R1, [31:1] stuff bits [0] set_cd.
|
||||||
|
#define SD_APP_SEND_SCR SD_ACMD_R1_R(51u) // R1, [31:0] stuff bits.
|
||||||
|
|
||||||
|
// Switch Function Commands (class 10).
|
||||||
|
#define SD_SWITCH_FUNC SD_CMD_R1_R(6u) // R1, [31] Mode 0: Check function 1: Switch function [30:24] reserved (All '0') [23:20] reserved for function group 6 (0h or Fh) [19:16] reserved for function group 5 (0h or Fh) [15:12] function group 4 for PowerLimit [11:8] function group 3 for Drive Strength [7:4] function group 2 for Command System [3:0] function group 1 for Access Mode.
|
||||||
|
|
||||||
|
// Function Extension Commands (class 11).
|
||||||
|
#define SD_READ_EXTR_SINGLE SD_CMD_R1_R(48u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO[26] Reserved (=0) [25:9] ADDR [8:0] LEN.
|
||||||
|
#define SD_WRITE_EXTR_SINGLE SD_CMD_R1_W(49u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] MW [25:9] ADDR [8:0] LEN/MASK.
|
||||||
|
#define SD_READ_EXTR_MULTI SD_CMD_R1_R(58u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
|
||||||
|
#define SD_WRITE_EXTR_MULTI SD_CMD_R1_W(59u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
|
||||||
|
|
||||||
|
// Command Queue Function Commands (class 1).
|
||||||
|
#define SD_Q_MANAGEMENT SD_CMD_R1b(43u) // R1b, [31:21] Reserved [20:16]: Task ID [3:0]: Operation Code (Abort tasks etc.).
|
||||||
|
#define SD_Q_TASK_INFO_A SD_CMD_R1(44u) // R1, [31] Reserved [30] Direction [29:24] Extended Address [23] Priority [22:21] Reserved [20:16] Task ID [15:0] Number of Blocks.
|
||||||
|
#define SD_Q_TASK_INFO_B SD_CMD_R1(45u) // R1, [31:0] Start block address.
|
||||||
|
#define SD_Q_RD_TASK SD_CMD_R1_R(46u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
|
||||||
|
#define SD_Q_WR_TASK SD_CMD_R1_W(47u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
|
||||||
|
|
||||||
|
|
||||||
|
// 4.10.1 Card Status.
|
||||||
|
// Type:
|
||||||
|
// E: Error bit.
|
||||||
|
// S: Status bit.
|
||||||
|
// R: Detected and set for the actual command response.
|
||||||
|
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
|
||||||
|
//
|
||||||
|
// Clear Condition:
|
||||||
|
// A: According to the card current status.
|
||||||
|
// B: Always related to the previous command. Reception of a valid command will clear it (with a delay of one command).
|
||||||
|
// C: Clear by read.
|
||||||
|
#define SD_R1_AKE_SEQ_ERROR (1u<<3) // E R C, Error in the sequence of the authentication process.
|
||||||
|
#define SD_R1_APP_CMD (1u<<5) // S R C, The card will expect ACMD, or an indication that the command has been interpreted as ACMD.
|
||||||
|
#define SD_R1_FX_EVENT (1u<<6) // S X A, ExtensionFunctions may set this bit to get host to deal with events.
|
||||||
|
#define SD_R1_READY_FOR_DATA (1u<<8) // S X A, Corresponds to buffer empty signaling on the bus.
|
||||||
|
#define SD_R1_STATE_IDLE (0u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_READY (1u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_IDENT (2u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_STBY (3u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_TRAN (4u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_DATA (5u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_RCV (6u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_PRG (7u<<9) // S X B
|
||||||
|
#define SD_R1_STATE_DIS (8u<<9) // S X B
|
||||||
|
#define SD_R1_ERASE_RESET (1u<<13) // S R C, An erase sequence was cleared before executing because an out of erase sequence command was received.
|
||||||
|
#define SD_R1_CARD_ECC_DISABLED (1u<<14) // S X A, The command has been executed without using the internal ECC.
|
||||||
|
#define SD_R1_WP_ERASE_SKIP (1u<<15) // E R X C, Set when only partial address space was erased due to existing write protected blocks or the temporary or permanent write protected cardwas erased.
|
||||||
|
#define SD_R1_CSD_OVERWRITE (1u<<16) // E R X C, Can be either one of the following errors: -The read only section of the CSD does not match the card content. -An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
|
||||||
|
// 17 reserved for DEFERRED_RESPONSE (Refer to eSD Addendum)
|
||||||
|
#define SD_R1_ERROR (1u<<19) // E R X C, A general or an unknown error occurred during the operation.
|
||||||
|
#define SD_R1_CC_ERROR (1u<<20) // E R X C, Internal card controller error:
|
||||||
|
#define SD_R1_CARD_ECC_FAILED (1u<<21) // E R X C, Card internal ECC was applied but failed to correct the data.
|
||||||
|
#define SD_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
|
||||||
|
#define SD_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
|
||||||
|
#define SD_R1_LOCK_UNLOCK_FAILED (1u<<24) // E R X C, Set when a sequence or password error has been detected in lock/unlock card command.
|
||||||
|
#define SD_R1_CARD_IS_LOCKED (1u<<25) // S X A, When set, signals that the card is locked by the host.
|
||||||
|
#define SD_R1_WP_VIOLATION (1u<<26) // E R X C, Set when the host attempts to write to a protected block or to thetemporary or permanent write protected card.
|
||||||
|
#define SD_R1_ERASE_PARAM (1u<<27) // E R X C, An invalid selection of write-blocks for erase occurred.
|
||||||
|
#define SD_R1_ERASE_SEQ_ERROR (1u<<28) // E R C, An error in the sequence of erase commands occurred.
|
||||||
|
#define SD_R1_BLOCK_LEN_ERROR (1u<<29) // E R X C, The transferred block length is not allowed for this card, or the number of transferred bytes does not match the block length.
|
||||||
|
#define SD_R1_ADDRESS_ERROR (1u<<30) // E R X C, A misaligned address which did not match the block length was used in the command.
|
||||||
|
#define SD_R1_OUT_OF_RANGE (1u<<31) // E R X C, The command's argument was out of the allowed range for this card.
|
||||||
|
|
||||||
|
#define SD_R1_ERR_ALL (SD_R1_OUT_OF_RANGE | SD_R1_ADDRESS_ERROR | SD_R1_BLOCK_LEN_ERROR | \
|
||||||
|
SD_R1_ERASE_SEQ_ERROR | SD_R1_ERASE_PARAM | SD_R1_WP_VIOLATION | \
|
||||||
|
SD_R1_LOCK_UNLOCK_FAILED | SD_R1_COM_CRC_ERROR | SD_R1_ILLEGAL_COMMAND | \
|
||||||
|
SD_R1_CARD_ECC_FAILED | SD_R1_CC_ERROR | SD_R1_ERROR | \
|
||||||
|
SD_R1_CSD_OVERWRITE | SD_R1_WP_ERASE_SKIP | SD_R1_AKE_SEQ_ERROR)
|
||||||
|
|
||||||
|
// Argument bits for SEND_IF_COND (CMD8).
|
||||||
|
#define SD_CMD8_CHK_PATT (0xAAu) // Check pattern.
|
||||||
|
#define SD_CMD8_VHS_2_7_3_6V (1u<<8) // Voltage supplied (VHS) 2.7-3.6V.
|
||||||
|
#define SD_CMD8_PCIe (1u<<12) // PCIe Avail-ability.
|
||||||
|
#define SD_CMD8_PCIe_1_2V (1u<<13) // PCIe 1.2V Support.
|
||||||
|
|
||||||
|
// 5.1 OCR register.
|
||||||
|
#define SD_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
|
||||||
|
#define SD_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
|
||||||
|
#define SD_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
|
||||||
|
#define SD_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
|
||||||
|
#define SD_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
|
||||||
|
#define SD_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
|
||||||
|
#define SD_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
|
||||||
|
#define SD_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
|
||||||
|
#define SD_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
|
||||||
|
#define SD_OCR_S18A (1u<<24) // S18A: Switching to 1.8V Accepted. 0b: Continues current voltage signaling, 1b: Ready for switching signal voltage.
|
||||||
|
#define SD_OCR_CO2T (1u<<27) // Over 2TB Card. CCS must also be 1 if this is 1.
|
||||||
|
#define SD_OCR_UHS_II (1u<<29) // UHS-II Card Status. 0b: Non UHS-II Card, 1b: UHS-II Card.
|
||||||
|
#define SD_OCR_CCS (1u<<30) // Card Capacity Status. 0b: SDSC, 1b: SDHC or SDXC.
|
||||||
|
#define SD_OCR_READY (1u<<31) // Busy Status. 0b: On Initialization, 1b: Initialization Complete.
|
||||||
|
|
||||||
|
// Argument bits for SEND_OP_COND (ACMD41).
|
||||||
|
// For voltage bits see OCR register above.
|
||||||
|
#define SD_ACMD41_S18R (1u<<24) // S18R: Switching to 1.8V Request. 0b: Use current signal voltage, 1b: Switch to 1.8V signal voltage.
|
||||||
|
#define SD_ACMD41_HO2T (1u<<27) // Over 2TB Supported Host. HCS must also be 1 if this is 1.
|
||||||
|
#define SD_ACMD41_XPC (1u<<28) // SDXC Power Control. 0b: Power Saving, 1b: Maximum Performance.
|
||||||
|
#define SD_ACMD41_HCS (1u<<30) // Host Capacity Support. 0b: SDSC Only Host, 1b: SDHC or SDXC Supported.
|
||||||
|
|
||||||
|
// 4.3.10 Switch Function Command.
|
||||||
|
// mode: 0 = check function, 1 = set function
|
||||||
|
// pwr: Function group 4 Power Limit.
|
||||||
|
// driver: Function group 3 Driver Strength.
|
||||||
|
// cmd: Function group 2 Command system.
|
||||||
|
// acc: Function group 1 Access mode.
|
||||||
|
#define SD_SWITCH_FUNC_ARG(mode, pwr, driver, cmd, acc) ((mode)<<31 | 0xFFu<<16 | ((pwr)&0xFu)<<12 | ((driver)&0xFu)<<8 | ((cmd)&0xFu)<<4 | ((acc)&0xFu))
|
||||||
240
arm7/source/mmc/sdmmc.h
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
// Possible error codes for most of the functions below.
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SDMMC_ERR_NONE = 0u, // No error.
|
||||||
|
SDMMC_ERR_INVAL_PARAM = 1u, // Invalid parameter.
|
||||||
|
SDMMC_ERR_INITIALIZED = 2u, // The device is already initialized.
|
||||||
|
SDMMC_ERR_GO_IDLE_STATE = 3u, // GO_IDLE_STATE CMD error.
|
||||||
|
SDMMC_ERR_SEND_IF_COND = 4u, // SEND_IF_COND CMD error.
|
||||||
|
SDMMC_ERR_IF_COND_RESP = 5u, // IF_COND response pattern mismatch or unsupported voltage.
|
||||||
|
SDMMC_ERR_SEND_OP_COND = 6u, // SEND_OP_COND CMD error.
|
||||||
|
SDMMC_ERR_OP_COND_TMOUT = 7u, // Card initialization timeout.
|
||||||
|
SDMMC_ERR_VOLT_SUPPORT = 8u, // Voltage not supported.
|
||||||
|
SDMMC_ERR_ALL_SEND_CID = 9u, // ALL_SEND_CID CMD error.
|
||||||
|
SDMMC_ERR_SET_SEND_RCA = 10u, // SET/SEND_RELATIVE_ADDR CMD error.
|
||||||
|
SDMMC_ERR_SEND_CSD = 11u, // SEND_CSD CMD error.
|
||||||
|
SDMMC_ERR_SELECT_CARD = 12u, // SELECT_CARD CMD error.
|
||||||
|
SDMMC_ERR_LOCKED = 13u, // Card is locked with a password.
|
||||||
|
SDMMC_ERR_SEND_EXT_CSD = 14u, // SEND_EXT_CSD CMD error.
|
||||||
|
SDMMC_ERR_SWITCH_HS = 15u, // Error on switching to high speed mode.
|
||||||
|
SDMMC_ERR_SET_CLR_CD = 16u, // SET_CLR_CARD_DETECT CMD error.
|
||||||
|
SDMMC_ERR_SET_BUS_WIDTH = 17u, // Error on switching to a different bus width.
|
||||||
|
SDMMC_ERR_SEND_STATUS = 18u, // SEND_STATUS CMD error.
|
||||||
|
SDMMC_ERR_CARD_STATUS = 19u, // The card returned an error via its status.
|
||||||
|
SDMMC_ERR_NO_CARD = 20u, // Card unitialized or not inserted.
|
||||||
|
SDMMC_ERR_SECT_RW = 21u, // Sector read/write error.
|
||||||
|
SDMMC_ERR_WRITE_PROT = 22u, // The card is write protected.
|
||||||
|
SDMMC_ERR_SEND_CMD = 23u, // An error occured while sending a custom CMD via SDMMC_sendCommand().
|
||||||
|
SDMMC_ERR_SET_BLOCKLEN = 24u, // SET_BLOCKLEN CMD error.
|
||||||
|
SDMMC_ERR_LOCK_UNLOCK = 25u, // LOCK_UNLOCK CMD error.
|
||||||
|
SDMMC_ERR_LOCK_UNLOCK_FAIL = 26u, // Lock/unlock operation failed (R1 status).
|
||||||
|
SDMMC_ERR_SLEEP_AWAKE = 27u // (e)MMC SLEEP_AWAKE CMD error.
|
||||||
|
};
|
||||||
|
|
||||||
|
// (e)MMC/SD device numbers.
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SDMMC_DEV_CARD = 0u, // SD card/MMC.
|
||||||
|
SDMMC_DEV_eMMC = 1u, // Builtin eMMC.
|
||||||
|
|
||||||
|
// Alias for internal use only.
|
||||||
|
SDMMC_MAX_DEV_NUM = SDMMC_DEV_eMMC
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bit definition for SdmmcInfo.prot.
|
||||||
|
// Each bit 1 = protected.
|
||||||
|
#define SDMMC_PROT_SLIDER (1u) // SD card write protection slider.
|
||||||
|
#define SDMMC_PROT_TEMP (1u<<1) // Temporary write protection (CSD).
|
||||||
|
#define SDMMC_PROT_PERM (1u<<2) // Permanent write protection (CSD).
|
||||||
|
#define SDMMC_PROT_PASSWORD (1u<<3) // (e)MMC/SD card is password protected.
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 type; // 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC, 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
|
||||||
|
u8 prot; // See SDMMC_PROT_... defines above for details.
|
||||||
|
u16 rca; // Relative Card Address (RCA).
|
||||||
|
u32 sectors; // Size in 512 byte units.
|
||||||
|
u32 clock; // The current clock frequency in Hz.
|
||||||
|
u32 cid[4]; // Raw CID without the CRC.
|
||||||
|
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
|
||||||
|
u8 busWidth; // The current bus width used to talk to the card.
|
||||||
|
} SdmmcInfo;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u16 cmd; // Command. T̲h̲e̲ ̲f̲o̲r̲m̲a̲t̲ ̲i̲s̲ ̲c̲o̲n̲t̲r̲o̲l̲l̲e̲r̲ ̲s̲p̲e̲c̲i̲f̲i̲c̲!̲
|
||||||
|
u32 arg; // Command argument.
|
||||||
|
u32 resp[4]; // Card response. Length depends on command.
|
||||||
|
u32 *buf; // In/out data buffer.
|
||||||
|
u16 blkLen; // Block length. Usually 512.
|
||||||
|
u16 count; // Number of blkSize blocks to transfer.
|
||||||
|
} MmcCommand;
|
||||||
|
|
||||||
|
// Mode bits for SDMMC_lockUnlock().
|
||||||
|
#define SDMMC_LK_CLR_PWD (1u<<1) // Clear password.
|
||||||
|
#define SDMMC_LK_UNLOCK (0u) // Unlock.
|
||||||
|
#define SDMMC_LK_LOCK (1u<<2) // Lock.
|
||||||
|
#define SDMMC_LK_ERASE (1u<<3) // Force erase a locked (e)MMC/SD card.
|
||||||
|
#define SDMMC_LK_COP (1u<<4) // SD cards only. Card Ownership Protection operation.
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"{
|
||||||
|
#endif
|
||||||
|
/**
|
||||||
|
* @brief Initializes a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device to initialize.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* one of the errors listed above on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_init(const u8 devNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Switches a (e)MMC/SD card device between sleep/awake mode.
|
||||||
|
* Note that SD cards don't have a true sleep mode.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param[in] enabled The mode. true to enable sleep and false to wake up.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* one of the errors listed above on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitializes a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device to deinitialize.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_deinit(const u8 devNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Manage password protection for a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param[in] mode The mode of operation. See defines above.
|
||||||
|
* @param[in] pwd The password buffer pointer.
|
||||||
|
* @param[in] pwdLen The password length. Maximum 32 for password replace. Otherwise 16.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* one of the errors listed above on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Exports the internal device state for fast init (bootloaders ect.).
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device state to export.
|
||||||
|
* @param devOut A pointer to a u8[60] array.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Imports a device state for fast init (bootloaders ect.).
|
||||||
|
* The state should be validated for example with a checksum.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device state to import.
|
||||||
|
* @param[in] devIn A pointer to a u8[60] array.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD/SDMMC_ERR_INITIALIZED on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs infos about a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param infoOut A pointer to a SdmmcInfo struct.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs the CID of a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param cidOut A u32[4] pointer for storing the CID.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the DSTATUS bits of a (e)MMC/SD card device. See FatFs diskio.h.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
*
|
||||||
|
* @return Returns the DSTATUS bits or STA_NODISK | STA_NOINIT on failure.
|
||||||
|
*/
|
||||||
|
//u8 SDMMC_getDiskStatus(const u8 devNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Outputs the number of sectors for a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
*
|
||||||
|
* @return Returns the number of sectors or 0 on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_getSectors(const u8 devNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads one or more sectors from a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param[in] sect The start sector.
|
||||||
|
* @param buf The output buffer pointer. NULL for DMA.
|
||||||
|
* @param[in] count The number of sectors to read.
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* one of the errors listed above on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Writes one or more sectors to a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param[in] sect The start sector.
|
||||||
|
* @param[in] buf The input buffer pointer. NULL for DMA.
|
||||||
|
* @param[in] count The count
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or
|
||||||
|
* one of the errors listed above on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a custom command to a (e)MMC/SD card device.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
* @param cmd MMC command struct pointer (see above).
|
||||||
|
*
|
||||||
|
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_SEND_CMD on failure.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the R1 card status for a previously failed read/write/custom command.
|
||||||
|
*
|
||||||
|
* @param[in] devNum The device.
|
||||||
|
*
|
||||||
|
* @return Returns the R1 card status or 0 if there was either no command error or invalid devNum.
|
||||||
|
*/
|
||||||
|
u32 pico_SDMMC_getLastR1error(const u8 devNum);
|
||||||
|
|
||||||
|
// TODO: TRIM/erase support.
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
751
arm7/source/mmc/sdmmc.twl.c
Normal file
@@ -0,0 +1,751 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
#include <stdalign.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <nds/ndstypes.h>
|
||||||
|
#include "mmc/sdmmc.h" // Includes types.h.
|
||||||
|
#include "tmio.h"
|
||||||
|
#include "mmc/mmc_spec.h"
|
||||||
|
#include "mmc/sd_spec.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Note on INIT_CLOCK:
|
||||||
|
// 400 kHz is allowed by the specs. 523 kHz has been proven to work reliably
|
||||||
|
// for SD cards and eMMC but very early MMCs can fail at init.
|
||||||
|
// We lose about 5 ms of time on init by using 261 kHz.
|
||||||
|
#define INIT_CLOCK (400000u) // Maximum 400 kHz.
|
||||||
|
#define DEFAULT_CLOCK (20000000u) // Maximum 20 MHz.
|
||||||
|
#define HS_CLOCK (50000000u) // Maximum 50 MHz.
|
||||||
|
|
||||||
|
// ARM7 timer clock = controller clock = CPU clock.
|
||||||
|
// swiDelay() doesn't seem to be cycle accurate meaning
|
||||||
|
// one cycle is 4 (?) CPU cycles.
|
||||||
|
#define SLEEP_MS_FUNC(ms) swi_waitByLoop(8378 * (ms))
|
||||||
|
|
||||||
|
|
||||||
|
#define MMC_OCR_VOLT_MASK (MMC_OCR_3_2_3_3V) // We support 3.3V only.
|
||||||
|
#define SD_OCR_VOLT_MASK (SD_OCR_3_2_3_3V) // We support 3.3V only.
|
||||||
|
#define SD_IF_COND_ARG (SD_CMD8_VHS_2_7_3_6V | SD_CMD8_CHK_PATT)
|
||||||
|
#define SD_OP_COND_ARG (SD_OCR_VOLT_MASK) // We support 100 mA and 3.3V. Without HCS bit.
|
||||||
|
#define MMC_OP_COND_ARG (MMC_OCR_SECT_MODE | MMC_OCR_VOLT_MASK) // We support sector addressing and 3.3V.
|
||||||
|
|
||||||
|
// Note: DEV_TYPE_NONE must be zero.
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
// Device types.
|
||||||
|
DEV_TYPE_NONE = 0u, // Unitialized/no device.
|
||||||
|
DEV_TYPE_MMC = 1u, // (e)MMC.
|
||||||
|
DEV_TYPE_MMCHC = 2u, // High capacity (e)MMC (>2 GB).
|
||||||
|
DEV_TYPE_SDSC = 3u, // SDSC.
|
||||||
|
DEV_TYPE_SDHC = 4u, // SDHC, SDXC.
|
||||||
|
DEV_TYPE_SDUC = 5u // SDUC.
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IS_DEV_MMC(dev) ((dev) < DEV_TYPE_SDSC)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
TmioPort port;
|
||||||
|
u8 type; // Device type. 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC,
|
||||||
|
// 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
|
||||||
|
u8 prot; // Protection bits. Each bit 1 = protected.
|
||||||
|
// Bit 0 SD card slider, bit 1 temporary write protection (CSD),
|
||||||
|
// bit 2 permanent write protection (CSD) and bit 3 password protection.
|
||||||
|
u16 rca; // Relative Card Address (RCA).
|
||||||
|
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
|
||||||
|
u32 sectors; // Size in 512 byte units.
|
||||||
|
u32 status; // R1 card status on error. Only updated on errors.
|
||||||
|
|
||||||
|
// Cached card infos.
|
||||||
|
u32 cid[4]; // Raw CID without the CRC.
|
||||||
|
} SdmmcDev;
|
||||||
|
|
||||||
|
static SdmmcDev g_devs[2] = {0};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static u32 sendAppCmd(TmioPort *const port, const u16 cmd, const u32 arg, const u32 rca)
|
||||||
|
{
|
||||||
|
// Send app CMD. Same CMD for (e)MMC/SD.
|
||||||
|
// TODO: Check the APP_CMD bit in the response?
|
||||||
|
// Linux does it but is it really necessary? SD spec 4.3.9.1.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_APP_CMD, rca);
|
||||||
|
if(res == 0)
|
||||||
|
{
|
||||||
|
res = TMIO_sendCommand(port, cmd, arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 goIdleState(TmioPort *const port)
|
||||||
|
{
|
||||||
|
// Enter idle state before we start the init procedure.
|
||||||
|
// Works from all but inactive state. CMD is the same for (e)MMC/SD.
|
||||||
|
// For (e)MMC there are optional init paths:
|
||||||
|
// arg = 0x00000000 -> GO_IDLE_STATE.
|
||||||
|
// arg = 0xF0F0F0F0 -> GO_PRE_IDLE_STATE.
|
||||||
|
// arg = 0xFFFFFFFA -> BOOT_INITIATION.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_GO_IDLE_STATE, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_GO_IDLE_STATE;
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 initIdleState(TmioPort *const port, u8 *const devTypeOut)
|
||||||
|
{
|
||||||
|
// Tell the card what interfaces and voltages we support.
|
||||||
|
// Only SD v2 and up will respond. (e)MMC won't respond.
|
||||||
|
u32 res = TMIO_sendCommand(port, SD_SEND_IF_COND, SD_IF_COND_ARG);
|
||||||
|
if(res == 0)
|
||||||
|
{
|
||||||
|
// If the card supports the interfaces and voltages
|
||||||
|
// it should echo back the check pattern and set the
|
||||||
|
// support bits.
|
||||||
|
// Since we don't support anything but the
|
||||||
|
// standard SD interface at 3.3V we can check
|
||||||
|
// the whole response at once.
|
||||||
|
if(port->resp[0] != SD_IF_COND_ARG) return SDMMC_ERR_IF_COND_RESP;
|
||||||
|
}
|
||||||
|
else if(res != STATUS_ERR_CMD_TIMEOUT) return SDMMC_ERR_SEND_IF_COND; // Card responded but an error occured.
|
||||||
|
|
||||||
|
// Send the first app CMD. If this times out it's (e)MMC.
|
||||||
|
// If SEND_IF_COND timed out tell the SD card we are a v1 host.
|
||||||
|
const u32 opCondArg = SD_OP_COND_ARG | (res<<8 ^ SD_ACMD41_HCS); // Caution! Controller specific hack.
|
||||||
|
u8 devType = DEV_TYPE_SDSC;
|
||||||
|
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
|
||||||
|
if(res == STATUS_ERR_CMD_TIMEOUT) devType = DEV_TYPE_MMC; // Continue with (e)MMC init.
|
||||||
|
else if(res != 0) return SDMMC_ERR_SEND_OP_COND; // Unknown error.
|
||||||
|
|
||||||
|
if(devType == DEV_TYPE_MMC) // (e)MMC.
|
||||||
|
{
|
||||||
|
// Loop until a timeout of 1 second or the card is ready.
|
||||||
|
u32 tries = 200;
|
||||||
|
u32 ocr;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
res = TMIO_sendCommand(port, MMC_SEND_OP_COND, MMC_OP_COND_ARG);
|
||||||
|
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
|
||||||
|
|
||||||
|
ocr = port->resp[0];
|
||||||
|
if(!--tries || (ocr & MMC_OCR_READY)) break;
|
||||||
|
|
||||||
|
// Linux uses 10 ms but the card doesn't become ready faster
|
||||||
|
// when polling with delay. Use 5 ms as compromise so not much
|
||||||
|
// time is wasted when the card becomes ready in the middle of the delay.
|
||||||
|
SLEEP_MS_FUNC(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (e)MMC didn't finish init within 1 second.
|
||||||
|
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
|
||||||
|
|
||||||
|
// Check if the (e)MMC supports the voltage and if it's high capacity.
|
||||||
|
if(!(ocr & MMC_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
|
||||||
|
if(ocr & MMC_OCR_SECT_MODE) devType = DEV_TYPE_MMCHC; // 7.4.3.
|
||||||
|
}
|
||||||
|
else // SD card.
|
||||||
|
{
|
||||||
|
// Loop until a timeout of 1 second or the card is ready.
|
||||||
|
u32 tries = 200;
|
||||||
|
u32 ocr;
|
||||||
|
while(1)
|
||||||
|
{
|
||||||
|
ocr = port->resp[0];
|
||||||
|
if(!--tries || (ocr & SD_OCR_READY)) break;
|
||||||
|
|
||||||
|
// Linux uses 10 ms but the card doesn't become ready faster
|
||||||
|
// when polling with delay. Use 5 ms as compromise so not much
|
||||||
|
// time is wasted when the card becomes ready in the middle of the delay.
|
||||||
|
SLEEP_MS_FUNC(5);
|
||||||
|
|
||||||
|
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SD card didn't finish init within 1 second.
|
||||||
|
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
|
||||||
|
|
||||||
|
if(!(ocr & SD_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
|
||||||
|
if(ocr & SD_OCR_CCS) devType = DEV_TYPE_SDHC;
|
||||||
|
}
|
||||||
|
|
||||||
|
*devTypeOut = devType;
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 initReadyState(SdmmcDev *const dev)
|
||||||
|
{
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
// SD card voltage switch sequence goes here if supported.
|
||||||
|
|
||||||
|
// Get the CID. CMD is the same for (e)MMC/SD.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_ALL_SEND_CID, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_ALL_SEND_CID;
|
||||||
|
memcpy(dev->cid, port->resp, 16);
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 initIdentState(SdmmcDev *const dev, const u8 devType, u32 *const rcaOut)
|
||||||
|
{
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
u32 rca;
|
||||||
|
if(IS_DEV_MMC(devType)) // (e)MMC.
|
||||||
|
{
|
||||||
|
// Set the RCA of the (e)MMC to 1. 0 is reserved.
|
||||||
|
// The RCA is in the upper 16 bits of the argument.
|
||||||
|
rca = 1;
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SET_RELATIVE_ADDR, rca<<16);
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
|
||||||
|
}
|
||||||
|
else // SD card.
|
||||||
|
{
|
||||||
|
// Ask the SD card to send its RCA.
|
||||||
|
u32 res = TMIO_sendCommand(port, SD_SEND_RELATIVE_ADDR, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
|
||||||
|
|
||||||
|
// RCA in upper 16 bits. Discards lower status bits of R6 response.
|
||||||
|
rca = port->resp[0]>>16;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->rca = rca;
|
||||||
|
*rcaOut = rca<<16;
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on UNSTUFF_BITS from linux/drivers/mmc/core/sd.c.
|
||||||
|
// Extracts up to 32 bits from a u32[4] array.
|
||||||
|
static inline u32 extractBits(const u32 resp[4], const u32 start, const u32 size)
|
||||||
|
{
|
||||||
|
const u32 mask = (size < 32 ? 1u<<size : 0u) - 1;
|
||||||
|
const u32 off = 3 - (start / 32);
|
||||||
|
const u32 shift = start & 31u;
|
||||||
|
|
||||||
|
u32 res = resp[off]>>shift;
|
||||||
|
if(size + shift > 32)
|
||||||
|
res |= resp[off - 1]<<((32u - shift) & 31u);
|
||||||
|
|
||||||
|
return res & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parseCsd(SdmmcDev *const dev, const u8 devType, u8 *const spec_vers_out)
|
||||||
|
{
|
||||||
|
// Note: The MSBs are in csd[0].
|
||||||
|
const u32 *const csd = dev->port.resp;
|
||||||
|
|
||||||
|
const u8 structure = extractBits(csd, 126, 2); // [127:126]
|
||||||
|
*spec_vers_out = extractBits(csd, 122, 4); // [125:122] All 0 for SD cards.
|
||||||
|
dev->ccc = extractBits(csd, 84, 12); // [95:84]
|
||||||
|
u32 sectors = 0;
|
||||||
|
if(structure == 0 || devType == DEV_TYPE_MMC) // structure = 0 is CSD version 1.0.
|
||||||
|
{
|
||||||
|
const u32 read_bl_len = extractBits(csd, 80, 4); // [83:80]
|
||||||
|
const u32 c_size = extractBits(csd, 62, 12); // [73:62]
|
||||||
|
const u32 c_size_mult = extractBits(csd, 47, 3); // [49:47]
|
||||||
|
|
||||||
|
// For SD cards with CSD 1.0 and <=2 GB (e)MMC this calculation is used.
|
||||||
|
// Note: READ_BL_LEN is at least 9.
|
||||||
|
// Modified/simplified to calculate sectors instead of bytes.
|
||||||
|
sectors = (c_size + 1)<<(c_size_mult + 2 + read_bl_len - 9);
|
||||||
|
}
|
||||||
|
else if(devType != DEV_TYPE_MMCHC)
|
||||||
|
{
|
||||||
|
// SD CSD version 3.0 format.
|
||||||
|
// For version 2.0 this is 22 bits however the upper bits
|
||||||
|
// are reserved and zero filled so this is fine.
|
||||||
|
const u32 c_size = extractBits(csd, 48, 28); // [75:48]
|
||||||
|
|
||||||
|
// Calculation for SD cards with CSD >1.0.
|
||||||
|
sectors = (c_size + 1)<<10;
|
||||||
|
}
|
||||||
|
// Else for high capacity (e)MMC the sectors will be read later from EXT_CSD.
|
||||||
|
dev->sectors = sectors;
|
||||||
|
|
||||||
|
// Parse temporary and permanent write protection bits.
|
||||||
|
u8 prot = extractBits(csd, 12, 1)<<1; // [12:12] Not checked by Linux.
|
||||||
|
prot |= extractBits(csd, 13, 1)<<2; // [13:13]
|
||||||
|
dev->prot |= prot;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 initStandbyState(SdmmcDev *const dev, const u8 devType, const u32 rca, u8 *const spec_vers_out)
|
||||||
|
{
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
// Get the CSD. CMD is the same for (e)MMC/SD.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SEND_CSD, rca);
|
||||||
|
if(res != 0) return SDMMC_ERR_SEND_CSD;
|
||||||
|
parseCsd(dev, devType, spec_vers_out);
|
||||||
|
|
||||||
|
// CMD is the same for (e)MMC/SD however both R1 and R1b responses are used.
|
||||||
|
// We assume R1b and hope it doesn't time out.
|
||||||
|
res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
|
||||||
|
if(res != 0) return SDMMC_ERR_SELECT_CARD;
|
||||||
|
|
||||||
|
// The SD card spec mentions that we should check the lock bit in the
|
||||||
|
// response to CMD7 to identify cards requiring a password to unlock.
|
||||||
|
// Same seems to apply for (e)MMC.
|
||||||
|
// Same bit for (e)MMC/SD R1 card status.
|
||||||
|
dev->prot |= (port->resp[0] & MMC_R1_CARD_IS_LOCKED)>>22; // Bit 3.
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Set the timeout based on clock speed (Tmio uses SDCLK for timeouts).
|
||||||
|
// The tmio driver sets a sane default but we should calculate it anyway.
|
||||||
|
static u32 initTranState(SdmmcDev *const dev, const u8 devType, const u32 rca, const u8 spec_vers)
|
||||||
|
{
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
if(IS_DEV_MMC(devType)) // (e)MMC.
|
||||||
|
{
|
||||||
|
// EXT_CSD, non-1 bit bus width and HS timing are only
|
||||||
|
// supported by (e)MMC SPEC_VERS 4.1 and higher.
|
||||||
|
if(spec_vers > 3) // Version 4.1–4.2–4.3 or higher.
|
||||||
|
{
|
||||||
|
// The (e)MMC spec says to check the card status after a SWITCH CMD (7.6.1).
|
||||||
|
// I think we can get away without checking this because support for HS timing
|
||||||
|
// and 4 bit bus width is mandatory for this spec version. If the card is
|
||||||
|
// non-standard we will encounter errors on the next CMD anyway.
|
||||||
|
// Switch to 4 bit bus mode.
|
||||||
|
const u32 busWidthArg = MMC_SWITCH_ARG(MMC_SWITCH_ACC_WR_BYTE, EXT_CSD_BUS_WIDTH, 1, 0);
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SWITCH, busWidthArg);
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
|
||||||
|
TMIO_setBusWidth(port, 4);
|
||||||
|
|
||||||
|
// We should also check in the EXT_CSD the power budget for the card.
|
||||||
|
// Nintendo seems to leave it on default (no change).
|
||||||
|
|
||||||
|
if(devType == DEV_TYPE_MMCHC)
|
||||||
|
{
|
||||||
|
// Note: The EXT_CSD is normally read before touching HS timing and bus width.
|
||||||
|
// We can take advantage of the faster data transfer with this order.
|
||||||
|
alignas(4) u8 ext_csd[512];
|
||||||
|
TMIO_setBuffer(port, (u32*)ext_csd, 1);
|
||||||
|
res = TMIO_sendCommand(port, MMC_SEND_EXT_CSD, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_SEND_EXT_CSD;
|
||||||
|
|
||||||
|
// Get sector count from EXT_CSD only if sector addressing is used because
|
||||||
|
// byte addressed (e)MMC may set sector count to 0.
|
||||||
|
dev->sectors = ext_csd[EXT_CSD_SEC_COUNT + 3]<<24 | ext_csd[EXT_CSD_SEC_COUNT + 2]<<16 |
|
||||||
|
ext_csd[EXT_CSD_SEC_COUNT + 1]<<8 | ext_csd[EXT_CSD_SEC_COUNT + 0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // SD card.
|
||||||
|
{
|
||||||
|
// Remove DAT3 pull-up. Linux doesn't do it but the SD spec recommends it.
|
||||||
|
u32 res = sendAppCmd(port, SD_APP_SET_CLR_CARD_DETECT, 0, rca); // arg = 0 removes the pull-up.
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_CLR_CD;
|
||||||
|
|
||||||
|
// Switch to 4 bit bus mode.
|
||||||
|
res = sendAppCmd(port, SD_APP_SET_BUS_WIDTH, 2, rca); // arg = 2 is 4 bit bus width.
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
|
||||||
|
TMIO_setBusWidth(port, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SD: The description for CMD SET_BLOCKLEN says 512 bytes is the default.
|
||||||
|
// (e)MMC: The description for READ_BL_LEN (CSD) says 512 bytes is the default.
|
||||||
|
// So it's not required to set the block length.
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_init(const u8 devNum)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
|
||||||
|
|
||||||
|
// Check SD card write protection slider.
|
||||||
|
if(devNum == SDMMC_DEV_CARD)
|
||||||
|
dev->prot = !TMIO_cardWritable();
|
||||||
|
|
||||||
|
// Init port, enable clock output and wait 74 clocks.
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
TMIO_initPort(port, devNum);
|
||||||
|
TMIO_powerupSequence(port); // Setup continuous clock and wait 74 clocks.
|
||||||
|
|
||||||
|
u32 res = goIdleState(port);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// (e)MMC/SD now in idle state (idle).
|
||||||
|
u8 devType;
|
||||||
|
res = initIdleState(port, &devType);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// Stop clock at idle, init clock.
|
||||||
|
TMIO_setClock(port, INIT_CLOCK);
|
||||||
|
|
||||||
|
// (e)MMC/SD now in ready state (ready).
|
||||||
|
res = initReadyState(dev);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// (e)MMC/SD now in identification state (ident).
|
||||||
|
u32 rca;
|
||||||
|
res = initIdentState(dev, devType, &rca);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// (e)MMC/SD now in stand-by state (stby).
|
||||||
|
// Maximum at this point would be 20 MHz for (e)MMC and 25 for SD.
|
||||||
|
// SD: We can increase the clock after end of identification state.
|
||||||
|
// TODO: eMMC spec section 7.6
|
||||||
|
// "Until the contents of the CSD register is known by the host,
|
||||||
|
// the fPP clock rate must remain at fOD. (See Section 12.7 on page 176.)"
|
||||||
|
// Since the absolute minimum clock rate is 20 MHz and we are in push-pull
|
||||||
|
// mode already can we cheat and switch to <=20 MHz before getting the CSD?
|
||||||
|
// Note: This seems to be working just fine in all tests.
|
||||||
|
TMIO_setClock(port, DEFAULT_CLOCK);
|
||||||
|
|
||||||
|
u8 spec_vers;
|
||||||
|
res = initStandbyState(dev, devType, rca, &spec_vers);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// (e)MMC/SD now in transfer state (tran).
|
||||||
|
res = initTranState(dev, devType, rca, spec_vers);
|
||||||
|
if(res != SDMMC_ERR_NONE) return res;
|
||||||
|
|
||||||
|
// Only set dev type on successful init.
|
||||||
|
dev->type = devType;
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
const u32 rca = (u32)dev->rca<<16;
|
||||||
|
const u8 devType = dev->type;
|
||||||
|
if(enabled)
|
||||||
|
{
|
||||||
|
// Deselect card to go back to stand-by state.
|
||||||
|
// CMD is the same for (e)MMC/SD.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_DESELECT_CARD, 0);
|
||||||
|
if(res != 0) return SDMMC_ERR_SELECT_CARD;
|
||||||
|
|
||||||
|
// Only (e)MMC can go into true sleep mode.
|
||||||
|
if(IS_DEV_MMC(devType))
|
||||||
|
{
|
||||||
|
// Switch (e)MMC into sleep mode.
|
||||||
|
res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca | 1u<<15);
|
||||||
|
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
|
||||||
|
// TODO: Power down eMMC. This is confirmed working on 3DS.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(IS_DEV_MMC(devType))
|
||||||
|
{
|
||||||
|
// TODO: Power up eMMC. This is confirmed working on 3DS.
|
||||||
|
// Wake (e)MMC up from sleep mode.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca);
|
||||||
|
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select card to go back to transfer state.
|
||||||
|
// CMD is the same for (e)MMC/SD.
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
|
||||||
|
if(res != 0) return SDMMC_ERR_SELECT_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Is there any "best practice" way of deinitializing cards?
|
||||||
|
// Kick the card back into idle state maybe?
|
||||||
|
// Linux seems to deselect cards on "suspend".
|
||||||
|
u32 pico_SDMMC_deinit(const u8 devNum)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
memset(&g_devs[devNum], 0, sizeof(SdmmcDev));
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen)
|
||||||
|
{
|
||||||
|
// Password length is maximum 16 bytes except when replacing a password.
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM || pwdLen > 32) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
// Set block length on (e)MMC/SD side and host.
|
||||||
|
// Same CMD for (e)MMC/SD.
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
const u32 blockLen = (mode != SDMMC_LK_ERASE ? 2 + pwdLen : 1);
|
||||||
|
u32 res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, blockLen);
|
||||||
|
if(res != 0) return SDMMC_ERR_SET_BLOCKLEN;
|
||||||
|
TMIO_setBlockLen(port, blockLen);
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// Prepare lock/unlock data block.
|
||||||
|
alignas(4) u8 buf[36] = {0}; // Size multiple of 4 (TMIO driver limitation).
|
||||||
|
buf[0] = mode;
|
||||||
|
buf[1] = pwdLen;
|
||||||
|
memcpy(&buf[2], pwd, pwdLen);
|
||||||
|
|
||||||
|
// Dirty hack to extend the data timeout to a bit over 4 minutes with TMIO controller.
|
||||||
|
// We need 3 minutes minimum for erase.
|
||||||
|
const u16 clk_ctrl_backup = port->sd_clk_ctrl;
|
||||||
|
TMIO_setClock(port, 130913);
|
||||||
|
|
||||||
|
// Note: Command class 7 support is mandatory for (e)MMC. Not for SD cards until 2.00.
|
||||||
|
// Same CMD for (e)MMC/SD.
|
||||||
|
TMIO_setBuffer(port, (u32*)buf, 1);
|
||||||
|
res = TMIO_sendCommand(port, MMC_LOCK_UNLOCK, 0);
|
||||||
|
port->sd_clk_ctrl = clk_ctrl_backup; // Undo the data timeout hack.
|
||||||
|
if(res != 0)
|
||||||
|
{
|
||||||
|
res = SDMMC_ERR_LOCK_UNLOCK;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore default block length and get the R1 status.
|
||||||
|
// Same CMD for (e)MMC/SD.
|
||||||
|
res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, 512);
|
||||||
|
if(res != 0)
|
||||||
|
{
|
||||||
|
res = SDMMC_ERR_SET_BLOCKLEN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
TMIO_setBlockLen(port, 512);
|
||||||
|
|
||||||
|
// Check if lock/unlock worked.
|
||||||
|
// Same bit for (e)MMC/SD R1 card status.
|
||||||
|
const u32 status = port->resp[0];
|
||||||
|
if(status & MMC_R1_LOCK_UNLOCK_FAILED)
|
||||||
|
res = SDMMC_ERR_LOCK_UNLOCK_FAIL;
|
||||||
|
|
||||||
|
// Update lock status.
|
||||||
|
const u8 prot = dev->prot & ~(1u<<3);
|
||||||
|
dev->prot = prot | (status>>22 & 1u<<3);
|
||||||
|
} while(0);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// People should not mess with the state which is the reason
|
||||||
|
// why the struct is not exposed directly.
|
||||||
|
static_assert(sizeof(SdmmcDev) == 64, "Wrong SDMMC dev export/import size.");
|
||||||
|
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64])
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
// Check if the device is initialized.
|
||||||
|
const SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
if(dev->type == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
|
||||||
|
|
||||||
|
memcpy(devOut, dev, 64);
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64])
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
// Make sure there is a card inserted.
|
||||||
|
if(devNum == SDMMC_DEV_CARD && !TMIO_cardDetected()) return SDMMC_ERR_NO_CARD;
|
||||||
|
|
||||||
|
// Check if the device is initialized.
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
|
||||||
|
|
||||||
|
memcpy(dev, devIn, 64);
|
||||||
|
|
||||||
|
// Update write protection slider state just in case.
|
||||||
|
dev->prot |= !TMIO_cardWritable();
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Less controller dependent code.
|
||||||
|
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
const SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
const TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
infoOut->type = dev->type;
|
||||||
|
infoOut->prot = dev->prot;
|
||||||
|
infoOut->rca = dev->rca;
|
||||||
|
infoOut->sectors = dev->sectors;
|
||||||
|
|
||||||
|
const u32 clkSetting = port->sd_clk_ctrl & 0xFFu;
|
||||||
|
infoOut->clock = TMIO_HCLK / (clkSetting ? clkSetting<<2 : 2);
|
||||||
|
|
||||||
|
memcpy(infoOut->cid, dev->cid, 16);
|
||||||
|
infoOut->ccc = dev->ccc;
|
||||||
|
infoOut->busWidth = (port->sd_option & OPTION_BUS_WIDTH1 ? 1 : 4);
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4])
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
if(cidOut != NULL) memcpy(cidOut, g_devs[devNum].cid, 16);
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*#include "fatfs/ff.h" // Needed for the "byte" type used in diskio.h.
|
||||||
|
#include "fatfs/diskio.h"
|
||||||
|
u8 SDMMC_getDiskStatus(const u8 devNum)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return STA_NODISK | STA_NOINIT;
|
||||||
|
|
||||||
|
u8 status = 0;
|
||||||
|
if(devNum == SDMMC_DEV_CARD)
|
||||||
|
status = (TMIO_cardDetected() == true ? 0 : STA_NODISK | STA_NOINIT);
|
||||||
|
|
||||||
|
const SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
status |= (dev->prot != 0 ? STA_PROTECT : 0);
|
||||||
|
if(dev->type == DEV_TYPE_NONE)
|
||||||
|
status |= STA_NOINIT;*/
|
||||||
|
|
||||||
|
// "Not valid if STA_NODISK is set."
|
||||||
|
/*if(status & STA_NODISK)
|
||||||
|
status &= ~STA_PROTECT;*/
|
||||||
|
|
||||||
|
// return status;
|
||||||
|
//}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_getSectors(const u8 devNum)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
|
||||||
|
|
||||||
|
return g_devs[devNum].sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 updateStatus(SdmmcDev *const dev, const bool stopTransmission)
|
||||||
|
{
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
|
||||||
|
// MMC_STOP_TRANSMISSION: Same CMD for (e)MMC/SD. Relies on the driver returning a proper response.
|
||||||
|
// MMC_SEND_STATUS: Same CMD for (e)MMC/SD but the argument format differs slightly.
|
||||||
|
u32 res;
|
||||||
|
if(stopTransmission) res = TMIO_sendCommand(port, MMC_STOP_TRANSMISSION, 0);
|
||||||
|
else res = TMIO_sendCommand(port, MMC_SEND_STATUS, (u32)dev->rca<<16);
|
||||||
|
dev->status = (res == 0 ? port->resp[0] : 0); // Don't update the status with stale data.
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: On multi-block read from the last 2 sectors there are no errors reported by the controller
|
||||||
|
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
|
||||||
|
// This error is normal for (e)MMC and can be ignored.
|
||||||
|
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
// Check if the device is initialized.
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
const u8 devType = dev->type;
|
||||||
|
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
|
||||||
|
|
||||||
|
// Set destination buffer and sector count.
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
TMIO_setBuffer(port, buf, count);
|
||||||
|
|
||||||
|
// Read a single 512 bytes block. Same CMD for (e)MMC/SD.
|
||||||
|
// Read multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
|
||||||
|
const u16 readCmd = (count == 1 ? MMC_READ_SINGLE_BLOCK : MMC_READ_MULTIPLE_BLOCK);
|
||||||
|
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
|
||||||
|
u32 res = TMIO_sendCommand(port, readCmd, sect);
|
||||||
|
if(res != 0)
|
||||||
|
{
|
||||||
|
// On error in the middle of multi-block reads the card will be stuck
|
||||||
|
// in data state and we need to send STOP_TRANSMISSION to bring it
|
||||||
|
// back to tran state.
|
||||||
|
// Otherwise for single-block reads just update the status.
|
||||||
|
updateStatus(dev, count > 1);
|
||||||
|
|
||||||
|
return SDMMC_ERR_SECT_RW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: On multi-block write to the last 2 sectors there are no errors reported by the controller
|
||||||
|
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
|
||||||
|
// This error is normal for (e)MMC and can be ignored.
|
||||||
|
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
// Check if the device is initialized.
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
const u8 devType = dev->type;
|
||||||
|
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
|
||||||
|
|
||||||
|
// Check if the device is write protected.
|
||||||
|
if(dev->prot != 0) return SDMMC_ERR_WRITE_PROT;
|
||||||
|
|
||||||
|
// Set source buffer and sector count.
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
TMIO_setBuffer(port, (void*)buf, count);
|
||||||
|
|
||||||
|
// Write a single 512 bytes block. Same CMD for (e)MMC/SD.
|
||||||
|
// Write multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
|
||||||
|
const u16 writeCmd = (count == 1 ? MMC_WRITE_BLOCK : MMC_WRITE_MULTIPLE_BLOCK);
|
||||||
|
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
|
||||||
|
const u32 res = TMIO_sendCommand(port, writeCmd, sect);
|
||||||
|
if(res != 0)
|
||||||
|
{
|
||||||
|
// On error in the middle of multi-block writes the card will be stuck
|
||||||
|
// in data state and we need to send STOP_TRANSMISSION to bring it
|
||||||
|
// back to tran state.
|
||||||
|
// Otherwise for single-block writes just update the status.
|
||||||
|
updateStatus(dev, count > 1);
|
||||||
|
|
||||||
|
return SDMMC_ERR_SECT_RW;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
|
||||||
|
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
TmioPort *const port = &dev->port;
|
||||||
|
TMIO_setBlockLen(port, mmcCmd->blkLen);
|
||||||
|
TMIO_setBuffer(port, mmcCmd->buf, mmcCmd->count);
|
||||||
|
|
||||||
|
const u32 res = TMIO_sendCommand(port, mmcCmd->cmd, mmcCmd->arg);
|
||||||
|
TMIO_setBlockLen(port, 512); // Restore default block length.
|
||||||
|
if(res != 0)
|
||||||
|
{
|
||||||
|
updateStatus(dev, false);
|
||||||
|
return SDMMC_ERR_SEND_CMD;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(mmcCmd->resp, port->resp, 16);
|
||||||
|
|
||||||
|
return SDMMC_ERR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 pico_SDMMC_getLastR1error(const u8 devNum)
|
||||||
|
{
|
||||||
|
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
|
||||||
|
|
||||||
|
SdmmcDev *const dev = &g_devs[devNum];
|
||||||
|
const u32 status = dev->status;
|
||||||
|
dev->status = 0;
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
414
arm7/source/mmc/tmio.h
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <nds/ndstypes.h>
|
||||||
|
#include <libtwl/sys/swi.h>
|
||||||
|
#include <libtwl/rtos/rtosIrq.h>
|
||||||
|
#include <libtwl/rtos/rtosEvent.h>
|
||||||
|
|
||||||
|
|
||||||
|
// For simplicity we will name the accessible 2 controllers 1 and 2.
|
||||||
|
// The real controller number is in the comment.
|
||||||
|
#define TMIO1_REGS_BASE (0x04004800u) // Controller 1.
|
||||||
|
#define TMIO2_REGS_BASE (0x04004A00u) // Controller 2.
|
||||||
|
|
||||||
|
#define TMIO_HCLK (33513982u) // In Hz.
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
vu16 sd_cmd; // 0x000
|
||||||
|
vu16 sd_portsel; // 0x002
|
||||||
|
vu32 sd_arg; // 0x004 SD_ARG0 and SD_ARG1 combined.
|
||||||
|
vu16 sd_stop; // 0x008
|
||||||
|
vu16 sd_blockcount; // 0x00A
|
||||||
|
const vu32 sd_resp[4]; // 0x00C SD_RESP0-7 16 bit reg pairs combined.
|
||||||
|
vu32 sd_status; // 0x01C SD_STATUS1 and SD_STATUS2 combined.
|
||||||
|
vu32 sd_status_mask; // 0x020 SD_STATUS1_MASK and SD_STATUS2_MASK combined.
|
||||||
|
vu16 sd_clk_ctrl; // 0x024
|
||||||
|
vu16 sd_blocklen; // 0x026
|
||||||
|
vu16 sd_option; // 0x028 Card detect timer, data timeout and bus width.
|
||||||
|
u8 _0x2a[2];
|
||||||
|
const vu32 sd_err_status; // 0x02C SD_ERR_STATUS1 and SD_ERR_STATUS2 combined.
|
||||||
|
vu16 sd_fifo; // 0x030
|
||||||
|
u8 _0x32[2];
|
||||||
|
vu16 sdio_mode; // 0x034
|
||||||
|
vu16 sdio_status; // 0x036
|
||||||
|
vu16 sdio_status_mask; // 0x038
|
||||||
|
u8 _0x3a[0x9e];
|
||||||
|
vu16 dma_ext_mode; // 0x0D8
|
||||||
|
u8 _0xda[6];
|
||||||
|
vu16 soft_rst; // 0x0E0
|
||||||
|
const vu16 revision; // 0x0E2 Controller version/revision?
|
||||||
|
u8 _0xe4[0xe];
|
||||||
|
vu16 unkF2; // 0x0F2 Power related? Default 0. Other values do nothing?
|
||||||
|
vu16 ext_sdio_irq; // 0x0F4 Port 1/2/3 SDIO IRQ control.
|
||||||
|
const vu16 ext_wrprot; // 0x0F6 Apparently for eMMC.
|
||||||
|
vu16 ext_cdet; // 0x0F8 Card detect status.
|
||||||
|
vu16 ext_cdet_dat3; // 0x0FA DAT3 card detect status.
|
||||||
|
vu16 ext_cdet_mask; // 0x0FC Card detect mask (IRQ).
|
||||||
|
vu16 ext_cdet_dat3_mask; // 0x0FE DAT3 card detect mask (IRQ).
|
||||||
|
vu16 sd_fifo32_cnt; // 0x100
|
||||||
|
u8 _0x102[2];
|
||||||
|
vu16 sd_blocklen32; // 0x104
|
||||||
|
u8 _0x106[2];
|
||||||
|
vu16 sd_blockcount32; // 0x108
|
||||||
|
u8 _0x10a[2];
|
||||||
|
vu32 sd_fifo32; // 0x10C Note: This is in the FIFO region on ARM11 (3DS).
|
||||||
|
} Tmio;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
static_assert(offsetof(Tmio, sd_fifo32) == 0x10C, "Error: Member sd_fifo32 of Tmio is not at offset 0x10C!");
|
||||||
|
|
||||||
|
__attribute__((always_inline)) static inline Tmio* getTmioRegs(const u8 controller)
|
||||||
|
{
|
||||||
|
return (controller == 0 ? (Tmio*)TMIO1_REGS_BASE : (Tmio*)TMIO2_REGS_BASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((always_inline)) static inline vu32* getTmioFifo(Tmio *const regs)
|
||||||
|
{
|
||||||
|
return ®s->sd_fifo32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
// REG_SD_CMD
|
||||||
|
// Auto response supported commands:
|
||||||
|
// CMD0, CMD2, CMD3 (only SD?), CMD7 (only select?), CMD9, CMD10, CMD12, CMD13,
|
||||||
|
// CMD16, CMD17, CMD18, CMD25, CMD28, CMD55, ACMD6, ACMD23, ACMD42, ACMD51.
|
||||||
|
//
|
||||||
|
// When using auto response leave bits 11-13 unset (zero).
|
||||||
|
|
||||||
|
// Bit 0-5 command index.
|
||||||
|
#define CMD_ACMD (1u<<6) // Application command.
|
||||||
|
#define CMD_RESP_AUTO (0u) // Response type auto. Only works with certain commands.
|
||||||
|
#define CMD_RESP_NONE (3u<<8) // Response type none.
|
||||||
|
#define CMD_RESP_R1 (4u<<8) // Response type R1 48 bit.
|
||||||
|
#define CMD_RESP_R5 (CMD_RESP_R1) // Response type R5 48 bit.
|
||||||
|
#define CMD_RESP_R6 (CMD_RESP_R1) // Response type R6 48 bit.
|
||||||
|
#define CMD_RESP_R7 (CMD_RESP_R1) // Response type R7 48 bit.
|
||||||
|
#define CMD_RESP_R1b (5u<<8) // Response type R1b 48 bit + busy.
|
||||||
|
#define CMD_RESP_R5b (CMD_RESP_R1b) // Response type R5b 48 bit + busy.
|
||||||
|
#define CMD_RESP_R2 (6u<<8) // Response type R2 136 bit.
|
||||||
|
#define CMD_RESP_R3 (7u<<8) // Response type R3 48 bit OCR without CRC.
|
||||||
|
#define CMD_RESP_R4 (CMD_RESP_R3) // Response type R4 48 bit OCR without CRC.
|
||||||
|
#define CMD_RESP_MASK (CMD_RESP_R3)
|
||||||
|
#define CMD_DATA_EN (1u<<11) // Data transfer enable.
|
||||||
|
#define CMD_DATA_R (1u<<12) // Data transfer direction read.
|
||||||
|
#define CMD_DATA_W (0u) // Data transfer direction write.
|
||||||
|
#define CMD_MULTI_DATA (1u<<13) // Multi block transfer (auto STOP_TRANSMISSION).
|
||||||
|
#define CMD_SEC_SDIO (1u<<14) // Security/SDIO command.
|
||||||
|
|
||||||
|
// REG_SD_PORTSEL
|
||||||
|
#define PORTSEL_P0 (0u) // Controller port 0.
|
||||||
|
#define PORTSEL_P1 (1u) // Controller port 1.
|
||||||
|
#define PORTSEL_P2 (2u) // Controller port 2.
|
||||||
|
#define PORTSEL_P3 (3u) // Controller port 3.
|
||||||
|
#define PORTSEL_MASK (PORTSEL_P3)
|
||||||
|
// Bit 8-9 number of supported ports?
|
||||||
|
#define PORTSEL_UNK10 (1u<<10) // Unknown writable bit 10?
|
||||||
|
|
||||||
|
// REG_SD_STOP
|
||||||
|
#define STOP_STOP (1u) // Abort data transfer and send STOP_TRANSMISSION CMD.
|
||||||
|
#define STOP_AUTO_STOP (1u<<8) // Automatically send STOP_TRANSMISSION on multi-block transfer end.
|
||||||
|
|
||||||
|
// REG_SD_STATUS1/2 Write 0 to acknowledge a bit.
|
||||||
|
// REG_SD_STATUS1/2_MASK (M) = Maskable bit. 1 = disabled.
|
||||||
|
// Unmaskable bits act as status only, don't trigger IRQs and can't be acknowledged.
|
||||||
|
#define STATUS_RESP_END (1u) // (M) Response end.
|
||||||
|
#define STATUS_DATA_END (1u<<2) // (M) Data transfer end (triggers after last block).
|
||||||
|
#define STATUS_REMOVE (1u<<3) // (M) Card got removed.
|
||||||
|
#define STATUS_INSERT (1u<<4) // (M) Card got inserted. Set at the same time as DETECT.
|
||||||
|
#define STATUS_DETECT (1u<<5) // Card detect status (SD_OPTION detection timer). 1 = inserted.
|
||||||
|
#define STATUS_NO_WRPROT (1u<<7) // Write protection slider unlocked (low).
|
||||||
|
#define STATUS_DAT3_REMOVE (1u<<8) // (M) Card DAT3 got removed (low).
|
||||||
|
#define STATUS_DAT3_INSERT (1u<<9) // (M) Card DAT3 got inserted (high).
|
||||||
|
#define STATUS_DAT3_DETECT (1u<<10) // Card DAT3 status. 1 = inserted.
|
||||||
|
#define STATUS_ERR_CMD_IDX (1u<<16) // (M) Bad CMD index in response.
|
||||||
|
#define STATUS_ERR_CRC (1u<<17) // (M) Bad CRC in response.
|
||||||
|
#define STATUS_ERR_STOP_BIT (1u<<18) // (M) Stop bit error. Failed to recognize response frame end?
|
||||||
|
#define STATUS_ERR_DATA_TIMEOUT (1u<<19) // (M) Response data timeout.
|
||||||
|
#define STATUS_ERR_RX_OVERF (1u<<20) // (M) Receive FIFO overflow.
|
||||||
|
#define STATUS_ERR_TX_UNDERF (1u<<21) // (M) Send FIFO underflow.
|
||||||
|
#define STATUS_ERR_CMD_TIMEOUT (1u<<22) // (M) Response start bit timeout.
|
||||||
|
#define STATUS_SD_BUSY (1u<<23) // SD card signals busy if this bit is 0 (DAT0 held low).
|
||||||
|
#define STATUS_RX_RDY (1u<<24) // (M) FIFO ready for read.
|
||||||
|
#define STATUS_TX_REQ (1u<<25) // (M) FIFO write request.
|
||||||
|
// Bit 27 is maskable. Purpose unknown.
|
||||||
|
// Bit 29 exists (not maskable). Signals when clock divider changes are allowed?
|
||||||
|
#define STATUS_CMD_BUSY (1u<<30) // Command register busy.
|
||||||
|
#define STATUS_ERR_ILL_ACC (1u<<31) // (M) Illegal access error. TODO: What does that mean?
|
||||||
|
|
||||||
|
#define STATUS_MASK_ALL (0xFFFFFFFFu)
|
||||||
|
#define STATUS_MASK_DEFAULT ((1u<<27) | STATUS_TX_REQ | STATUS_RX_RDY | \
|
||||||
|
STATUS_DAT3_INSERT | STATUS_DAT3_REMOVE)
|
||||||
|
#define STATUS_MASK_ERR (STATUS_ERR_ILL_ACC | STATUS_ERR_CMD_TIMEOUT | STATUS_ERR_TX_UNDERF | \
|
||||||
|
STATUS_ERR_RX_OVERF | STATUS_ERR_DATA_TIMEOUT | STATUS_ERR_STOP_BIT | \
|
||||||
|
STATUS_ERR_CRC | STATUS_ERR_CMD_IDX)
|
||||||
|
|
||||||
|
// REG_SD_CLK_CTRL
|
||||||
|
#define SD_CLK_DIV_2 (0u) // Clock divider 2.
|
||||||
|
#define SD_CLK_DIV_4 (1u) // Clock divider 4.
|
||||||
|
#define SD_CLK_DIV_8 (1u<<1) // Clock divider 8.
|
||||||
|
#define SD_CLK_DIV_16 (1u<<2) // Clock divider 16.
|
||||||
|
#define SD_CLK_DIV_32 (1u<<3) // Clock divider 32.
|
||||||
|
#define SD_CLK_DIV_64 (1u<<4) // Clock divider 64.
|
||||||
|
#define SD_CLK_DIV_128 (1u<<5) // Clock divider 128.
|
||||||
|
#define SD_CLK_DIV_256 (1u<<6) // Clock divider 256.
|
||||||
|
#define SD_CLK_DIV_512 (1u<<7) // Clock divider 512.
|
||||||
|
#define SD_CLK_EN (1u<<8) // Clock enable.
|
||||||
|
#define SD_CLK_PWR_SAVE (1u<<9) // Disables clock on idle.
|
||||||
|
// Bit 10 is writable... at least according to gbatek (can't confirm). Purpose unknown.
|
||||||
|
|
||||||
|
// Outputs the matching divider for clk.
|
||||||
|
// Shift the output right by 2 to get the value for REG_SD_CLK_CTRL.
|
||||||
|
#define TMIO_CLK2DIV(clk) \
|
||||||
|
({ \
|
||||||
|
u32 __shift = 1; \
|
||||||
|
while((clk) < TMIO_HCLK>>__shift) ++__shift; \
|
||||||
|
1u<<__shift; \
|
||||||
|
})
|
||||||
|
|
||||||
|
// Clock off by default.
|
||||||
|
// Nearest possible for 400 kHz is 261.827984375 kHz.
|
||||||
|
#define SD_CLK_DEFAULT (TMIO_CLK2DIV(400000)>>2)
|
||||||
|
|
||||||
|
// REG_SD_OPTION
|
||||||
|
// Note on card detection time:
|
||||||
|
// The card detection timer starts only on inserting cards (including cold boot with inserted card)
|
||||||
|
// and when mapping ports between controllers. Card power doesn't have any effect on the timer.
|
||||||
|
//
|
||||||
|
// Bit 0-3 card detect timer 0x400<<x HCLKs. 0xF timer test (0x100 HCLKs).
|
||||||
|
// Bit 4-7 data timeout 0x2000<<x SDCLKs. 0xF timeout test (0x100 SDCLKs).
|
||||||
|
#define OPTION_UNK14 (1u<<14) // "no C2 module" What the fuck is a C2 module?
|
||||||
|
#define OPTION_BUS_WIDTH4 (0u) // 4 bit bus width.
|
||||||
|
#define OPTION_BUS_WIDTH1 (1u<<15) // 1 bit bus width.
|
||||||
|
|
||||||
|
// Card detect time: 0x400<<8 / 33513982 = 0.007821929 seconds.
|
||||||
|
// Data timeout: 0x2000<<11 / (33513982 / 2) = 1.001206959 seconds.
|
||||||
|
#define OPTION_DEFAULT_TIMINGS (11u<<4 | 8u)
|
||||||
|
|
||||||
|
// REG_SD_ERR_STATUS1/2 Write 0 to acknowledge a bit.
|
||||||
|
// TODO: Are all of these actually supported on this controller?
|
||||||
|
#define ERR_RESP_CMD_IDX (1u) // Manual command index error in response.
|
||||||
|
#define ERR_RESP_CMD12_IDX (1u<<1) // Auto command index error in response.
|
||||||
|
#define ERR_RESP_STOP_BIT (1u<<2) // Manual command response stop bit error.
|
||||||
|
#define ERR_RESP_STOP_BIT_CMD12 (1u<<3) // Auto command response stop bit error.
|
||||||
|
#define ERR_STOP_BIT_DATA_READ (1u<<4) // Stop bit error in read data.
|
||||||
|
#define ERR_STOP_BIT_WR_CRC (1u<<5) // Stop bit error for write CRC status. What the hell does that mean?
|
||||||
|
#define ERR_CMD_RESP_CRC (1u<<8) // Manual command response CRC error.
|
||||||
|
#define ERR_CMD12_RESP_CRC (1u<<9) // Auto command response CRC error.
|
||||||
|
#define ERR_DATA_READ_CRC (1u<<10) // CRC error for read data.
|
||||||
|
#define ERR_WR_CRC_STAT (1u<<11) // "CRC error for Write CRC status for a write command". What the hell does that mean?
|
||||||
|
// Bit 13 always 1.
|
||||||
|
#define ERR_CMD_RESP_TMOUT (1u<<16) // Manual command response timeout.
|
||||||
|
#define ERR_CMD12_RESP_TMOUT (1u<<17) // Auto command response timeout.
|
||||||
|
// TODO: Add the correct remaining ones.
|
||||||
|
|
||||||
|
// REG_SDIO_MODE
|
||||||
|
#define SDIO_MODE_SDIO_IRQ_EN (1u) // SDIO IRQ enable (DAT1 low).
|
||||||
|
#define SDIO_MODE_UNK2_EN (1u<<2) // IRQ on "read wait" requests?
|
||||||
|
#define SDIO_MODE_UNK8 (1u<<8) // Aborts command and data transfer?
|
||||||
|
#define SDIO_MODE_UNK9 (1u<<9) // Aborts command but not data transfer? CMD52 related.
|
||||||
|
|
||||||
|
// REG_SDIO_STATUS Write 0 to acknowledge a bit.
|
||||||
|
// REG_SDIO_STATUS_MASK (M) = Maskable bit. 1 = disabled.
|
||||||
|
#define SDIO_STATUS_SDIO_IRQ (1u) // (M) SDIO IRQ (DAT1 low).
|
||||||
|
#define SDIO_STATUS_UNK1_IRQ (1u<<1) // (M) IRQ once CMD52 can be used after abort?
|
||||||
|
#define SDIO_STATUS_UNK2_IRQ (1u<<2) // (M) Related to SDIO_MODE_UNK2_EN?
|
||||||
|
#define SDIO_STATUS_UNK14_IRQ (1u<<14) // (M) Related to SDIO_MODE_UNK9?
|
||||||
|
#define SDIO_STATUS_UNK15_IRQ (1u<<15) // (M) Related to SDIO_MODE_UNK2_EN?
|
||||||
|
|
||||||
|
#define SDIO_STATUS_MASK_ALL (0xFFFFu)
|
||||||
|
|
||||||
|
// REG_DMA_EXT_MODE
|
||||||
|
#define DMA_EXT_CPU_MODE (0u) // Disables DMA requests. Actually also turns off the 32 bit FIFO.
|
||||||
|
#define DMA_EXT_DMA_MODE (1u<<1) // Enables DMA requests.
|
||||||
|
#define DMA_EXT_UNK5 (1u<<5) // "Buffer status mode"?
|
||||||
|
|
||||||
|
// REG_SOFT_RST
|
||||||
|
#define SOFT_RST_RST (0u) // Reset.
|
||||||
|
#define SOFT_RST_NORST (1u) // No reset.
|
||||||
|
|
||||||
|
// REG_EXT_SDIO_IRQ
|
||||||
|
#define EXT_SDIO_IRQ_P1 (1u) // Port 1 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
|
||||||
|
#define EXT_SDIO_IRQ_P2 (1u<<1) // Port 2 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
|
||||||
|
#define EXT_SDIO_IRQ_P3 (1u<<2) // Port 3 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
|
||||||
|
#define EXT_SDIO_IRQ_P1_EN (1u<<4) // Port 1 SDIO IRQ enable (controller).
|
||||||
|
#define EXT_SDIO_IRQ_P2_EN (1u<<5) // Port 2 SDIO IRQ enable (controller).
|
||||||
|
#define EXT_SDIO_IRQ_P3_EN (1u<<6) // Port 3 SDIO IRQ enable (controller).
|
||||||
|
#define EXT_SDIO_IRQ_P1_MASK (1u<<8) // Port 1 SDIO IRQ mask. 1 = disable IRQ (CPU).
|
||||||
|
#define EXT_SDIO_IRQ_P2_MASK (1u<<9) // Port 2 SDIO IRQ mask. 1 = disable IRQ (CPU).
|
||||||
|
#define EXT_SDIO_IRQ_P3_MASK (1u<<10) // Port 3 SDIO IRQ mask. 1 = disable IRQ (CPU).
|
||||||
|
|
||||||
|
#define EXT_SDIO_IRQ_MASK_ALL (EXT_SDIO_IRQ_P3_MASK | EXT_SDIO_IRQ_P2_MASK | EXT_SDIO_IRQ_P1_MASK)
|
||||||
|
|
||||||
|
// REG_EXT_WRPROT Each bit 1 = write protected unlike SD_STATUS.
|
||||||
|
#define EXT_WRPROT_P1 (1u)
|
||||||
|
#define EXT_WRPROT_P2 (1u<<1)
|
||||||
|
#define EXT_WRPROT_P3 (1u<<2)
|
||||||
|
|
||||||
|
// REG_EXT_CDET Acknowledgeable?
|
||||||
|
// REG_EXT_CDET_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
|
||||||
|
#define EXT_CDET_P1_REMOVE (1u) // (M) Port 1 card got removed.
|
||||||
|
#define EXT_CDET_P1_INSERT (1u<<1) // (M) Port 1 card got inserted. TODO: With detection timer?
|
||||||
|
#define EXT_CDET_P1_DETECT (1u<<2) // Port 1 card detect status. 1 = inserted. TODO: With detection timer?
|
||||||
|
#define EXT_CDET_P2_REMOVE (1u<<3) // (M) Port 2 card got removed.
|
||||||
|
#define EXT_CDET_P2_INSERT (1u<<4) // (M) Port 2 card got inserted. TODO: With detection timer?
|
||||||
|
#define EXT_CDET_P2_DETECT (1u<<5) // Port 2 card detect status. 1 = inserted. TODO: With detection timer?
|
||||||
|
#define EXT_CDET_P3_REMOVE (1u<<6) // (M) Port 3 card got removed.
|
||||||
|
#define EXT_CDET_P3_INSERT (1u<<7) // (M) Port 3 card got inserted. TODO: With detection timer?
|
||||||
|
#define EXT_CDET_P3_DETECT (1u<<8) // Port 3 card detect status. 1 = inserted. TODO: With detection timer?
|
||||||
|
|
||||||
|
#define EXT_CDET_MASK_ALL (0xFFFFu)
|
||||||
|
|
||||||
|
// REG_EXT_CDET_DAT3 Acknowledgeable?
|
||||||
|
// REG_EXT_CDET_DAT3_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
|
||||||
|
#define EXT_CDET_DAT3_P1_REMOVE (1u) // (M) Port 1 card DAT3 got removed (low).
|
||||||
|
#define EXT_CDET_DAT3_P1_INSERT (1u<<1) // (M) Port 1 card DAT3 got inserted (high).
|
||||||
|
#define EXT_CDET_DAT3_P1_DETECT (1u<<2) // Port 1 card DAT3 status. 1 = inserted.
|
||||||
|
#define EXT_CDET_DAT3_P2_REMOVE (1u<<3) // (M) Port 2 card DAT3 got removed (low).
|
||||||
|
#define EXT_CDET_DAT3_P2_INSERT (1u<<4) // (M) Port 2 card DAT3 got inserted (high).
|
||||||
|
#define EXT_CDET_DAT3_P2_DETECT (1u<<5) // Port 2 card DAT3 status. 1 = inserted.
|
||||||
|
#define EXT_CDET_DAT3_P3_REMOVE (1u<<6) // (M) Port 3 card DAT3 got removed (low).
|
||||||
|
#define EXT_CDET_DAT3_P3_INSERT (1u<<7) // (M) Port 3 card DAT3 got inserted (high).
|
||||||
|
#define EXT_CDET_DAT3_P3_DETECT (1u<<8) // Port 3 card DAT3 status. 1 = inserted.
|
||||||
|
|
||||||
|
#define EXT_CDET_DAT3_MASK_ALL (0xFFFFu)
|
||||||
|
|
||||||
|
// REG_SD_FIFO32_CNT
|
||||||
|
// Bit 0 unknown, non-writable.
|
||||||
|
#define FIFO32_EN (1u<<1) // Enables the 32 bit FIFO.
|
||||||
|
#define FIFO32_FULL (1u<<8) // FIFO is full.
|
||||||
|
#define FIFO32_NOT_EMPTY (1u<<9) // FIFO is not empty. Inverted bit. 0 means empty.
|
||||||
|
#define FIFO32_CLEAR (1u<<10) // Clears the FIFO.
|
||||||
|
#define FIFO32_FULL_IE (1u<<11) // FIFO full IRQ enable.
|
||||||
|
#define FIFO32_NOT_EMPTY_IE (1u<<12) // FIFO not empty IRQ enable.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
u8 portNum;
|
||||||
|
u16 sd_clk_ctrl;
|
||||||
|
u16 sd_blocklen; // Also sd_blocklen32.
|
||||||
|
u16 sd_option;
|
||||||
|
void *buf;
|
||||||
|
u16 blocks;
|
||||||
|
u32 resp[4]; // Little endian, MSB first.
|
||||||
|
} TmioPort;
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
/**
|
||||||
|
* @brief Initializes the tmio driver.
|
||||||
|
*/
|
||||||
|
void TMIO_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deinitializes the tmio driver.
|
||||||
|
*/
|
||||||
|
void TMIO_deinit(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initializes a tmio port to defaults.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param[in] portNum The port number.
|
||||||
|
*/
|
||||||
|
void TMIO_initPort(TmioPort *const port, const u8 portNum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a MMC/SD card is inserted.
|
||||||
|
*
|
||||||
|
* @return Returns true if a card is inserted.
|
||||||
|
*/
|
||||||
|
bool TMIO_cardDetected(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the write protect slider is set to locked.
|
||||||
|
*
|
||||||
|
* @return Returns true if the card is unlocked.
|
||||||
|
*/
|
||||||
|
bool TMIO_cardWritable(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Handles the device specific powerup sequence including the 74 clocks.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
*/
|
||||||
|
void TMIO_powerupSequence(TmioPort *const port);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends a command.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param[in] cmd The command.
|
||||||
|
* @param[in] arg The argument for the command.
|
||||||
|
*
|
||||||
|
* @return Returns 0 on success otherwise see REG_SD_STATUS1/2 bits.
|
||||||
|
*/
|
||||||
|
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the clock for a tmio port.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param[in] clk The target clock in Hz.
|
||||||
|
*/
|
||||||
|
__attribute__((always_inline)) static inline void TMIO_setClock(TmioPort *const port, const u32 clk)
|
||||||
|
{
|
||||||
|
port->sd_clk_ctrl = SD_CLK_PWR_SAVE | SD_CLK_EN | TMIO_CLK2DIV(clk)>>2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the transfer block length for a tmio port.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param[in] blockLen The block length. Caution: Provide a buffer with multiple of 4 size regardless of block length.
|
||||||
|
*/
|
||||||
|
__attribute__((always_inline)) static inline void TMIO_setBlockLen(TmioPort *const port, u16 blockLen)
|
||||||
|
{
|
||||||
|
if(blockLen > 512) blockLen = 512;
|
||||||
|
|
||||||
|
port->sd_blocklen = blockLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the bus width for a tmio port.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param[in] width The bus width.
|
||||||
|
*/
|
||||||
|
__attribute__((always_inline)) static inline void TMIO_setBusWidth(TmioPort *const port, const u8 width)
|
||||||
|
{
|
||||||
|
port->sd_option = (width == 4 ? OPTION_BUS_WIDTH4 : OPTION_BUS_WIDTH1) |
|
||||||
|
OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets a transfer buffer for a tmio port.
|
||||||
|
*
|
||||||
|
* @param port A pointer to the port struct.
|
||||||
|
* @param buf The buffer pointer.
|
||||||
|
* @param[in] blocks The number of blocks to transfer.
|
||||||
|
*/
|
||||||
|
__attribute__((always_inline)) static inline void TMIO_setBuffer(TmioPort *const port, void *buf, const u16 blocks)
|
||||||
|
{
|
||||||
|
port->buf = buf;
|
||||||
|
port->blocks = blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
279
arm7/source/mmc/tmio.twl.c
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
// Copyright (c) 2023 profi200
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
|
#include "tmio.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Using atomic load/store produces better code than volatile
|
||||||
|
// but still ensures that the status is always read from memory.
|
||||||
|
#define GET_STATUS(ptr) atomic_load_explicit((ptr), memory_order_relaxed)
|
||||||
|
#define SET_STATUS(ptr, val) atomic_store_explicit((ptr), (val), memory_order_relaxed)
|
||||||
|
|
||||||
|
// ARM7 timer clock = controller clock = CPU clock.
|
||||||
|
// swiDelay() doesn't seem to be cycle accurate meaning
|
||||||
|
// one cycle is 4 (?) CPU cycles.
|
||||||
|
#define INIT_DELAY_FUNC() swi_waitByLoop(TMIO_CLK2DIV(400000u) * 74 / 4)
|
||||||
|
|
||||||
|
|
||||||
|
static u32 g_status[2] = {0};
|
||||||
|
|
||||||
|
static rtos_event_t sSdEvent;
|
||||||
|
|
||||||
|
__attribute__((always_inline)) static inline u8 port2Controller(const u8 portNum)
|
||||||
|
{
|
||||||
|
return portNum / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tmio1Isr(u32 irqMask) // SD/eMMC.
|
||||||
|
{
|
||||||
|
Tmio *const regs = getTmioRegs(0);
|
||||||
|
g_status[0] |= regs->sd_status;
|
||||||
|
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
|
||||||
|
rtos_signalEvent(&sSdEvent);
|
||||||
|
|
||||||
|
// TODO: Some kind of event to notify the main loop for remove/insert.
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tmio2Isr(u32 irqMask) // WiFi SDIO.
|
||||||
|
{
|
||||||
|
Tmio *const regs = getTmioRegs(1);
|
||||||
|
g_status[1] |= regs->sd_status;
|
||||||
|
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMIO_init(void)
|
||||||
|
{
|
||||||
|
rtos_createEvent(&sSdEvent);
|
||||||
|
// Register ISR and enable IRQs.
|
||||||
|
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, tmio1Isr);
|
||||||
|
rtos_setIrq2Func(RTOS_IRQ2_SDIO, tmio2Isr);
|
||||||
|
rtos_enableIrq2Mask(RTOS_IRQ2_SDMMC);
|
||||||
|
rtos_enableIrq2Mask(RTOS_IRQ2_SDIO);
|
||||||
|
|
||||||
|
// Reset all controllers.
|
||||||
|
for(u32 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
// Setup 32 bit FIFO.
|
||||||
|
Tmio *const regs = getTmioRegs(i);
|
||||||
|
regs->sd_fifo32_cnt = FIFO32_CLEAR | FIFO32_EN;
|
||||||
|
regs->sd_blocklen32 = 512;
|
||||||
|
regs->sd_blockcount32 = 1;
|
||||||
|
regs->dma_ext_mode = DMA_EXT_DMA_MODE;
|
||||||
|
|
||||||
|
// Reset. Unlike similar controllers no delay is needed.
|
||||||
|
// Resets the following regs:
|
||||||
|
// REG_SD_STOP, REG_SD_RESP0-7, REG_SD_STATUS1-2, REG_SD_ERR_STATUS1-2,
|
||||||
|
// REG_SD_CLK_CTRL, REG_SD_OPTION, REG_SDIO_STATUS.
|
||||||
|
regs->soft_rst = SOFT_RST_RST;
|
||||||
|
regs->soft_rst = SOFT_RST_NORST;
|
||||||
|
|
||||||
|
regs->sd_portsel = PORTSEL_P0;
|
||||||
|
regs->sd_blockcount = 1;
|
||||||
|
regs->sd_status_mask = STATUS_MASK_DEFAULT;
|
||||||
|
regs->sd_clk_ctrl = SD_CLK_DEFAULT;
|
||||||
|
regs->sd_blocklen = 512;
|
||||||
|
regs->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
|
||||||
|
regs->ext_cdet_mask = EXT_CDET_MASK_ALL;
|
||||||
|
regs->ext_cdet_dat3_mask = EXT_CDET_DAT3_MASK_ALL;
|
||||||
|
|
||||||
|
// Disable SDIO.
|
||||||
|
regs->sdio_mode = 0;
|
||||||
|
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
|
||||||
|
regs->ext_sdio_irq = EXT_SDIO_IRQ_MASK_ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMIO_deinit(void)
|
||||||
|
{
|
||||||
|
rtos_disableIrq2Mask(RTOS_IRQ2_SDMMC);
|
||||||
|
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, NULL);
|
||||||
|
rtos_disableIrq2Mask(RTOS_IRQ2_SDIO);
|
||||||
|
rtos_setIrq2Func(RTOS_IRQ2_SDIO, NULL);
|
||||||
|
|
||||||
|
// Mask all IRQs.
|
||||||
|
for(u32 i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
// 32 bit FIFO IRQs.
|
||||||
|
Tmio *const regs = getTmioRegs(i);
|
||||||
|
regs->sd_fifo32_cnt = 0; // FIFO and all IRQs disabled/masked.
|
||||||
|
|
||||||
|
// Regular IRQs.
|
||||||
|
regs->sd_status_mask = STATUS_MASK_ALL;
|
||||||
|
|
||||||
|
// SDIO IRQs.
|
||||||
|
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMIO_initPort(TmioPort *const port, const u8 portNum)
|
||||||
|
{
|
||||||
|
// Reset port state.
|
||||||
|
port->portNum = portNum;
|
||||||
|
port->sd_clk_ctrl = SD_CLK_DEFAULT;
|
||||||
|
port->sd_blocklen = 512;
|
||||||
|
port->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: What if we get rid of setPort() and only use one port per controller?
|
||||||
|
static void setPort(Tmio *const regs, const TmioPort *const port)
|
||||||
|
{
|
||||||
|
// TODO: Can we somehow prevent all these reg writes each time?
|
||||||
|
// Maybe some kind of dirty flag + active port check?
|
||||||
|
regs->sd_portsel = port->portNum % 2u;
|
||||||
|
regs->sd_clk_ctrl = port->sd_clk_ctrl;
|
||||||
|
const u16 blocklen = port->sd_blocklen;
|
||||||
|
regs->sd_blocklen = blocklen;
|
||||||
|
regs->sd_option = port->sd_option;
|
||||||
|
regs->sd_blocklen32 = blocklen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TMIO_cardDetected(void)
|
||||||
|
{
|
||||||
|
return getTmioRegs(0)->sd_status & STATUS_DETECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TMIO_cardWritable(void)
|
||||||
|
{
|
||||||
|
return getTmioRegs(0)->sd_status & STATUS_NO_WRPROT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TMIO_powerupSequence(TmioPort *const port)
|
||||||
|
{
|
||||||
|
port->sd_clk_ctrl = SD_CLK_EN | SD_CLK_DEFAULT;
|
||||||
|
setPort(getTmioRegs(port2Controller(port->portNum)), port);
|
||||||
|
INIT_DELAY_FUNC();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void getResponse(const Tmio *const regs, TmioPort *const port, const u16 cmd)
|
||||||
|
{
|
||||||
|
// We could check for response type none as well but it's not worth it.
|
||||||
|
if((cmd & CMD_RESP_MASK) != CMD_RESP_R2)
|
||||||
|
{
|
||||||
|
port->resp[0] = regs->sd_resp[0];
|
||||||
|
}
|
||||||
|
else // 136 bit R2 responses need special treatment...
|
||||||
|
{
|
||||||
|
u32 resp[4];
|
||||||
|
for(u32 i = 0; i < 4; i++) resp[i] = regs->sd_resp[i];
|
||||||
|
|
||||||
|
port->resp[0] = resp[3]<<8 | resp[2]>>24;
|
||||||
|
port->resp[1] = resp[2]<<8 | resp[1]>>24;
|
||||||
|
port->resp[2] = resp[1]<<8 | resp[0]>>24;
|
||||||
|
port->resp[3] = resp[0]<<8; // TODO: Add the missing CRC7 and bit 0?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: Using STATUS_DATA_END to detect transfer end doesn't work reliably
|
||||||
|
// because STATUS_DATA_END fires before we even read anything from FIFO
|
||||||
|
// on single block read transfer.
|
||||||
|
static void doCpuTransfer(Tmio *const regs, const u16 cmd, u8 *buf, const u32 *const statusPtr)
|
||||||
|
{
|
||||||
|
const u32 blockLen = regs->sd_blocklen;
|
||||||
|
u32 blockCount = regs->sd_blockcount;
|
||||||
|
vu32 *const fifo = getTmioFifo(regs);
|
||||||
|
if(cmd & CMD_DATA_R)
|
||||||
|
{
|
||||||
|
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
|
||||||
|
{
|
||||||
|
if(regs->sd_fifo32_cnt & FIFO32_FULL) // RX ready.
|
||||||
|
{
|
||||||
|
const u8 *const blockEnd = buf + blockLen;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if((uintptr_t)buf % 4 == 0)
|
||||||
|
{
|
||||||
|
*((u32*)buf) = *fifo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const u32 tmp = *fifo;
|
||||||
|
buf[0] = tmp;
|
||||||
|
buf[1] = tmp>>8;
|
||||||
|
buf[2] = tmp>>16;
|
||||||
|
buf[3] = tmp>>24;
|
||||||
|
}
|
||||||
|
buf += 4;
|
||||||
|
} while(buf < blockEnd);
|
||||||
|
|
||||||
|
blockCount--;
|
||||||
|
}
|
||||||
|
else rtos_waitEvent(&sSdEvent, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Write first block ahead of time?
|
||||||
|
// gbatek Command/Param/Response/Data at bottom of page.
|
||||||
|
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
|
||||||
|
{
|
||||||
|
if(!(regs->sd_fifo32_cnt & FIFO32_NOT_EMPTY)) // TX request.
|
||||||
|
{
|
||||||
|
const u8 *const blockEnd = buf + blockLen;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if((uintptr_t)buf % 4 == 0)
|
||||||
|
{
|
||||||
|
*fifo = *((u32*)buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
u32 tmp = buf[0];
|
||||||
|
tmp |= (u32)buf[1]<<8;
|
||||||
|
tmp |= (u32)buf[2]<<16;
|
||||||
|
tmp |= (u32)buf[3]<<24;
|
||||||
|
*fifo = tmp;
|
||||||
|
}
|
||||||
|
buf += 4;
|
||||||
|
} while(buf < blockEnd);
|
||||||
|
|
||||||
|
blockCount--;
|
||||||
|
}
|
||||||
|
else rtos_waitEvent(&sSdEvent, false, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg)
|
||||||
|
{
|
||||||
|
const u8 controller = port2Controller(port->portNum);
|
||||||
|
Tmio *const regs = getTmioRegs(controller);
|
||||||
|
|
||||||
|
// Clear status before sending another command.
|
||||||
|
u32 *const statusPtr = &g_status[controller];
|
||||||
|
SET_STATUS(statusPtr, 0);
|
||||||
|
|
||||||
|
setPort(regs, port);
|
||||||
|
const u16 blocks = port->blocks;
|
||||||
|
regs->sd_blockcount = blocks; // sd_blockcount32 doesn't need to be set.
|
||||||
|
regs->sd_stop = STOP_AUTO_STOP; // Auto STOP_TRANSMISSION (CMD12) on multi-block transfer.
|
||||||
|
regs->sd_arg = arg;
|
||||||
|
|
||||||
|
// We don't need FIFO IRQs when using DMA. buf = NULL means DMA.
|
||||||
|
u8 *buf = port->buf;
|
||||||
|
u16 f32Cnt = FIFO32_CLEAR | FIFO32_EN;
|
||||||
|
if(buf != NULL) f32Cnt |= (cmd & CMD_DATA_R ? FIFO32_FULL_IE : FIFO32_NOT_EMPTY_IE);
|
||||||
|
regs->sd_fifo32_cnt = f32Cnt;
|
||||||
|
regs->sd_cmd = (blocks > 1 ? CMD_MULTI_DATA | cmd : cmd); // Start.
|
||||||
|
|
||||||
|
// TODO: Benchmark if this order is ideal?
|
||||||
|
// Response end comes immediately after the
|
||||||
|
// command so we need to check before __wfi().
|
||||||
|
// On error response end still fires.
|
||||||
|
while((GET_STATUS(statusPtr) & STATUS_RESP_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
|
||||||
|
getResponse(regs, port, cmd);
|
||||||
|
|
||||||
|
if((cmd & CMD_DATA_EN) != 0)
|
||||||
|
{
|
||||||
|
// If we have to transfer data do so now.
|
||||||
|
if(buf != NULL) doCpuTransfer(regs, cmd, buf, statusPtr);
|
||||||
|
|
||||||
|
// Wait for data end if needed.
|
||||||
|
// On error data end still fires.
|
||||||
|
while((GET_STATUS(statusPtr) & STATUS_DATA_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// STATUS_CMD_BUSY is no longer set at this point.
|
||||||
|
|
||||||
|
return GET_STATUS(statusPtr) & STATUS_MASK_ERR;
|
||||||
|
}
|
||||||
39
arm7/source/picoLoaderBootstrap.cpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#include "common.h"
|
||||||
|
#include <libtwl/rtos/rtosIrq.h>
|
||||||
|
#include <libtwl/ipc/ipcFifoSystem.h>
|
||||||
|
#include <libtwl/sound/sound.h>
|
||||||
|
#include "ipcChannels.h"
|
||||||
|
#include "picoLoader7.h"
|
||||||
|
#include "picoLoaderBootstrap.h"
|
||||||
|
|
||||||
|
typedef void (*pico_loader_7_func_t)(void);
|
||||||
|
|
||||||
|
static volatile bool sShouldStart = false;
|
||||||
|
|
||||||
|
static void ipcMessageHandler(u32 channel, u32 data, void* arg)
|
||||||
|
{
|
||||||
|
if (data == 1)
|
||||||
|
{
|
||||||
|
sShouldStart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pload_init()
|
||||||
|
{
|
||||||
|
ipc_setChannelHandler(IPC_CHANNEL_LOADER, ipcMessageHandler, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pload_shouldStart()
|
||||||
|
{
|
||||||
|
return sShouldStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pload_start()
|
||||||
|
{
|
||||||
|
snd_setMasterEnable(false);
|
||||||
|
rtos_disableIrqs();
|
||||||
|
REG_IME = 0;
|
||||||
|
auto header7 = (pload_header7_t*)0x06000000;
|
||||||
|
header7->dldiDriver = (void*)0x037F8000;
|
||||||
|
((pico_loader_7_func_t)header7->entryPoint)();
|
||||||
|
}
|
||||||
5
arm7/source/picoLoaderBootstrap.h
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
void pload_init();
|
||||||
|
bool pload_shouldStart();
|
||||||
|
void pload_start();
|
||||||
BIN
arm9/data/NotoSansJP-Medium-10.nft2
Normal file
BIN
arm9/data/NotoSansJP-Medium-11.nft2
Normal file
BIN
arm9/data/NotoSansJP-Medium-7_5.nft2
Normal file
BIN
arm9/data/NotoSansJP-Regular-10.nft2
Normal file
7
arm9/gfx/backIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/backIcon.png
Normal file
|
After Width: | Height: | Size: 172 B |
7
arm9/gfx/bannerListIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/bannerListIcon.png
Normal file
|
After Width: | Height: | Size: 184 B |
5
arm9/gfx/bannerListItemBg0.grit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
BIN
arm9/gfx/bannerListItemBg0.png
Normal file
|
After Width: | Height: | Size: 256 B |
7
arm9/gfx/bannerListItemBg1.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/bannerListItemBg1.png
Normal file
|
After Width: | Height: | Size: 187 B |
7
arm9/gfx/bannerListItemBg2.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/bannerListItemBg2.png
Normal file
|
After Width: | Height: | Size: 248 B |
18
arm9/gfx/bottomSheetBg.grit
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# tile reduction by tiles, palette and hflip/vflip
|
||||||
|
-mRtpf
|
||||||
|
-ma2
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p
|
||||||
|
-pn16
|
||||||
|
|
||||||
|
# map layout standard bg format
|
||||||
|
-mLs
|
||||||
|
|
||||||
|
#-gzl
|
||||||
|
#-mzl
|
||||||
BIN
arm9/gfx/bottomSheetBg.png
Normal file
|
After Width: | Height: | Size: 438 B |
3
arm9/gfx/carouselMask.grit
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-gb
|
||||||
|
-gB8
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/carouselMask.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
5
arm9/gfx/chipFilled.grit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
BIN
arm9/gfx/chipFilled.png
Normal file
|
After Width: | Height: | Size: 197 B |
7
arm9/gfx/coverflowIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/coverflowIcon.png
Normal file
|
After Width: | Height: | Size: 186 B |
3
arm9/gfx/folderCover.grit
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-gb
|
||||||
|
-gB8
|
||||||
|
-p
|
||||||
BIN
arm9/gfx/folderCover.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
7
arm9/gfx/gamesIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/gamesIcon.png
Normal file
|
After Width: | Height: | Size: 227 B |
7
arm9/gfx/hGridIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/hGridIcon.png
Normal file
|
After Width: | Height: | Size: 173 B |
7
arm9/gfx/heartIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/heartIcon.png
Normal file
|
After Width: | Height: | Size: 226 B |
7
arm9/gfx/iconButtonSelector.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/iconButtonSelector.png
Normal file
|
After Width: | Height: | Size: 266 B |
3
arm9/gfx/iconButtonSelectorTexture.grit
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
-gb
|
||||||
|
-gB8
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/iconButtonSelectorTexture.png
Normal file
|
After Width: | Height: | Size: 922 B |
7
arm9/gfx/iconCell.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/iconCell.png
Normal file
|
After Width: | Height: | Size: 242 B |
5
arm9/gfx/iconCell2.grit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
BIN
arm9/gfx/iconCell2.png
Normal file
|
After Width: | Height: | Size: 368 B |
5
arm9/gfx/iconCell3.grit
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
BIN
arm9/gfx/iconCell3.png
Normal file
|
After Width: | Height: | Size: 325 B |
7
arm9/gfx/largeDSCardIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/largeDSCardIcon.png
Normal file
|
After Width: | Height: | Size: 203 B |
7
arm9/gfx/largeFileIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/largeFileIcon.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
7
arm9/gfx/largeFolderIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/largeFolderIcon.png
Normal file
|
After Width: | Height: | Size: 204 B |
7
arm9/gfx/listIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/listIcon.png
Normal file
|
After Width: | Height: | Size: 160 B |
13
arm9/gfx/mainBg.grit
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# tile reduction
|
||||||
|
-mR8
|
||||||
|
|
||||||
|
# graphics bit depth is 8 (256 color)
|
||||||
|
-gB8
|
||||||
|
|
||||||
|
-p!
|
||||||
|
|
||||||
|
# map layout standard bg format
|
||||||
|
-mLs
|
||||||
BIN
arm9/gfx/mainBg.png
Normal file
|
After Width: | Height: | Size: 631 B |
7
arm9/gfx/moviesIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/moviesIcon.png
Normal file
|
After Width: | Height: | Size: 203 B |
7
arm9/gfx/musicIcon.grit
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# tile format
|
||||||
|
-gt
|
||||||
|
|
||||||
|
# graphics bit depth is 4 (16 color)
|
||||||
|
-gB4
|
||||||
|
|
||||||
|
-p!
|
||||||
BIN
arm9/gfx/musicIcon.png
Normal file
|
After Width: | Height: | Size: 206 B |