Initial commit

This commit is contained in:
Gericom
2025-10-11 13:23:06 +02:00
commit cecac10408
9 changed files with 665 additions and 0 deletions

35
.github/workflows/nightly.yml vendored Normal file
View File

@@ -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

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
*.dldi
*.elf
build/

17
LICENSE.txt Normal file
View 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.

202
Makefile Normal file
View File

@@ -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)

22
README.md Normal file
View File

@@ -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)

72
source/card.c Normal file
View File

@@ -0,0 +1,72 @@
#include <nds/ndstypes.h>
#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());
}

124
source/card.h Normal file
View File

@@ -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*)&REG_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

61
source/dldi_header.s Normal file
View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Zlib
//
// Copyright (C) 2006-2016 Michael Chisholm (Chishm)
// Copyright (C) 2006-2016 Dave Murphy (WinterMute)
#include <nds/arm9/dldi_asm.h>
.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

129
source/iointerface.c Normal file
View File

@@ -0,0 +1,129 @@
#include <nds/ndstypes.h>
#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;
}