You've already forked pico-loader
mirror of
https://github.com/LNH-team/pico-loader.git
synced 2026-01-09 16:28:35 -08:00
Add support for DATEL devices (GAMES n' MUSIC and Action Replay DS(i) Media Edition) (#64)
This commit is contained in:
1
.github/workflows/nightly.yml
vendored
1
.github/workflows/nightly.yml
vendored
@@ -19,6 +19,7 @@ jobs:
|
||||
"ACE3DS",
|
||||
"AK2",
|
||||
"AKRPG",
|
||||
"DATEL",
|
||||
"DSPICO",
|
||||
"DSTT",
|
||||
"EZP",
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -12,6 +12,7 @@ jobs:
|
||||
"ACE3DS",
|
||||
"AK2",
|
||||
"AKRPG",
|
||||
"DATEL",
|
||||
"DSPICO",
|
||||
"DSTT",
|
||||
"EZP",
|
||||
|
||||
@@ -25,6 +25,7 @@ Note that there can be some game compatibility differences between different pla
|
||||
| ACE3DS | Ace3DS+, Gateway 3DS (blue), r4isdhc.com.cn carts, r4isdhc.hk carts 2020+, various derivatives | ✅ |
|
||||
| AK2 | Acekard 2, 2.1, 2i, r4ids.cn, various derivatives | ❌ |
|
||||
| AKRPG | Acekard RPG SD card | ❌ |
|
||||
| DATEL | DATEL devices consisting of GAMES n' MUSIC and Action Replay DS(i) Media Edition | ❌ |
|
||||
| DSPICO | DSpico | ✅ |
|
||||
| DSTT | DSTT, SuperCard DSONE SDHC, r4isdhc.com carts 2014+, r4i-sdhc.com carts, various derivatives | ❌ |
|
||||
| EZP | EZ-Flash Parallel | ❌ |
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "patches/platform/r4idsn/R4iDSNLoaderPlatform.h"
|
||||
#include "patches/platform/supercard/SuperCardLoaderPlatform.h"
|
||||
#include "patches/platform/ezp/EZPLoaderPlatform.h"
|
||||
#include "patches/platform/datel/DatelLoaderPlatform.h"
|
||||
#include "LoaderPlatformFactory.h"
|
||||
|
||||
LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
|
||||
@@ -43,6 +44,8 @@ LoaderPlatform* LoaderPlatformFactory::CreateLoaderPlatform() const
|
||||
return new SuperCardLoaderPlatform();
|
||||
#elif defined(PICO_LOADER_TARGET_EZP)
|
||||
return new EZPLoaderPlatform();
|
||||
#elif defined(PICO_LOADER_TARGET_DATEL)
|
||||
return new DatelLoaderPlatform();
|
||||
#else
|
||||
#error "No loader platform defined"
|
||||
return nullptr;
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#define SD_CMD16_SET_BLOCKLEN 16
|
||||
#define SD_CMD55_APP_CMD 55
|
||||
|
||||
#define SD_SPI_CMD58_READ_OCR 58
|
||||
|
||||
#define SD_ACMD6_SET_BUS_WIDTH 6
|
||||
#define SD_ACMD41_SD_SEND_OP_COND 41
|
||||
|
||||
|
||||
154
arm9/source/patches/platform/datel/DatelLoaderPlatform.cpp
Normal file
154
arm9/source/patches/platform/datel/DatelLoaderPlatform.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "common.h"
|
||||
#include <libtwl/card/card.h>
|
||||
#include "../SdioDefinitions.h"
|
||||
#include "thumbInstructions.h"
|
||||
#include "DatelLoaderPlatform.h"
|
||||
|
||||
static constexpr int MAX_STARTUP_TRIES = 5000;
|
||||
static constexpr int SD_COMMAND_TIMEOUT = 0xFFF;
|
||||
static constexpr u32 DATEL_CTRL_BASE = (MCCNT1_RESET_OFF | MCCNT1_CMD_SCRAMBLE | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY2(0x3F));
|
||||
|
||||
static constexpr u8 DATEL_CMD_F2_SPI_ENABLE = 0xCC;
|
||||
static constexpr u8 DATEL_CMD_F2_SPI_DISABLE = 0xC8;
|
||||
|
||||
static inline u64 DATEL_CMD_F2(u32 param1, u8 param2)
|
||||
{
|
||||
return (0xF200000000000000ull | ((u64)param1 << 24) | ((u64)param2 << 16));
|
||||
}
|
||||
|
||||
static inline void enableSpi()
|
||||
{
|
||||
REG_MCCNT0 = MCCNT0_MODE_SPI | MCCNT0_SPI_HOLD_CS | MCCNT0_ENABLE;
|
||||
}
|
||||
|
||||
static u8 readWriteSpiByte(u8 data)
|
||||
{
|
||||
REG_MCD0 = data;
|
||||
while(REG_MCCNT0 & MCCNT0_SPI_BUSY);
|
||||
return REG_MCD0;
|
||||
}
|
||||
|
||||
static u8 readSpiByteTimeout()
|
||||
{
|
||||
auto timeout = SD_COMMAND_TIMEOUT;
|
||||
u8 res;
|
||||
do
|
||||
{
|
||||
res = readWriteSpiByte(0xFF);
|
||||
} while (res == 0xFF && --timeout > 0);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void sendNtrCommandF2(u32 param1, u8 param2)
|
||||
{
|
||||
card_romSetCmd(DATEL_CMD_F2(param1, param2));
|
||||
card_romStartXfer(DATEL_CTRL_BASE | MCCNT1_LEN_0, false);
|
||||
card_romWaitBusy();
|
||||
}
|
||||
|
||||
static void cycleSpi()
|
||||
{
|
||||
sendNtrCommandF2(0, DATEL_CMD_F2_SPI_DISABLE);
|
||||
enableSpi();
|
||||
readWriteSpiByte(0xFF);
|
||||
sendNtrCommandF2(0, DATEL_CMD_F2_SPI_ENABLE);
|
||||
enableSpi();
|
||||
}
|
||||
|
||||
// Sends SDIO command to DATEL device.
|
||||
static u8 spiSendSdioCommand(u8 cmdId, u32 arg, u8 * buffer, int messageLen)
|
||||
{
|
||||
cycleSpi();
|
||||
u8 cmd[6];
|
||||
|
||||
// Build a SPI SD command to be sent as-is.
|
||||
cmd[0] = 0x40 | (cmdId & 0x3f);
|
||||
cmd[1] = arg >> 24;
|
||||
cmd[2] = arg >> 16;
|
||||
cmd[3] = arg >> 8;
|
||||
cmd[4] = arg >> 0;
|
||||
// CRC in SPI mode is ignored for every command but CMD0 (hardcoded to 0x95)
|
||||
// and CMD8, hardcoded to 0x86 with the default 0x1AA argument.
|
||||
cmd[5] = (messageLen > 1) ? 0x86 : 0x95;
|
||||
|
||||
for (u8 byte : cmd)
|
||||
{
|
||||
readWriteSpiByte(byte);
|
||||
}
|
||||
|
||||
u8 timeout = readSpiByteTimeout();
|
||||
|
||||
for (int i = 0; i < (messageLen - 1); i++)
|
||||
{
|
||||
buffer[i] = readWriteSpiByte(0xFF);
|
||||
}
|
||||
|
||||
return timeout;
|
||||
}
|
||||
|
||||
static u8 spiSendSdioCommandR0(u8 cmd, u32 arg)
|
||||
{
|
||||
return spiSendSdioCommand(cmd, arg, nullptr, 1);
|
||||
}
|
||||
|
||||
static bool trySendAcmd41(u32 acmd41Arg)
|
||||
{
|
||||
for (int i = 0; i < MAX_STARTUP_TRIES; i++)
|
||||
{
|
||||
// Send ACMD41.
|
||||
spiSendSdioCommandR0(SD_CMD55_APP_CMD, 0);
|
||||
if (spiSendSdioCommandR0(SD_ACMD41_SD_SEND_OP_COND, acmd41Arg) == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DatelLoaderPlatform::InitializeSdCard()
|
||||
{
|
||||
for (int i = 0; i < 0x100; i++)
|
||||
{
|
||||
sendNtrCommandF2(0x7FFFFFFF | ((i & 1) << 31), 0x00);
|
||||
}
|
||||
|
||||
// Send CMD0.
|
||||
if (spiSendSdioCommandR0(SD_CMD0_GO_IDLE_STATE, 0) != 0x01)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 cmd8Answer = 0;
|
||||
|
||||
u32 acmd41Arg = 0;
|
||||
bool isV2 = false;
|
||||
|
||||
if (spiSendSdioCommand(SD_CMD8_SEND_IF_COND, SD_IF_COND_PATTERN, (u8*)&cmd8Answer, 5) == 0x1
|
||||
&& cmd8Answer == 0xAA010000)
|
||||
{
|
||||
isV2 = true;
|
||||
acmd41Arg |= (1 << 30); // Set HCS bit,Supports SDHC
|
||||
}
|
||||
|
||||
if (!trySendAcmd41(acmd41Arg))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isSdhc = false;
|
||||
if (isV2)
|
||||
{
|
||||
u32 cmd58Answer = 0;
|
||||
spiSendSdioCommand(SD_SPI_CMD58_READ_OCR, 0, (u8*)&cmd58Answer, 5);
|
||||
isSdhc = (cmd58Answer & 0x40) != 0;
|
||||
}
|
||||
spiSendSdioCommandR0(SD_CMD16_SET_BLOCKLEN, 0x200);
|
||||
|
||||
const u16 nonSdhcOpcode = THUMB_LSLS_IMM(THUMB_R0, THUMB_R0, 9);
|
||||
const u16 sdhcOpcode = THUMB_MOVS_REG(THUMB_R0, THUMB_R0);
|
||||
const u16 opcode = isSdhc ? sdhcOpcode : nonSdhcOpcode;
|
||||
datel_writeSectorSdhcLabel = opcode;
|
||||
datel_readSectorSdhcLabel = opcode;
|
||||
|
||||
return true;
|
||||
}
|
||||
49
arm9/source/patches/platform/datel/DatelLoaderPlatform.h
Normal file
49
arm9/source/patches/platform/datel/DatelLoaderPlatform.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include "../LoaderPlatform.h"
|
||||
#include "DatelSpiCommandsAsm.h"
|
||||
#include "DatelReadSectorsAsm.h"
|
||||
#include "DatelWriteSectorsAsm.h"
|
||||
|
||||
/// @brief Implementation of LoaderPlatform for the DATEL line of flashcarts
|
||||
class DatelLoaderPlatform : public LoaderPlatform
|
||||
{
|
||||
public:
|
||||
const SdReadPatchCode* CreateSdReadPatchCode(
|
||||
PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override
|
||||
{
|
||||
auto spi = patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelReadSpiBytePatchCode(patchHeap);
|
||||
});
|
||||
auto sendSdio = patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelSendSDIOCommandPatchCode(patchHeap, spi);
|
||||
});
|
||||
return patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelReadSdPatchCode(patchHeap, spi, sendSdio);
|
||||
});
|
||||
}
|
||||
|
||||
const SdWritePatchCode* CreateSdWritePatchCode(
|
||||
PatchCodeCollection& patchCodeCollection, PatchHeap& patchHeap) const override
|
||||
{
|
||||
auto spi = patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelReadSpiBytePatchCode(patchHeap);
|
||||
});
|
||||
auto sendSdio = patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelSendSDIOCommandPatchCode(patchHeap, spi);
|
||||
});
|
||||
return patchCodeCollection.GetOrAddSharedPatchCode([&]
|
||||
{
|
||||
return new DatelWriteSdPatchCode(patchHeap, spi, sendSdio);
|
||||
});
|
||||
}
|
||||
|
||||
LoaderPlatformType GetPlatformType() const override { return LoaderPlatformType::Slot1; }
|
||||
|
||||
bool InitializeSdCard() override;
|
||||
};
|
||||
33
arm9/source/patches/platform/datel/DatelReadSectorsAsm.h
Normal file
33
arm9/source/patches/platform/datel/DatelReadSectorsAsm.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "sections.h"
|
||||
#include "../SdReadPatchCode.h"
|
||||
DEFINE_SECTION_SYMBOLS(datel_read);
|
||||
|
||||
extern u16 datel_readSectorSdhcLabel;
|
||||
|
||||
extern u32 datel_SDReadMultipleSector_SpiSendSDIOCommandR0;
|
||||
|
||||
extern u32 datel_SDReadMultipleSector_ReadSpiByteTimeout;
|
||||
extern u32 datel_SDReadMultipleSector_ReadSpiByte;
|
||||
|
||||
extern "C" void datel_SDReadMultipleSector(u32 srcSector, void* dst, u32 sectorCount);
|
||||
|
||||
class DatelReadSdPatchCode : public SdReadPatchCode
|
||||
{
|
||||
public:
|
||||
DatelReadSdPatchCode(PatchHeap& patchHeap,
|
||||
const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode,
|
||||
const DatelSendSDIOCommandPatchCode* datelSendSDIOCommandPatchCode)
|
||||
: SdReadPatchCode(SECTION_START(datel_read), SECTION_SIZE(datel_read), patchHeap)
|
||||
{
|
||||
datel_SDReadMultipleSector_SpiSendSDIOCommandR0 = (u32)datelSendSDIOCommandPatchCode->GetSpiSendSDIOCommandR0Function();
|
||||
|
||||
datel_SDReadMultipleSector_ReadSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetReadSpiByteTimeoutFunction();
|
||||
datel_SDReadMultipleSector_ReadSpiByte = (u32)datelReadSpiBytePatchCode->GetReadSpiByteFunction();
|
||||
}
|
||||
|
||||
const SdReadFunc GetSdReadFunction() const override
|
||||
{
|
||||
return (const SdReadFunc)GetAddressAtTarget((void*)datel_SDReadMultipleSector);
|
||||
}
|
||||
};
|
||||
102
arm9/source/patches/platform/datel/DatelReadSectorsAsm.s
Normal file
102
arm9/source/patches/platform/datel/DatelReadSectorsAsm.s
Normal file
@@ -0,0 +1,102 @@
|
||||
#include "asminc.h"
|
||||
|
||||
.syntax unified
|
||||
.thumb
|
||||
|
||||
.section "datel_read", "ax"
|
||||
|
||||
.global datel_readSectorSdhcLabel
|
||||
|
||||
@ All the called functions leave every registers unchanged, except for r0 in case the function has a return value
|
||||
|
||||
@datel_SDReadMultipleSector(u32 sector, u8 * buffer, u32 num_sectors)
|
||||
BEGIN_ASM_FUNC datel_SDReadMultipleSector
|
||||
push {r3-r7, lr}
|
||||
movs r4, r1
|
||||
@ We get the number of bytes total to write, which we'll compare to against in the main loop
|
||||
lsls r3, r2, #9
|
||||
|
||||
@ Total written bytes
|
||||
movs r5, #0
|
||||
|
||||
datel_readSectorSdhcLabel:
|
||||
@ if not sdhc this needs to be shifted to the left by 9
|
||||
lsls r0, #9
|
||||
@mov r0, r0
|
||||
|
||||
movs r1, DATEL_SDIO_CMD18_READ_MULTIPLE_BLOCK
|
||||
|
||||
ldr r7, datel_SDReadMultipleSector_SpiSendSDIOCommandR0
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
bne CMD18_not_ok
|
||||
|
||||
read_next_sector:
|
||||
ldr r7, datel_SDReadMultipleSector_ReadSpiByteTimeout
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
|
||||
cmp r0, DATEL_SPI_START_DATA_TOKEN
|
||||
bne wrong_spi_start_token
|
||||
|
||||
@ preload datel_readSpiByte
|
||||
ldr r7, datel_SDReadMultipleSector_ReadSpiByte
|
||||
read_next_byte:
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
strb r0, [r4, r5]
|
||||
|
||||
adds r5, #1
|
||||
@ Shifting left by 0x17 will set the Zero flag if the number that was shifted is a multiple
|
||||
@ of 0x200 (indicating a full sector has been written)
|
||||
lsls r0, r5, #0x17
|
||||
bne read_next_byte
|
||||
|
||||
@ drop crc
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
|
||||
cmp r3, r5
|
||||
bne read_next_sector
|
||||
|
||||
movs r0, #0
|
||||
movs r1, DATEL_SDIO_CMD12_STOP_TRANSMISSION
|
||||
movs r2, #7
|
||||
|
||||
@ datel_spiSendSDIOCommand is 1 instruction after datel_spiSendSDIOCommandR0
|
||||
ldr r7, datel_SDReadMultipleSector_SpiSendSDIOCommandR0
|
||||
adds r7, #2
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
|
||||
ldr r4, =DATEL_SD_CMD_TIMEOUT_LEN
|
||||
ldr r7, datel_SDReadMultipleSector_ReadSpiByte
|
||||
1:
|
||||
bl datel_SDReadMultipleSector_Interwork
|
||||
bne read_timeout_expired
|
||||
subs r4, #1
|
||||
bne 1b
|
||||
|
||||
read_timeout_expired:
|
||||
@ movs r0, r4
|
||||
@ subs r3, r0, #1
|
||||
@ sbcs r0, r3
|
||||
|
||||
@ pico loader has no error result
|
||||
@ pop {r3-r7, pc}
|
||||
|
||||
CMD18_not_ok:
|
||||
wrong_spi_start_token:
|
||||
@ movs r0, #0
|
||||
pop {r3-r7, pc}
|
||||
|
||||
datel_SDReadMultipleSector_Interwork:
|
||||
bx r7
|
||||
.balign 4
|
||||
.pool
|
||||
|
||||
.global datel_SDReadMultipleSector_SpiSendSDIOCommandR0
|
||||
datel_SDReadMultipleSector_SpiSendSDIOCommandR0:
|
||||
.word 0
|
||||
.global datel_SDReadMultipleSector_ReadSpiByteTimeout
|
||||
datel_SDReadMultipleSector_ReadSpiByteTimeout:
|
||||
.word 0
|
||||
.global datel_SDReadMultipleSector_ReadSpiByte
|
||||
datel_SDReadMultipleSector_ReadSpiByte:
|
||||
.word 0
|
||||
66
arm9/source/patches/platform/datel/DatelSpiCommandsAsm.h
Normal file
66
arm9/source/patches/platform/datel/DatelSpiCommandsAsm.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
#include "sections.h"
|
||||
#include "../SdReadDmaPatchCode.h"
|
||||
|
||||
DEFINE_SECTION_SYMBOLS(datel_read_spi);
|
||||
DEFINE_SECTION_SYMBOLS(datel_spi_send);
|
||||
|
||||
extern "C" u8 datel_readSpiByte();
|
||||
extern "C" u8 datel_readWriteSpiByte(u8 value);
|
||||
extern "C" u8 datel_readSpiByteTimeout();
|
||||
extern "C" bool datel_waitSpiByteTimeout();
|
||||
|
||||
extern "C" u8 datel_spiSendSDIOCommandR0(u32 arg, u8 cmd);
|
||||
extern "C" u8 datel_spiSendSDIOCommand(u32 arg, u8 cmd, int extraBytes);
|
||||
|
||||
extern u32 datel_spiSendSDIOCommandR0_ReadWriteSpiByte;
|
||||
extern u32 datel_spiSendSDIOCommandR0_ReadSpiByteTimeout;
|
||||
|
||||
class DatelReadSpiBytePatchCode : public PatchCode
|
||||
{
|
||||
public:
|
||||
explicit DatelReadSpiBytePatchCode(PatchHeap& patchHeap)
|
||||
: PatchCode(SECTION_START(datel_read_spi), SECTION_SIZE(datel_read_spi), patchHeap) { }
|
||||
|
||||
const void* GetReadSpiByteFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_readSpiByte);
|
||||
}
|
||||
|
||||
const void* GetReadWriteSpiByteFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_readWriteSpiByte);
|
||||
}
|
||||
|
||||
const void* GetWaitSpiByteTimeoutFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_waitSpiByteTimeout);
|
||||
}
|
||||
|
||||
const void* GetReadSpiByteTimeoutFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_readSpiByteTimeout);
|
||||
}
|
||||
};
|
||||
|
||||
class DatelSendSDIOCommandPatchCode : public PatchCode
|
||||
{
|
||||
public:
|
||||
DatelSendSDIOCommandPatchCode(PatchHeap& patchHeap,
|
||||
const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode)
|
||||
: PatchCode(SECTION_START(datel_spi_send), SECTION_SIZE(datel_spi_send), patchHeap)
|
||||
{
|
||||
datel_spiSendSDIOCommandR0_ReadWriteSpiByte = (u32)datelReadSpiBytePatchCode->GetReadWriteSpiByteFunction();
|
||||
datel_spiSendSDIOCommandR0_ReadSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetReadSpiByteTimeoutFunction();
|
||||
}
|
||||
|
||||
const void* GetSpiSendSDIOCommandR0Function() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_spiSendSDIOCommandR0);
|
||||
}
|
||||
|
||||
const void* GetSpiSendSDIOCommandFunction() const
|
||||
{
|
||||
return GetAddressAtTarget((void*)datel_spiSendSDIOCommand);
|
||||
}
|
||||
};
|
||||
156
arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s
Normal file
156
arm9/source/patches/platform/datel/DatelSpiCommandsAsm.s
Normal file
@@ -0,0 +1,156 @@
|
||||
#include "asminc.h"
|
||||
|
||||
.syntax unified
|
||||
.thumb
|
||||
|
||||
.section "datel_read_spi", "ax"
|
||||
@ NOTE!!!: This function needs to set r0 last with mov or something similar so that it updates the zero flags
|
||||
@u8 datel_readSpiByte(void);
|
||||
BEGIN_ASM_FUNC datel_readSpiByte
|
||||
movs r0, 0xFF
|
||||
@u8 datel_readWriteSpiByte(u8);
|
||||
BEGIN_ASM_FUNC datel_readWriteSpiByte
|
||||
push {r1-r3, lr}
|
||||
ldr r3, =REG_MCCNT0
|
||||
strh r0, [r3, #2]
|
||||
1:
|
||||
ldrh r1, [r3]
|
||||
lsrs r1, r1, #8
|
||||
bcs 1b
|
||||
@uppper half always 0
|
||||
ldrh r0, [r3, #2]
|
||||
cmp r0, #0
|
||||
pop {r1-r3, pc}
|
||||
|
||||
@u8 datel_readSpiByteTimeout(void);
|
||||
BEGIN_ASM_FUNC datel_readSpiByteTimeout
|
||||
push {r1-r4, lr}
|
||||
@ use a timeout of 0x1000 instead of 0xFFF, easier to setup
|
||||
@ ldr r4, =DATEL_SD_CMD_TIMEOUT_LEN
|
||||
movs r4, #1
|
||||
lsls r4, #12
|
||||
1:
|
||||
bl datel_readSpiByte
|
||||
cmp r0, #0xFF
|
||||
bne 1f
|
||||
subs r4, r4, #1
|
||||
bne 1b
|
||||
1:
|
||||
pop {r1-r4, pc}
|
||||
|
||||
@bool datel_waitSpiByteTimeout();
|
||||
BEGIN_ASM_FUNC datel_waitSpiByteTimeout
|
||||
push {r1-r4, lr}
|
||||
@ use a timeout of 0x1000 instead of 0xFFFF, easier to setup
|
||||
@ ldr r2, =DATEL_SD_WRITE_TIMEOUT_LEN
|
||||
movs r2, #1
|
||||
lsls r2, #16
|
||||
1:
|
||||
bl datel_readSpiByte
|
||||
bne 1f
|
||||
subs r2, #1
|
||||
bne 1b
|
||||
|
||||
movs r0, #0
|
||||
pop {r1-r4, pc}
|
||||
|
||||
1:
|
||||
movs r0, #1
|
||||
pop {r1-r4, pc}
|
||||
|
||||
.section "datel_spi_send", "ax"
|
||||
@void datel_cycleSpi();
|
||||
datel_cycleSpi:
|
||||
push {r0-r4, lr}
|
||||
adr r0, datel_cycleSpi_data
|
||||
ldm r0!, {r1,r3,r4}
|
||||
strh r3, [r1] @ Enable Spi
|
||||
movs r0, DATEL_CMD_F2_SPI_ENABLE
|
||||
|
||||
@ datel_SendNtrCommandF2
|
||||
movs r2, #0xF2
|
||||
lsls r0, r0, #8
|
||||
str r2, [r1, #8]
|
||||
str r0, [r1, #12]
|
||||
|
||||
@ we shift the 0xF2 loaded above by 14 to get 0xXXXX8000 (MCCNT0_ENABLE)
|
||||
lsls r2, #14
|
||||
strh r2, [r1]
|
||||
@ REG_MCCNT0 + 4 = REG_MCCNT1
|
||||
str r4, [r1, #4]
|
||||
1:
|
||||
ldr r2, [r1, #4]
|
||||
cmp r2, #0
|
||||
blt 1b
|
||||
|
||||
strh r3, [r1] @ Enable Spi
|
||||
pop {r0-r4, pc}
|
||||
|
||||
@ NOTE!!!: This function needs to set r0 last with mov or something similar so that it updates the zero flags
|
||||
@u8 datel_spiSendSDIOCommandR0(u32 arg, u8 cmd);
|
||||
BEGIN_ASM_FUNC datel_spiSendSDIOCommandR0
|
||||
movs r2, 0
|
||||
@u8 datel_spiSendSDIOCommand(u32 arg, u8 cmdId, int extraBytes);
|
||||
BEGIN_ASM_FUNC datel_spiSendSDIOCommand
|
||||
push {r0-r7, lr}
|
||||
|
||||
bl datel_cycleSpi
|
||||
|
||||
adr r4, datel_spiSendSDIOCommandR0_ReadSpiByteTimeout
|
||||
@ r3 contains ReadSpiByteTimeout
|
||||
@ r7 contains ReadWriteSpiByte
|
||||
ldm r4!, {r3,r7}
|
||||
|
||||
@ we use the cmd and arg directly from the stack
|
||||
@ r0 is on top, r1 is right below, we read the command id as the last byte pushed of r1,
|
||||
@ so at sp -1, the command arguments are at sp+0,sp+1,sp+2,sp+3, the 6th byte is garbage data read
|
||||
@ from sp+4, we don't need it to be something meaningful
|
||||
mov r4, sp
|
||||
@ TODO: this could maybe be optimized by setting 4 in r5, and having the last extra byte be sent by the timeout function itself
|
||||
subs r4, #1
|
||||
|
||||
movs r5, #6
|
||||
|
||||
@ sets up lr so that the interwork function jumps back here
|
||||
bl 1f
|
||||
1:
|
||||
subs r5, r5, #1
|
||||
ldrb r0, [r4, r5]
|
||||
@ branch to datel_readWriteSpiByte
|
||||
bcs datel_spiSendSDIOCommandR0_Interwork
|
||||
|
||||
@ branch to datel_readSpiByteTimeout
|
||||
bl datel_spiSendSDIOCommandR0_InterworkR3
|
||||
|
||||
@ load datel_readSpiByte since it's 1 thumb instruction before datel_readWriteSpiByte
|
||||
subs r7, r7, #2
|
||||
|
||||
@ Save the return value
|
||||
movs r6, r0
|
||||
|
||||
@ sets up lr so that the interwork function jumps back here
|
||||
bl 1f
|
||||
1:
|
||||
subs r2, #1
|
||||
@ branch to datel_readSpiByte
|
||||
bcc datel_spiSendSDIOCommandR0_Interwork
|
||||
movs r0, r6
|
||||
pop {r1}
|
||||
pop {r1-r7, pc}
|
||||
|
||||
datel_spiSendSDIOCommandR0_Interwork:
|
||||
bx r7
|
||||
datel_spiSendSDIOCommandR0_InterworkR3:
|
||||
bx r3
|
||||
.balign 4
|
||||
.pool
|
||||
datel_cycleSpi_data:
|
||||
.word REG_MCCNT0
|
||||
.word 0x0000A040 @ MCCNT0_MODE_SPI | MCCNT0_SPI_HOLD_CS | MCCNT0_ENABLE
|
||||
.word 0xA07F6000 @ MCCNT1_RESET_OFF | MCCNT1_CMD_SCRAMBLE | MCCNT1_READ_DATA_DESCRAMBLE | MCCNT1_CLOCK_SCRAMBLER | MCCNT1_LATENCY2(0x3F)
|
||||
.global datel_spiSendSDIOCommandR0_ReadSpiByteTimeout
|
||||
datel_spiSendSDIOCommandR0_ReadSpiByteTimeout:
|
||||
.word 0
|
||||
.global datel_spiSendSDIOCommandR0_ReadWriteSpiByte
|
||||
datel_spiSendSDIOCommandR0_ReadWriteSpiByte:
|
||||
.word 0
|
||||
34
arm9/source/patches/platform/datel/DatelWriteSectorsAsm.h
Normal file
34
arm9/source/patches/platform/datel/DatelWriteSectorsAsm.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "sections.h"
|
||||
#include "../SdWritePatchCode.h"
|
||||
|
||||
DEFINE_SECTION_SYMBOLS(datel_write);
|
||||
|
||||
extern u16 datel_writeSectorSdhcLabel;
|
||||
|
||||
extern u32 datel_SDWriteMultipleSector_SpiSendSDIOCommand;
|
||||
|
||||
extern u32 datel_SDWriteMultipleSector_WaitSpiByteTimeout;
|
||||
extern u32 datel_SDWriteMultipleSector_ReadSpiByte;
|
||||
|
||||
extern "C" void datel_SDWriteMultipleSector(u32 srcSector, void* dst, u32 sectorCount);
|
||||
|
||||
class DatelWriteSdPatchCode : public SdWritePatchCode
|
||||
{
|
||||
public:
|
||||
DatelWriteSdPatchCode(PatchHeap& patchHeap,
|
||||
const DatelReadSpiBytePatchCode* datelReadSpiBytePatchCode,
|
||||
const DatelSendSDIOCommandPatchCode* datelSendSDIOCommandPatchCode)
|
||||
: SdWritePatchCode(SECTION_START(datel_write), SECTION_SIZE(datel_write), patchHeap)
|
||||
{
|
||||
datel_SDWriteMultipleSector_SpiSendSDIOCommand = (u32)datelSendSDIOCommandPatchCode->GetSpiSendSDIOCommandFunction();
|
||||
|
||||
datel_SDWriteMultipleSector_WaitSpiByteTimeout = (u32)datelReadSpiBytePatchCode->GetWaitSpiByteTimeoutFunction();
|
||||
datel_SDWriteMultipleSector_ReadSpiByte = (u32)datelReadSpiBytePatchCode->GetReadSpiByteFunction();
|
||||
}
|
||||
|
||||
const SdWriteFunc GetSdWriteFunction() const override
|
||||
{
|
||||
return (const SdWriteFunc)GetAddressAtTarget((void*)datel_SDWriteMultipleSector);
|
||||
}
|
||||
};
|
||||
117
arm9/source/patches/platform/datel/DatelWriteSectorsAsm.s
Normal file
117
arm9/source/patches/platform/datel/DatelWriteSectorsAsm.s
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "asminc.h"
|
||||
|
||||
.syntax unified
|
||||
.thumb
|
||||
|
||||
.section "datel_write", "ax"
|
||||
|
||||
.global datel_writeSectorSdhcLabel
|
||||
|
||||
@ All the called functions leave every registers unchanged, except for r0 in case the function has a return value
|
||||
|
||||
@datel_SDWriteMultipleSector(u32 sector, const u8 * buffer, u32 num_sectors)
|
||||
BEGIN_ASM_FUNC datel_SDWriteMultipleSector
|
||||
@ Among those regs, there's r2 being pushed, accessed afterwards from the stack
|
||||
push {r1-r7, lr}
|
||||
movs r4, r1
|
||||
@ We get the number of bytes total to write, which we'll compare to against in the main loop
|
||||
lsls r3, r2, #9
|
||||
|
||||
@ Total written bytes
|
||||
movs r5, #0
|
||||
|
||||
datel_writeSectorSdhcLabel:
|
||||
@ if not sdhc this needs to be shifted to the left by 9
|
||||
lsls r0, #9
|
||||
@mov r0, r0
|
||||
|
||||
@ this message needs 1 byte of extra clock before it starts waiting for the start token
|
||||
movs r1, DATEL_SDIO_CMD25_WRITE_MULTIPLE_BLOCK
|
||||
movs r2, #1
|
||||
ldr r7, datel_SDWriteMultipleSector_SpiSendSDIOCommand
|
||||
bl datel_SDWriteMultipleSector_Interwork
|
||||
bne CMD25_not_ok
|
||||
|
||||
@ r6 contains datel_SDWriteMultipleSector_WaitSpiByteTimeout
|
||||
@ r7 contains datel_SDWriteMultipleSector_ReadSpiByte
|
||||
adr r1, datel_SDWriteMultipleSector_WaitSpiByteTimeout
|
||||
ldm r1!, {r6,r7}
|
||||
@ r1 contains datel_readWriteSpiByte
|
||||
adds r1, r7, #2
|
||||
|
||||
@ We use the r2 set above to put 0x10000 to use later as write timeout
|
||||
@ it's 1 bigger than the timeout len but we save an instruction
|
||||
@ ldr r2, =DATEL_SD_WRITE_TIMEOUT_LEN
|
||||
lsls r2, r2, #16
|
||||
|
||||
write_next_sector:
|
||||
|
||||
@ Send start token
|
||||
movs r0, DATEL_SPI_MULTI_BLOCK_WRITE_TOKEN
|
||||
|
||||
bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte
|
||||
|
||||
write_next_byte:
|
||||
ldrb r0, [r4, r5]
|
||||
bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte
|
||||
|
||||
adds r5, #1
|
||||
@ Shifting left by 0x17 will set the Zero flag if the number that was shifted is a multiple
|
||||
@ of 0x200 (indicating a full sector has been written)
|
||||
lsls r0, r5, #0x17
|
||||
bne write_next_byte
|
||||
|
||||
@ write dummy crc
|
||||
bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte
|
||||
bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte
|
||||
|
||||
bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte
|
||||
|
||||
@ we check if the lower nibble is equal to DATEL_SD_WRITE_OK
|
||||
subs r0, DATEL_SD_WRITE_OK
|
||||
lsls r0,#28
|
||||
bne write_command_failed
|
||||
|
||||
@ Wait for card to write data
|
||||
bl datel_SDWriteMultipleSector_InterworkR6 @ call datel_waitSpiByteTimeout
|
||||
beq sector_write_timeout_expired
|
||||
|
||||
@ r3 holds the total number of bytes to write
|
||||
cmp r3, r5
|
||||
bne write_next_sector
|
||||
|
||||
@ send stop token
|
||||
movs r0, DATEL_SPI_END_MULTI_BLOCK_WRITE
|
||||
bl datel_SDWriteMultipleSector_InterworkR1 @ call datel_readWriteSpiByte
|
||||
|
||||
@ send 1 byte clock
|
||||
bl datel_SDWriteMultipleSector_Interwork @ call datel_readSpiByte
|
||||
|
||||
bl datel_SDWriteMultipleSector_InterworkR6 @ call datel_waitSpiByteTimeout
|
||||
|
||||
@ pop {r1-r7, pc}
|
||||
|
||||
CMD25_not_ok:
|
||||
write_command_failed:
|
||||
sector_write_timeout_expired:
|
||||
@ movs r0, #0
|
||||
pop {r1-r7, pc}
|
||||
|
||||
datel_SDWriteMultipleSector_Interwork:
|
||||
bx r7
|
||||
datel_SDWriteMultipleSector_InterworkR6:
|
||||
bx r6
|
||||
datel_SDWriteMultipleSector_InterworkR1:
|
||||
bx r1
|
||||
.balign 4
|
||||
.pool
|
||||
|
||||
.global datel_SDWriteMultipleSector_SpiSendSDIOCommand
|
||||
datel_SDWriteMultipleSector_SpiSendSDIOCommand:
|
||||
.word 0
|
||||
.global datel_SDWriteMultipleSector_WaitSpiByteTimeout
|
||||
datel_SDWriteMultipleSector_WaitSpiByteTimeout:
|
||||
.word 0
|
||||
.global datel_SDWriteMultipleSector_ReadSpiByte
|
||||
datel_SDWriteMultipleSector_ReadSpiByte:
|
||||
.word 0
|
||||
25
arm9/source/patches/platform/datel/asminc.h
Normal file
25
arm9/source/patches/platform/datel/asminc.h
Normal file
@@ -0,0 +1,25 @@
|
||||
.macro BEGIN_ASM_FUNC name
|
||||
.global \name
|
||||
.type \name, %function
|
||||
.align 1
|
||||
\name:
|
||||
.endm
|
||||
|
||||
.equ REG_MCCNT0, 0x040001A0
|
||||
.equ REG_MCD0, 0x040001A2
|
||||
.equ REG_MCCNT1, 0x040001A4
|
||||
.equ REG_MCCMD0, 0x040001A8
|
||||
|
||||
.equ DATEL_SDIO_CMD18_READ_MULTIPLE_BLOCK, 18 | 0x40
|
||||
.equ DATEL_SDIO_CMD12_STOP_TRANSMISSION, 12 | 0x40
|
||||
.equ DATEL_SDIO_CMD25_WRITE_MULTIPLE_BLOCK, 25 | 0x40
|
||||
.equ DATEL_SPI_MULTI_BLOCK_WRITE_TOKEN, 0xFC
|
||||
.equ DATEL_SPI_END_MULTI_BLOCK_WRITE, 0xFD
|
||||
.equ DATEL_SPI_START_DATA_TOKEN, 0xFE
|
||||
.equ DATEL_SD_CMD_TIMEOUT_LEN, 0xFFF
|
||||
|
||||
.equ DATEL_SD_WRITE_OK, 0x5
|
||||
.equ DATEL_SD_WRITE_TIMEOUT_LEN, 0xFFFF
|
||||
|
||||
.equ DATEL_CMD_F2_SPI_ENABLE, 0xCC
|
||||
.equ DATEL_CMD_F2_SPI_DISABLE, 0xC8
|
||||
Reference in New Issue
Block a user