From cecac10408837f5ac60963555c5b5dc2885ef95b Mon Sep 17 00:00:00 2001 From: Gericom Date: Sat, 11 Oct 2025 13:23:06 +0200 Subject: [PATCH] Initial commit --- .github/workflows/nightly.yml | 35 ++++++ .gitignore | 3 + LICENSE.txt | 17 +++ Makefile | 202 ++++++++++++++++++++++++++++++++++ README.md | 22 ++++ source/card.c | 72 ++++++++++++ source/card.h | 124 +++++++++++++++++++++ source/dldi_header.s | 61 ++++++++++ source/iointerface.c | 129 ++++++++++++++++++++++ 9 files changed, 665 insertions(+) create mode 100644 .github/workflows/nightly.yml create mode 100644 .gitignore create mode 100644 LICENSE.txt create mode 100644 Makefile create mode 100644 README.md create mode 100644 source/card.c create mode 100644 source/card.h create mode 100644 source/dldi_header.s create mode 100644 source/iointerface.c diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 0000000..3217330 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,35 @@ +name: Build DSpico DLDI + +on: + push: + branches: ["develop"] + paths-ignore: + - 'README.md' + pull_request: + branches: ["develop"] + paths-ignore: + - 'README.md' + workflow_dispatch: + +jobs: + dspico_dldi: + runs-on: ubuntu-latest + container: skylyrac/blocksds:slim-v1.13.1 + name: Build DSpico DLDI + 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: DSpico.dldi + name: DSpico.dldi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccfb601 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.dldi +*.elf +build/ \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..59dc7d2 --- /dev/null +++ b/LICENSE.txt @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa1c082 --- /dev/null +++ b/Makefile @@ -0,0 +1,202 @@ +# 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 WONDERFUL_TOOLCHAIN ?= /opt/wonderful +ARM_NONE_EABI_PATH ?= $(WONDERFUL_TOOLCHAIN)/toolchain/gcc-arm-none-eabi/bin/ + +# User config +# =========== + +NAME := DSpico + +# Source code paths +# ----------------- + +SOURCEDIRS := source +INCLUDEDIRS := +BINDIRS := + +# Defines passed to all files +# --------------------------- + +DEFINES := + +# Libraries +# --------- + +ifeq ($(DLDI_ARM9),1) +LIBS := -lnds9 +else +LIBS := -lnds7 +endif +LIBDIRS := $(BLOCKSDS)/libs/libnds + +# Build artifacts +# ----------------- + +BUILDDIR := build/$(NAME) +ELF := build/$(NAME).elf +DUMP := build/$(NAME).dump +MAP := build/$(NAME).map +DLDI := $(NAME).dldi + +# Tools +# ----- + +PREFIX := $(ARM_NONE_EABI_PATH)arm-none-eabi- +CC := $(PREFIX)gcc +CXX := $(PREFIX)g++ +LD := $(PREFIX)gcc +OBJDUMP := $(PREFIX)objdump +OBJCOPY := $(PREFIX)objcopy +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 +# ------------------------- + +ifeq ($(DLDI_ARM9),1) + ARCH := -mthumb -mcpu=arm946e-s+nofp + SPECS := $(BLOCKSDS)/sys/crts/dldi_arm9.specs +else + ARCH := -mthumb -mcpu=arm7tdmi + SPECS := $(BLOCKSDS)/sys/crts/dldi_arm7.specs +endif + +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++17 $(WARNFLAGS) $(DEFINES) $(INCLUDEFLAGS) \ + $(ARCH) -O2 -ffunction-sections -fdata-sections \ + -fno-exceptions -fno-rtti \ + -specs=$(SPECS) + +LDFLAGS += $(ARCH) $(LIBDIRSFLAGS) -Wl,-Map,$(MAP) $(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: $(DLDI) + +$(ELF): $(OBJS) + @echo " LD $@" + $(V)$(LD) -o $@ $(OBJS) $(LDFLAGS) + +$(DLDI): $(ELF) + @echo " OBJCOPY $@" + @$(OBJCOPY) -O binary $< $@ + +$(DUMP): $(ELF) + @echo " OBJDUMP $@" + $(V)$(OBJDUMP) -h -C -S $< > $@ + +dump: $(DUMP) + +clean: + @echo " CLEAN" + $(V)$(RM) $(DLDI) $(BUILDDIR) + +# Rules +# ----- + +$(BUILDDIR)/%.s.o : %.s + @echo " AS $<" + @$(MKDIR) -p $(@D) + $(V)$(CC) $(ASFLAGS) -MMD -MP -c -o $@ $< + +$(BUILDDIR)/%.c.o : %.c + @echo " CC $<" + @$(MKDIR) -p $(@D) + $(V)$(CC) $(CFLAGS) -MMD -MP -c -o $@ $< + +$(BUILDDIR)/%.arm.c.o : %.arm.c + @echo " CC $<" + @$(MKDIR) -p $(@D) + $(V)$(CC) $(CFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $< + +$(BUILDDIR)/%.cpp.o : %.cpp + @echo " CXX $<" + @$(MKDIR) -p $(@D) + $(V)$(CXX) $(CXXFLAGS) -MMD -MP -c -o $@ $< + +$(BUILDDIR)/%.arm.cpp.o : %.arm.cpp + @echo " CXX $<" + @$(MKDIR) -p $(@D) + $(V)$(CXX) $(CXXFLAGS) -MMD -MP -marm -mlong-calls -c -o $@ $< + +$(BUILDDIR)/%.bin.o $(BUILDDIR)/%_bin.h : %.bin + @echo " BIN2C $<" + @$(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) diff --git a/README.md b/README.md new file mode 100644 index 0000000..58ea010 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# DSpico DLDI + +Source code for the DSpico DLDI driver. + +## 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` + +## License + +This project is licensed under the Zlib license. For details, see `LICENSE.txt`. + +## Contributors +- [@Gericom](https://github.com/Gericom) +- [@lifehackerhansol](https://github.com/lifehackerhansol) diff --git a/source/card.c b/source/card.c new file mode 100644 index 0000000..53e67a7 --- /dev/null +++ b/source/card.c @@ -0,0 +1,72 @@ +#include +#include "card.h" + +void card_romCpuRead(u32* dst, u32 words) +{ + u32* target = dst + words; + do + { + // Read data if available + if (card_romIsDataReady()) + { + u32 data = card_romGetData(); + if (dst < target) + *dst++ = data; + } + } while (card_romIsBusy()); +} + +void card_romCpuReadUnaligned(u8* dst, u32 words) +{ + u8* target = dst + (words << 2); + do + { + // Read data if available + if (card_romIsDataReady()) + { + u32 data = card_romGetData(); + if (dst < target) + { + *dst++ = data & 0xFF; + *dst++ = (data >> 8) & 0xFF; + *dst++ = (data >> 16) & 0xFF; + *dst++ = (data >> 24) & 0xFF; + } + } + } while (card_romIsBusy()); +} + +void card_romCpuWrite(const u32* src, u32 words) +{ + u32 data = 0; + const u32* target = src + words; + do + { + // Write data if ready + if (card_romIsDataReady()) + { + if (src < target) + data = *src++; + card_romSetData(data); + } + } while (card_romIsBusy()); +} + +void card_romCpuWriteUnaligned(const u8* src, u32 words) +{ + u32 data = 0; + const u8* target = src + (words << 2); + do + { + // Write data if ready + if (card_romIsDataReady()) + { + if (src < target) + { + data = src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24); + src += 4; + } + card_romSetData(data); + } + } while (card_romIsBusy()); +} diff --git a/source/card.h b/source/card.h new file mode 100644 index 0000000..260d21b --- /dev/null +++ b/source/card.h @@ -0,0 +1,124 @@ +#pragma once + +#define REG_MCCNT0 (*(vu16*)0x040001A0) +#define REG_MCD0 (*(vu16*)0x040001A2) +#define REG_MCCNT1 (*(vu32*)0x040001A4) +#define REG_MCCMD0 (*(vu32*)0x040001A8) +#define REG_MCCMD1 (*(vu32*)0x040001AC) +#define REG_MCSCR0 (*(vu32*)0x040001B0) +#define REG_MCSCR1 (*(vu32*)0x040001B4) +#define REG_MCSCR2 (*(vu32*)0x040001B8) +#define REG_MCD1 (*(vu32*)0x04100010) + +// REG_MCCNT0 + +#define MCCNT0_SPI_RATE_4_19_MHZ 0 +#define MCCNT0_SPI_RATE_2_09_MHZ 1 +#define MCCNT0_SPI_RATE_1_05_MHZ 2 +#define MCCNT0_SPI_RATE_524_KHZ 3 + +#define MCCNT0_SPI_HOLD_CS (1 << 6) +#define MCCNT0_SPI_BUSY (1 << 7) + +#define MCCNT0_MODE_MASK (1 << 13) +#define MCCNT0_MODE_ROM (0 << 13) +#define MCCNT0_MODE_SPI (1 << 13) + +#define MCCNT0_ROM_XFER_IRQ (1 << 14) +#define MCCNT0_ENABLE (1 << 15) + +// REG_MCCNT1 + +#define MCCNT1_LATENCY1_SHIFT 0 +#define MCCNT1_LATENCY1_MASK 0x1FFF +#define MCCNT1_LATENCY1(x) (x) + +#define MCCNT1_READ_DATA_DESCRAMBLE (1 << 13) +#define MCCNT1_CLOCK_SCRAMBLER (1 << 14) +#define MCCNT1_APPLY_SCRAMBLE_SEED (1 << 15) + +#define MCCNT1_LATENCY2_SHIFT 16 +#define MCCNT1_LATENCY2_MASK 0x3F0000 +#define MCCNT1_LATENCY2(x) (((x) << MCCNT1_LATENCY2_SHIFT) & MCCNT1_LATENCY2_MASK) + +#define MCCNT1_CMD_SCRAMBLE (1 << 22) + +#define MCCNT1_DATA_READY (1 << 23) + +#define MCCNT1_LEN_0 (0 << 24) +#define MCCNT1_LEN_512 (1 << 24) +#define MCCNT1_LEN_1024 (2 << 24) +#define MCCNT1_LEN_2048 (3 << 24) +#define MCCNT1_LEN_4096 (4 << 24) +#define MCCNT1_LEN_8192 (5 << 24) +#define MCCNT1_LEN_16384 (6 << 24) +#define MCCNT1_LEN_4 (7 << 24) + +#define MCCNT1_CLK_6_7_MHZ (0 << 27) +#define MCCNT1_CLK_4_2_MHZ (1 << 27) + +#define MCCNT1_LATENCY_CLK (1 << 28) + +#define MCCNT1_RESET_ON (0 << 29) +#define MCCNT1_RESET_OFF (1 << 29) + +#define MCCNT1_DIR_READ (0 << 30) +#define MCCNT1_DIR_WRITE (1 << 30) + +#define MCCNT1_ENABLE (1 << 31) + +#ifdef __cplusplus +extern "C" +{ +#endif + + static inline void card_romSetCmd(u64 cmd) + { + *(vu64*)®_MCCMD0 = __builtin_bswap64(cmd); + } + + static inline bool card_romIsDataReady(void) + { + return REG_MCCNT1 & MCCNT1_DATA_READY; + } + + static inline void card_romWaitDataReady(void) + { + while(!card_romIsDataReady()); + } + + static inline u32 card_romGetData(void) + { + return REG_MCD1; + } + + static inline void card_romSetData(u32 data) + { + REG_MCD1 = data; + } + + static inline bool card_romIsBusy(void) + { + return REG_MCCNT1 & MCCNT1_ENABLE; + } + + static inline void card_romWaitBusy(void) + { + while(card_romIsBusy()); + } + + static inline void card_romStartXfer(u32 settings, bool irq) + { + REG_MCCNT0 = (REG_MCCNT0 & ~MCCNT0_MODE_MASK) | MCCNT0_MODE_ROM | (irq ? MCCNT0_ROM_XFER_IRQ : 0) | MCCNT0_ENABLE; + REG_MCCNT1 = MCCNT1_ENABLE | settings; + } + + void card_romCpuRead(u32* dst, u32 len); + void card_romCpuReadUnaligned(u8* dst, u32 words); + + void card_romCpuWrite(const u32* src, u32 words); + void card_romCpuWriteUnaligned(const u8* src, u32 words); + +#ifdef __cplusplus +} +#endif diff --git a/source/dldi_header.s b/source/dldi_header.s new file mode 100644 index 0000000..9f5dd89 --- /dev/null +++ b/source/dldi_header.s @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: Zlib +// +// Copyright (C) 2006-2016 Michael Chisholm (Chishm) +// Copyright (C) 2006-2016 Dave Murphy (WinterMute) + +#include + + .syntax unified + .section ".crt0","ax" + .global _start + .align 4 + .arm + +@ Driver patch file standard header -- 16 bytes + + .word 0xBF8DA5ED @ DLDI identifier - magic number + .asciz " Chishm" @ DLDI identifier - magic string (8 bytes with null terminator) + .byte 0x01 @ DLDI identifier - DLDI version number + .byte __dldi_header_driver_size @ Log [base-2] of the size of this driver in bytes. + @ Calculated automatically in the link script. + .byte __dldi_header_fix_flags @ Sections to fix. + @ Calculated automatically in the link script. + .byte 0x00 @ Space allocated in the .nds file; leave empty. + +@ Text identifier - can be anything up to 47 chars + terminating null -- 48 bytes + + .align 4 + .asciz "DSpico DLDI" + +@ Offsets to important sections within the data -- 32 bytes + + .align 6 + .word __text_start @ data start + .word __data_end @ data end + .word __glue_start @ Interworking glue start -- Needs address fixing + .word __glue_end @ Interworking glue end + .word __got_start @ GOT start -- Needs address fixing + .word __got_end @ GOT end + .word __bss_start @ bss start -- Needs setting to zero + .word __bss_end @ bss end + +@ IO_INTERFACE data -- 32 bytes + + .ascii "PICO" @ ioType (Normally "DLDI") +#ifdef ARM9 + .word FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_SLOT_NDS +#else + .word FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_SLOT_NDS | FEATURE_ARM7_CAPABLE +#endif + .word dldi_startup @ Function pointers to standard device driver functions + .word dldi_isInserted + .word dldi_readSectors + .word dldi_writeSectors + .word dldi_clearStatus + .word dldi_shutdown + +_start: + + .align + .pool + .end diff --git a/source/iointerface.c b/source/iointerface.c new file mode 100644 index 0000000..c73f6c2 --- /dev/null +++ b/source/iointerface.c @@ -0,0 +1,129 @@ +#include +#include "card.h" + +#define DSPICO_CMD_REQUEST_SD_READ(sector) (0xE300000000000000ull | (sector)) +#define DSPICO_CMD_POLL_SD_READY 0xE400000000000000ull +#define DSPICO_CMD_GET_SD_DATA 0xE500000000000000ull +#define DSPICO_CMD_WRITE_SD_DATA(sector, isFirst, isLast)\ + (0xF6E10D9800000000ull | ((isFirst ? 1ULL : 0ULL) << 33) | ((isLast ? 1ULL : 0ULL) << 32) | (sector)) + +__attribute__((noinline)) static void requestSdRead(u32 sector) +{ + card_romSetCmd(DSPICO_CMD_REQUEST_SD_READ(sector)); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_0 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(0) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + card_romWaitBusy(); +} + +__attribute__((noinline)) static u32 pollSdDataReady(void) +{ + u32 result; + card_romSetCmd(DSPICO_CMD_POLL_SD_READY); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_4 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(4) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + card_romCpuRead(&result, 1); + return result; +} + +__attribute__((noinline)) static void getSdData(u8* dst) +{ + card_romSetCmd(DSPICO_CMD_GET_SD_DATA); + card_romStartXfer(MCCNT1_DIR_READ | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_512 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(4) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY1(0), false); + if ((u32)dst & 3) + { + card_romCpuReadUnaligned(dst, 128); + } + else + { + card_romCpuRead((u32*)dst, 128); + } +} + +static void writeSdData(u32 sector, const u8* src, bool isFirst, bool isLast) +{ + card_romSetCmd(DSPICO_CMD_WRITE_SD_DATA(sector, isFirst, isLast)); + card_romStartXfer(MCCNT1_DIR_WRITE | MCCNT1_RESET_OFF | MCCNT1_CLK_6_7_MHZ | MCCNT1_LEN_512 | MCCNT1_CMD_SCRAMBLE | + MCCNT1_LATENCY2(8) | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_LATENCY1(0), false); + if ((u32)src & 3) + { + card_romCpuWriteUnaligned(src, 128); + } + else + { + card_romCpuWrite((const u32*)src, 128); + } +} + +bool dldi_startup(void) +{ + return true; +} + +bool dldi_isInserted(void) +{ + return true; +} + +bool dldi_clearStatus(void) +{ + return true; +} + +bool dldi_readSectors(u32 sector, u32 numSectors, void* buffer) +{ + u8* ptr = (u8*)buffer; + + requestSdRead(sector); + + do + { + while (!pollSdDataReady()); + getSdData(ptr); + ptr += 512; + } while (--numSectors); + + // Important! This makes sure that SdCard has returned + // to State::Idle. Otherwise the next transfer may fail. + while (!pollSdDataReady()); + + return true; +} + +bool dldi_writeSectors(u32 sector, u32 numSectors, void* buffer) +{ + if (numSectors > 0) + { + const u8* ptr = (const u8*)buffer; + if (numSectors == 1) + { + writeSdData(sector, ptr, true, true); // send 0 = last + } + else + { + writeSdData(sector, ptr, true, false); // send 0 + sector++; + ptr += 512; + + for (u32 i = 1; i < numSectors - 1; i++) + { + writeSdData(sector, ptr, false, false); // send i + sector++; + ptr += 512; + while (!pollSdDataReady()); // wait i - 1 + } + + writeSdData(sector, ptr, false, true); // send last + while (!pollSdDataReady()); // wait last - 1 + } + + while (!pollSdDataReady()); // wait last + } + + return true; +} + +bool dldi_shutdown(void) +{ + return true; +}