From aeca0d43bca0ff5c287337394b009843b3dcbfcb Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sat, 4 May 2024 15:31:14 -0600 Subject: [PATCH 1/6] Add profi200 dsi-sdmmc driver https://github.com/profi200/dsi_sdmmc --- code/core/arm7/Makefile | 2 +- code/core/arm7/source/mmc/LICENSE.txt | 21 + code/core/arm7/source/mmc/README.md | 16 + code/core/arm7/source/mmc/mmc_spec.h | 235 ++++++++ code/core/arm7/source/mmc/sd_spec.h | 192 +++++++ code/core/arm7/source/mmc/sdmmc.c | 751 ++++++++++++++++++++++++++ code/core/arm7/source/mmc/sdmmc.h | 239 ++++++++ code/core/arm7/source/mmc/tmio.c | 277 ++++++++++ code/core/arm7/source/mmc/tmio.h | 396 ++++++++++++++ 9 files changed, 2128 insertions(+), 1 deletion(-) create mode 100644 code/core/arm7/source/mmc/LICENSE.txt create mode 100644 code/core/arm7/source/mmc/README.md create mode 100644 code/core/arm7/source/mmc/mmc_spec.h create mode 100644 code/core/arm7/source/mmc/sd_spec.h create mode 100644 code/core/arm7/source/mmc/sdmmc.c create mode 100644 code/core/arm7/source/mmc/sdmmc.h create mode 100644 code/core/arm7/source/mmc/tmio.c create mode 100644 code/core/arm7/source/mmc/tmio.h diff --git a/code/core/arm7/Makefile b/code/core/arm7/Makefile index e759735..2ba57c4 100644 --- a/code/core/arm7/Makefile +++ b/code/core/arm7/Makefile @@ -15,7 +15,7 @@ include $(DEVKITARM)/ds_rules # all directories are relative to this makefile #--------------------------------------------------------------------------------- BUILD := build -SOURCES := source source/IpcServices source/Sound +SOURCES := source source/IpcServices source/Sound source/mmc INCLUDES := source ../common build DATA := diff --git a/code/core/arm7/source/mmc/LICENSE.txt b/code/core/arm7/source/mmc/LICENSE.txt new file mode 100644 index 0000000..773bcc0 --- /dev/null +++ b/code/core/arm7/source/mmc/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 profi200 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/code/core/arm7/source/mmc/README.md b/code/core/arm7/source/mmc/README.md new file mode 100644 index 0000000..effbdc3 --- /dev/null +++ b/code/core/arm7/source/mmc/README.md @@ -0,0 +1,16 @@ +# DSi (TWL) SDMMC code + +## Usage +* Call `TMIO_init()` once **after** irqInit() (libnds). +* Call `SDMMC_init(const u8 devNum)`. + +If SDMMC_init() succeeded you can use all other SDMMC_...() functions. SDMMC_init() can be called again at any time to reinitialize the SD card/eMMC provided you call SDMMC_deinit() first. + +DMA is supported for SDMMC_readSectors() and SDMMC_writeSectors() by passing a NULL pointer for buf and setting up a NDMA channel prior to calling these functions. Note that the TMIO driver does not support unaligned access for DMA and buffers not a multiple of 4 bytes (for CPU and DMA transfers). It's recommended to add a bounce buffer or cache to make sure buffers are 4 bytes aligned. + +## License +This software is licensed under the MIT license. See LICENSE.txt for details. + +Exemptions: +* The [BlocksDS](https://github.com/blocksds) project and its members are exempt from MIT licensing. They are permitted to relicense the source code of this repository, at their discretion, to the [zlib license](https://zlib.net/zlib_license.html). In doing so they may alter, supplement or entirely remove the copyright notice for each file they choose to relicense. +* The [dsi-modchip](https://github.com/dsi-modchip) project and its members are exempt from MIT licensing. They are permitted to relicense the source code of this repository, at their discretion, to the [LGPL license version 3 or later](https://www.gnu.org/licenses/lgpl-3.0.html). In doing so they may alter, supplement or entirely remove the copyright notice for each file they choose to relicense. \ No newline at end of file diff --git a/code/core/arm7/source/mmc/mmc_spec.h b/code/core/arm7/source/mmc/mmc_spec.h new file mode 100644 index 0000000..b6adebd --- /dev/null +++ b/code/core/arm7/source/mmc/mmc_spec.h @@ -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. diff --git a/code/core/arm7/source/mmc/sd_spec.h b/code/core/arm7/source/mmc/sd_spec.h new file mode 100644 index 0000000..808895a --- /dev/null +++ b/code/core/arm7/source/mmc/sd_spec.h @@ -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)) diff --git a/code/core/arm7/source/mmc/sdmmc.c b/code/core/arm7/source/mmc/sdmmc.c new file mode 100644 index 0000000..f315353 --- /dev/null +++ b/code/core/arm7/source/mmc/sdmmc.c @@ -0,0 +1,751 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#include +#include +#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) swiDelay(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<>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 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 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 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 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 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 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 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 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 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 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 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 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 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; +} diff --git a/code/core/arm7/source/mmc/sdmmc.h b/code/core/arm7/source/mmc/sdmmc.h new file mode 100644 index 0000000..0da82d5 --- /dev/null +++ b/code/core/arm7/source/mmc/sdmmc.h @@ -0,0 +1,239 @@ +#pragma once + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include + + +// 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. + + + +/** + * @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 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 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 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 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 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 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 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 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 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 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 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 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 SDMMC_getLastR1error(const u8 devNum); + +// TODO: TRIM/erase support. diff --git a/code/core/arm7/source/mmc/tmio.c b/code/core/arm7/source/mmc/tmio.c new file mode 100644 index 0000000..fe91e1e --- /dev/null +++ b/code/core/arm7/source/mmc/tmio.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#include +#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() swiDelay(TMIO_CLK2DIV(400000u) * 74 / 4) + + +static u32 g_status[2] = {0}; + + + +__attribute__((always_inline)) static inline u8 port2Controller(const u8 portNum) +{ + return portNum / 2; +} + +static void tmio1Isr(void) // SD/eMMC. +{ + Tmio *const regs = getTmioRegs(0); + g_status[0] |= regs->sd_status; + regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY. + + // TODO: Some kind of event to notify the main loop for remove/insert. +} + +static void tmio2Isr(void) // 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) +{ + // Register ISR and enable IRQs. + irqSetAUX(IRQ_SDMMC, tmio1Isr); + irqSetAUX(BIT(10), tmio2Isr); // Controller 2. + irqEnableAUX(IRQ_SDMMC); + irqEnableAUX(BIT(10)); // Controller 2. + + // 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) +{ + // Unregister ISR and disable IRQs. + irqClearAUX(IRQ_SDMMC); + irqClearAUX(BIT(10)); // Controller 2. + + // 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 swiHalt(); + } + } + 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 swiHalt(); + } + } +} + +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) swiHalt(); + 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) swiHalt(); + } + + // STATUS_CMD_BUSY is no longer set at this point. + + return GET_STATUS(statusPtr) & STATUS_MASK_ERR; +} diff --git a/code/core/arm7/source/mmc/tmio.h b/code/core/arm7/source/mmc/tmio.h new file mode 100644 index 0000000..9f141ac --- /dev/null +++ b/code/core/arm7/source/mmc/tmio.h @@ -0,0 +1,396 @@ +#pragma once + +// SPDX-License-Identifier: MIT +// Copyright (c) 2023 profi200 + +#include +#include + + +// 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; +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; +} + + +// 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<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; +} From c225a190629ec58d32bea8cbbf4b33c629cf4ca8 Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sat, 4 May 2024 17:31:30 -0600 Subject: [PATCH 2/6] Change dsi-mmc irq calls to libtwl --- code/core/arm7/source/mmc/sdmmc.c | 2 +- code/core/arm7/source/mmc/sdmmc.h | 9 +++++--- code/core/arm7/source/mmc/tmio.c | 36 ++++++++++++++++--------------- code/core/arm7/source/mmc/tmio.h | 12 ++++++++++- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/code/core/arm7/source/mmc/sdmmc.c b/code/core/arm7/source/mmc/sdmmc.c index f315353..a763fb3 100644 --- a/code/core/arm7/source/mmc/sdmmc.c +++ b/code/core/arm7/source/mmc/sdmmc.c @@ -21,7 +21,7 @@ // 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) swiDelay(8378 * (ms)) +#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. diff --git a/code/core/arm7/source/mmc/sdmmc.h b/code/core/arm7/source/mmc/sdmmc.h index 0da82d5..a1b2b71 100644 --- a/code/core/arm7/source/mmc/sdmmc.h +++ b/code/core/arm7/source/mmc/sdmmc.h @@ -5,7 +5,6 @@ #include - // Possible error codes for most of the functions below. enum { @@ -85,8 +84,9 @@ typedef struct #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. * @@ -237,3 +237,6 @@ u32 SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd); u32 SDMMC_getLastR1error(const u8 devNum); // TODO: TRIM/erase support. +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/code/core/arm7/source/mmc/tmio.c b/code/core/arm7/source/mmc/tmio.c index fe91e1e..9001313 100644 --- a/code/core/arm7/source/mmc/tmio.c +++ b/code/core/arm7/source/mmc/tmio.c @@ -2,7 +2,6 @@ // Copyright (c) 2023 profi200 #include -#include #include "tmio.h" @@ -14,28 +13,29 @@ // 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() swiDelay(TMIO_CLK2DIV(400000u) * 74 / 4) +#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(void) // SD/eMMC. +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(void) // WiFi SDIO. +static void tmio2Isr(u32 irqMask) // WiFi SDIO. { Tmio *const regs = getTmioRegs(1); g_status[1] |= regs->sd_status; @@ -44,11 +44,12 @@ static void tmio2Isr(void) // WiFi SDIO. void TMIO_init(void) { + rtos_createEvent(&sSdEvent); // Register ISR and enable IRQs. - irqSetAUX(IRQ_SDMMC, tmio1Isr); - irqSetAUX(BIT(10), tmio2Isr); // Controller 2. - irqEnableAUX(IRQ_SDMMC); - irqEnableAUX(BIT(10)); // Controller 2. + 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++) @@ -85,9 +86,10 @@ void TMIO_init(void) void TMIO_deinit(void) { - // Unregister ISR and disable IRQs. - irqClearAUX(IRQ_SDMMC); - irqClearAUX(BIT(10)); // Controller 2. + 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++) @@ -196,7 +198,7 @@ static void doCpuTransfer(Tmio *const regs, const u16 cmd, u8 *buf, const u32 *c blockCount--; } - else swiHalt(); + else rtos_waitEvent(&sSdEvent, false, true); } } else @@ -227,7 +229,7 @@ static void doCpuTransfer(Tmio *const regs, const u16 cmd, u8 *buf, const u32 *c blockCount--; } - else swiHalt(); + else rtos_waitEvent(&sSdEvent, false, true); } } } @@ -258,7 +260,7 @@ u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg) // 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) swiHalt(); + while((GET_STATUS(statusPtr) & STATUS_RESP_END) == 0) rtos_waitEvent(&sSdEvent, false, true); getResponse(regs, port, cmd); if((cmd & CMD_DATA_EN) != 0) @@ -268,10 +270,10 @@ u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg) // Wait for data end if needed. // On error data end still fires. - while((GET_STATUS(statusPtr) & STATUS_DATA_END) == 0) swiHalt(); + 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; -} +} \ No newline at end of file diff --git a/code/core/arm7/source/mmc/tmio.h b/code/core/arm7/source/mmc/tmio.h index 9f141ac..44ccbe6 100644 --- a/code/core/arm7/source/mmc/tmio.h +++ b/code/core/arm7/source/mmc/tmio.h @@ -5,6 +5,9 @@ #include #include +#include +#include +#include // For simplicity we will name the accessible 2 controllers 1 and 2. @@ -295,7 +298,10 @@ typedef struct } TmioPort; - +#ifdef __cplusplus +extern "C" +{ +#endif /** * @brief Initializes the tmio driver. */ @@ -394,3 +400,7 @@ __attribute__((always_inline)) static inline void TMIO_setBuffer(TmioPort *const port->buf = buf; port->blocks = blocks; } + +#ifdef __cplusplus +} +#endif \ No newline at end of file From 2bdd9923c467627712970bbc90060f329393433a Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sat, 4 May 2024 17:53:07 -0600 Subject: [PATCH 3/6] Switch DSi SD handling to dsi-sdmmc driver --- code/core/arm7/source/IpcServices/FsIpcService.cpp | 9 ++++----- code/core/arm7/source/main.cpp | 3 +++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/code/core/arm7/source/IpcServices/FsIpcService.cpp b/code/core/arm7/source/IpcServices/FsIpcService.cpp index 58b2def..1d2fb9e 100644 --- a/code/core/arm7/source/IpcServices/FsIpcService.cpp +++ b/code/core/arm7/source/IpcServices/FsIpcService.cpp @@ -1,15 +1,14 @@ #include "common.h" +#include "../mmc/sdmmc.h" #include #include "dldi.h" #include "FsIpcService.h" -extern "C" int sdmmc_sd_startup(); - void FsIpcService::Start() { if (isDSiMode()) { - sdmmc_sd_startup(); + SDMMC_init(SDMMC_DEV_CARD); } ThreadIpcService::Start(); } @@ -68,12 +67,12 @@ void FsIpcService::DldiWriteSectors(const fs_ipc_cmd_t* cmd) const void FsIpcService::DsiSdReadSectors(const fs_ipc_cmd_t* cmd) const { - sdmmc_sdcard_readsectors(cmd->sector, cmd->count, cmd->buffer); + SDMMC_readSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count); SendResponseMessage(0); } void FsIpcService::DsiSdWriteSectors(const fs_ipc_cmd_t* cmd) const { - sdmmc_sdcard_writesectors(cmd->sector, cmd->count, cmd->buffer); + SDMMC_writeSectors(SDMMC_DEV_CARD, cmd->sector, cmd->buffer, cmd->count); SendResponseMessage(0); } diff --git a/code/core/arm7/source/main.cpp b/code/core/arm7/source/main.cpp index a3a8501..4c03b59 100644 --- a/code/core/arm7/source/main.cpp +++ b/code/core/arm7/source/main.cpp @@ -19,6 +19,7 @@ #include "Arm7State.h" #include "ExitMode.h" #include "FramerateAdjustment.h" +#include "mmc/tmio.h" static FsIpcService sFsIpcService; static GbaSoundIpcService sGbaSoundIpcService; @@ -107,6 +108,8 @@ static void initializeArm7() sio_setGpioSiIrq(false); sio_setGpioMode(RCNT0_L_MODE_GPIO); + TMIO_init(); + initializeIpcServices(); snd_setMasterVolume(127); From d0dea7fc862a76d6e4e1ce2137db036213943f30 Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sun, 5 May 2024 11:42:23 -0600 Subject: [PATCH 4/6] Init TMIO only in DSi mode --- code/core/arm7/source/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code/core/arm7/source/main.cpp b/code/core/arm7/source/main.cpp index 4c03b59..e62a358 100644 --- a/code/core/arm7/source/main.cpp +++ b/code/core/arm7/source/main.cpp @@ -108,8 +108,6 @@ static void initializeArm7() sio_setGpioSiIrq(false); sio_setGpioMode(RCNT0_L_MODE_GPIO); - TMIO_init(); - initializeIpcServices(); snd_setMasterVolume(127); @@ -119,6 +117,7 @@ static void initializeArm7() if (isDSiMode()) { + TMIO_init(); rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); rtos_enableIrq2Mask(RTOS_IRQ2_MCU); } From 3ebbbdf82490d5e43bbe742ee36c200db4b3c28c Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sun, 5 May 2024 11:43:59 -0600 Subject: [PATCH 5/6] Moved `extern "C"` blocks Moved around all C function declarations in tmio.h --- code/core/arm7/source/mmc/tmio.h | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/code/core/arm7/source/mmc/tmio.h b/code/core/arm7/source/mmc/tmio.h index 44ccbe6..66e47da 100644 --- a/code/core/arm7/source/mmc/tmio.h +++ b/code/core/arm7/source/mmc/tmio.h @@ -58,6 +58,12 @@ typedef struct 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) @@ -70,7 +76,11 @@ __attribute__((always_inline)) static inline vu32* getTmioFifo(Tmio *const regs) return ®s->sd_fifo32; } +#ifdef __cplusplus +} +#endif +// REG_SD_C // REG_SD_CMD // Auto response supported commands: // CMD0, CMD2, CMD3 (only SD?), CMD7 (only select?), CMD9, CMD10, CMD12, CMD13, From 9af037771ecdbf8195284bf6e90bf756f70a4a18 Mon Sep 17 00:00:00 2001 From: Dartz150 Date: Sun, 5 May 2024 13:55:43 -0600 Subject: [PATCH 6/6] Ensure that TMIO is initialized properly and only in DSi mode --- code/core/arm7/source/main.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/code/core/arm7/source/main.cpp b/code/core/arm7/source/main.cpp index e62a358..76efc5d 100644 --- a/code/core/arm7/source/main.cpp +++ b/code/core/arm7/source/main.cpp @@ -108,6 +108,11 @@ static void initializeArm7() sio_setGpioSiIrq(false); sio_setGpioMode(RCNT0_L_MODE_GPIO); + if (isDSiMode()) + { + TMIO_init(); + } + initializeIpcServices(); snd_setMasterVolume(127); @@ -117,7 +122,6 @@ static void initializeArm7() if (isDSiMode()) { - TMIO_init(); rtos_setIrq2Func(RTOS_IRQ2_MCU, mcuIrq); rtos_enableIrq2Mask(RTOS_IRQ2_MCU); }