diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc741f..5391bfc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,7 @@ add_executable(${PROJECT_NAME} pio_spi.c nuvoton_spi.c isd1200.c + sdio.c ) # Create map/bin/hex/uf2 files @@ -30,6 +31,8 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}) pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/spi.pio) + +pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/sdio.pio) # Link to pico_stdlib (gpio, time, etc. functions) target_link_libraries(${PROJECT_NAME} @@ -37,6 +40,7 @@ target_link_libraries(${PROJECT_NAME} tinyusb_device_unmarked hardware_pio hardware_spi + hardware_dma ) # Enable usb output, disable uart output diff --git a/crc-itu-t.h b/crc-itu-t.h new file mode 100644 index 0000000..6157fa2 --- /dev/null +++ b/crc-itu-t.h @@ -0,0 +1,34 @@ +const uint16_t crc_itu_t_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +}; diff --git a/crc7.h b/crc7.h new file mode 100644 index 0000000..100fc79 --- /dev/null +++ b/crc7.h @@ -0,0 +1,34 @@ +static const uint8_t crc7_table[256] = { + 0x00, 0x12, 0x24, 0x36, 0x48, 0x5a, 0x6c, 0x7e, + 0x90, 0x82, 0xb4, 0xa6, 0xd8, 0xca, 0xfc, 0xee, + 0x32, 0x20, 0x16, 0x04, 0x7a, 0x68, 0x5e, 0x4c, + 0xa2, 0xb0, 0x86, 0x94, 0xea, 0xf8, 0xce, 0xdc, + 0x64, 0x76, 0x40, 0x52, 0x2c, 0x3e, 0x08, 0x1a, + 0xf4, 0xe6, 0xd0, 0xc2, 0xbc, 0xae, 0x98, 0x8a, + 0x56, 0x44, 0x72, 0x60, 0x1e, 0x0c, 0x3a, 0x28, + 0xc6, 0xd4, 0xe2, 0xf0, 0x8e, 0x9c, 0xaa, 0xb8, + 0xc8, 0xda, 0xec, 0xfe, 0x80, 0x92, 0xa4, 0xb6, + 0x58, 0x4a, 0x7c, 0x6e, 0x10, 0x02, 0x34, 0x26, + 0xfa, 0xe8, 0xde, 0xcc, 0xb2, 0xa0, 0x96, 0x84, + 0x6a, 0x78, 0x4e, 0x5c, 0x22, 0x30, 0x06, 0x14, + 0xac, 0xbe, 0x88, 0x9a, 0xe4, 0xf6, 0xc0, 0xd2, + 0x3c, 0x2e, 0x18, 0x0a, 0x74, 0x66, 0x50, 0x42, + 0x9e, 0x8c, 0xba, 0xa8, 0xd6, 0xc4, 0xf2, 0xe0, + 0x0e, 0x1c, 0x2a, 0x38, 0x46, 0x54, 0x62, 0x70, + 0x82, 0x90, 0xa6, 0xb4, 0xca, 0xd8, 0xee, 0xfc, + 0x12, 0x00, 0x36, 0x24, 0x5a, 0x48, 0x7e, 0x6c, + 0xb0, 0xa2, 0x94, 0x86, 0xf8, 0xea, 0xdc, 0xce, + 0x20, 0x32, 0x04, 0x16, 0x68, 0x7a, 0x4c, 0x5e, + 0xe6, 0xf4, 0xc2, 0xd0, 0xae, 0xbc, 0x8a, 0x98, + 0x76, 0x64, 0x52, 0x40, 0x3e, 0x2c, 0x1a, 0x08, + 0xd4, 0xc6, 0xf0, 0xe2, 0x9c, 0x8e, 0xb8, 0xaa, + 0x44, 0x56, 0x60, 0x72, 0x0c, 0x1e, 0x28, 0x3a, + 0x4a, 0x58, 0x6e, 0x7c, 0x02, 0x10, 0x26, 0x34, + 0xda, 0xc8, 0xfe, 0xec, 0x92, 0x80, 0xb6, 0xa4, + 0x78, 0x6a, 0x5c, 0x4e, 0x30, 0x22, 0x14, 0x06, + 0xe8, 0xfa, 0xcc, 0xde, 0xa0, 0xb2, 0x84, 0x96, + 0x2e, 0x3c, 0x0a, 0x18, 0x66, 0x74, 0x42, 0x50, + 0xbe, 0xac, 0x9a, 0x88, 0xf6, 0xe4, 0xd2, 0xc0, + 0x1c, 0x0e, 0x38, 0x2a, 0x54, 0x46, 0x70, 0x62, + 0x8c, 0x9e, 0xa8, 0xba, 0xc4, 0xd6, 0xe0, 0xf2 +}; diff --git a/main.c b/main.c index dff395b..6dd1d45 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,9 @@ #include "tusb.h" #include "xbox.h" #include "isd1200.h" +#include "sdio.h" +#include "pins.h" +#include "mmc_defs.h" #define LED_PIN 25 @@ -92,6 +95,15 @@ void led_blink(void) #define WRITE_FLASH 0x03 #define READ_FLASH_STREAM 0x04 +#define EMMC_DETECT 0x50 +#define EMMC_INIT 0x51 +#define EMMC_GET_CID 0x52 +#define EMMC_GET_CSD 0x53 +#define EMMC_GET_EXT_CSD 0x54 +#define EMMC_READ 0x55 +#define EMMC_READ_STREAM 0x56 +#define EMMC_WRITE 0x57 + #define ISD1200_INIT 0xA0 #define ISD1200_DEINIT 0xA1 #define ISD1200_READ_ID 0xA2 @@ -112,6 +124,8 @@ struct cmd }; #pragma pack(pop) +bool emmc_detected = false; +bool stream_emmc = false; bool do_stream = false; uint32_t stream_offset = 0; uint32_t stream_end = 0; @@ -125,21 +139,40 @@ void stream() return; } - if (tud_cdc_write_available() < 4 + 0x210) + if (tud_cdc_write_available() < 4 + (stream_emmc ? 0x200 : 0x210)) return; - static uint8_t buffer[4 + 0x210]; - uint32_t ret = xbox_nand_read_block(stream_offset, &buffer[4], &buffer[4 + 0x200]); - *(uint32_t *)buffer = ret; - if (ret == 0) + if (!stream_emmc) { - tud_cdc_write(buffer, sizeof(buffer)); - ++stream_offset; + static uint8_t buffer[4 + 0x210]; + uint32_t ret = xbox_nand_read_block(stream_offset, &buffer[4], &buffer[4 + 0x200]); + *(uint32_t *)buffer = ret; + if (ret == 0) + { + tud_cdc_write(buffer, sizeof(buffer)); + ++stream_offset; + } + else + { + tud_cdc_write(&ret, 4); + do_stream = false; + } } else { - tud_cdc_write(&ret, 4); - do_stream = false; + static uint8_t buffer[4 + 0x200]; + int ret = sd_readblocks_sync(&buffer[4], stream_offset, 1); + *(uint32_t *)buffer = ret; + if (ret == 0) + { + tud_cdc_write(buffer, sizeof(buffer)); + ++stream_offset; + } + else + { + tud_cdc_write(&ret, 4); + do_stream = false; + } } } } @@ -172,7 +205,7 @@ void tud_cdc_rx_cb(uint8_t itf) if (cmd.cmd == GET_VERSION) { - uint32_t ver = 2; + uint32_t ver = 3; tud_cdc_write(&ver, 4); } else if (cmd.cmd == GET_FLASH_CONFIG) @@ -199,20 +232,21 @@ void tud_cdc_rx_cb(uint8_t itf) } else if (cmd.cmd == READ_FLASH_STREAM) { + stream_emmc = false; do_stream = true; stream_offset = 0; stream_end = cmd.lba; } if (cmd.cmd == ISD1200_INIT) { - uint8_t fc = isd1200_init() ? 0 : 1; - tud_cdc_write(&fc, 1); + uint8_t ret = isd1200_init() ? 0 : 1; + tud_cdc_write(&ret, 1); } if (cmd.cmd == ISD1200_DEINIT) { isd1200_deinit(); - uint8_t fc = 0; - tud_cdc_write(&fc, 1); + uint8_t ret = 0; + tud_cdc_write(&ret, 1); } else if (cmd.cmd == ISD1200_READ_ID) { @@ -228,8 +262,8 @@ void tud_cdc_rx_cb(uint8_t itf) if (cmd.cmd == ISD1200_ERASE_FLASH) { isd1200_chip_erase(); - uint8_t fc = 0; - tud_cdc_write(&fc, 1); + uint8_t ret = 0; + tud_cdc_write(&ret, 1); } else if (cmd.cmd == ISD1200_WRITE_FLASH) { @@ -244,25 +278,87 @@ void tud_cdc_rx_cb(uint8_t itf) else if (cmd.cmd == ISD1200_PLAY_VOICE) { isd1200_play_vp(cmd.lba); - uint8_t fc = 0; - tud_cdc_write(&fc, 1); + uint8_t ret = 0; + tud_cdc_write(&ret, 1); } else if (cmd.cmd == ISD1200_EXEC_MACRO) { isd1200_exe_vm(cmd.lba); - uint8_t fc = 0; - tud_cdc_write(&fc, 1); + uint8_t ret = 0; + tud_cdc_write(&ret, 1); } if (cmd.cmd == ISD1200_RESET) { isd1200_reset(); - uint8_t fc = 0; - tud_cdc_write(&fc, 1); + uint8_t ret = 0; + tud_cdc_write(&ret, 1); } else if (cmd.cmd == REBOOT_TO_BOOTLOADER) { reset_usb_boot(0, 0); } + else if (cmd.cmd == EMMC_DETECT) + { + if (!emmc_detected) + { + gpio_init(MMC_CLK_PIN); + gpio_set_dir(MMC_CLK_PIN, GPIO_IN); + emmc_detected = gpio_get(MMC_CLK_PIN); + } + tud_cdc_write(&emmc_detected, 1); + } + else if (cmd.cmd == EMMC_INIT) + { + // Put SMC into reset + gpio_init(SMC_RST_XDK_N); + gpio_set_dir(SMC_RST_XDK_N, GPIO_OUT); + gpio_put(SMC_RST_XDK_N, 0); + + uint32_t ret = sd_init(); + tud_cdc_write(&ret, 4); + } + else if (cmd.cmd == EMMC_GET_CID) + { + uint8_t cid_raw[16]; + sd_read_cid(cid_raw); + tud_cdc_write(cid_raw, sizeof(cid_raw)); + } + else if (cmd.cmd == EMMC_GET_CSD) + { + uint8_t csd_raw[16]; + sd_read_csd(csd_raw); + tud_cdc_write(csd_raw, sizeof(csd_raw)); + } + else if (cmd.cmd == EMMC_GET_EXT_CSD) + { + uint8_t ext_csd[512]; + sd_read_ext_csd(ext_csd); + tud_cdc_write(ext_csd, sizeof(ext_csd)); + } + else if (cmd.cmd == EMMC_READ) + { + uint8_t buffer[0x200]; + int ret = sd_readblocks_sync(buffer, cmd.lba, 1); + tud_cdc_write(&ret, 4); + if (ret == 0) + tud_cdc_write(buffer, sizeof(buffer)); + } + else if (cmd.cmd == EMMC_READ_STREAM) + { + stream_emmc = true; + do_stream = true; + stream_offset = 0; + stream_end = cmd.lba; + } + else if (cmd.cmd == EMMC_WRITE) + { + uint8_t buffer[0x200]; + uint32_t count = tud_cdc_read(&buffer, sizeof(buffer)); + if (count != sizeof(buffer)) + return; + uint32_t ret = sd_writeblocks_sync(buffer, cmd.lba, 1); + tud_cdc_write(&ret, 4); + } tud_cdc_write_flush(); } diff --git a/mmc_defs.h b/mmc_defs.h new file mode 100644 index 0000000..0d87e35 --- /dev/null +++ b/mmc_defs.h @@ -0,0 +1,432 @@ +/* + * Header for MultiMediaCard (MMC) + * + * Copyright 2002 Hewlett-Packard Company + * + * Use consistent with the GNU GPL is permitted, + * provided that this copyright notice is + * preserved in its entirety in all copies and derived works. + * + * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, + * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS + * FITNESS FOR ANY PARTICULAR PURPOSE. + * + * Many thanks to Alessandro Rubini and Jonathan Corbet! + * + * Based strongly on code by: + * + * Author: Yong-iL Joh + * + * Author: Andrew Christian + * 15 May 2002 + */ + +#ifndef LINUX_MMC_MMC_H +#define LINUX_MMC_MMC_H + +/* Standard MMC commands (4.1) type argument response */ +/* class 1 */ +#define MMC_GO_IDLE_STATE 0 /* bc */ +#define MMC_SEND_OP_COND 1 /* bcr [31:0] OCR R3 */ +#define MMC_ALL_SEND_CID 2 /* bcr R2 */ +#define MMC_SET_RELATIVE_ADDR 3 /* ac [31:16] RCA R1 */ +#define MMC_SET_DSR 4 /* bc [31:16] RCA */ +#define MMC_SLEEP_AWAKE 5 /* ac [31:16] RCA 15:flg R1b */ +#define MMC_SWITCH 6 /* ac [31:0] See below R1b */ +#define MMC_SELECT_CARD 7 /* ac [31:16] RCA R1 */ +#define MMC_SEND_EXT_CSD 8 /* adtc R1 */ +#define MMC_SEND_CSD 9 /* ac [31:16] RCA R2 */ +#define MMC_SEND_CID 10 /* ac [31:16] RCA R2 */ +#define MMC_READ_DAT_UNTIL_STOP 11 /* adtc [31:0] dadr R1 */ +#define MMC_STOP_TRANSMISSION 12 /* ac R1b */ +#define MMC_SEND_STATUS 13 /* ac [31:16] RCA R1 */ +#define MMC_BUS_TEST_R 14 /* adtc R1 */ +#define MMC_GO_INACTIVE_STATE 15 /* ac [31:16] RCA */ +#define MMC_BUS_TEST_W 19 /* adtc R1 */ +#define MMC_SPI_READ_OCR 58 /* spi spi_R3 */ +#define MMC_SPI_CRC_ON_OFF 59 /* spi [0:0] flag spi_R1 */ + +/* class 2 */ +#define MMC_SET_BLOCKLEN 16 /* ac [31:0] block len R1 */ +#define MMC_READ_SINGLE_BLOCK 17 /* adtc [31:0] data addr R1 */ +#define MMC_READ_MULTIPLE_BLOCK 18 /* adtc [31:0] data addr R1 */ +#define MMC_SEND_TUNING_BLOCK 19 /* adtc R1 */ +#define MMC_SEND_TUNING_BLOCK_HS200 21 /* adtc R1 */ + +/* class 3 */ +#define MMC_WRITE_DAT_UNTIL_STOP 20 /* adtc [31:0] data addr R1 */ + +/* class 4 */ +#define MMC_SET_BLOCK_COUNT 23 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_BLOCK 24 /* adtc [31:0] data addr R1 */ +#define MMC_WRITE_MULTIPLE_BLOCK 25 /* adtc R1 */ +#define MMC_PROGRAM_CID 26 /* adtc R1 */ +#define MMC_PROGRAM_CSD 27 /* adtc R1 */ + +/* class 6 */ +#define MMC_SET_WRITE_PROT 28 /* ac [31:0] data addr R1b */ +#define MMC_CLR_WRITE_PROT 29 /* ac [31:0] data addr R1b */ +#define MMC_SEND_WRITE_PROT 30 /* adtc [31:0] wpdata addr R1 */ + +/* class 5 */ +#define MMC_ERASE_GROUP_START 35 /* ac [31:0] data addr R1 */ +#define MMC_ERASE_GROUP_END 36 /* ac [31:0] data addr R1 */ +#define MMC_ERASE 38 /* ac R1b */ + +/* class 9 */ +#define MMC_FAST_IO 39 /* ac R4 */ +#define MMC_GO_IRQ_STATE 40 /* bcr R5 */ + +/* class 7 */ +#define MMC_LOCK_UNLOCK 42 /* adtc R1b */ + +/* class 8 */ +#define MMC_APP_CMD 55 /* ac [31:16] RCA R1 */ +#define MMC_GEN_CMD 56 /* adtc [0] RD/WR R1 */ + +/* class 11 */ +#define MMC_QUE_TASK_PARAMS 44 /* ac [20:16] task id R1 */ +#define MMC_QUE_TASK_ADDR 45 /* ac [31:0] data addr R1 */ +#define MMC_EXECUTE_READ_TASK 46 /* adtc [20:16] task id R1 */ +#define MMC_EXECUTE_WRITE_TASK 47 /* adtc [20:16] task id R1 */ +#define MMC_CMDQ_TASK_MGMT 48 /* ac [20:16] task id R1b */ + +/* +* MMC_SWITCH argument format: +* +* [31:26] Always 0 +* [25:24] Access Mode +* [23:16] Location of target Byte in EXT_CSD +* [15:08] Value Byte +* [07:03] Always 0 +* [02:00] Command Set +*/ + +/* +MMC status in R1, for native mode (SPI bits are different) +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 must poll +the card by sending status command in order to read these bits. +Clear condition +a : according to the card state +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 R1_OUT_OF_RANGE (1 << 31) /* er, c */ +#define R1_ADDRESS_ERROR (1 << 30) /* erx, c */ +#define R1_BLOCK_LEN_ERROR (1 << 29) /* er, c */ +#define R1_ERASE_SEQ_ERROR (1 << 28) /* er, c */ +#define R1_ERASE_PARAM (1 << 27) /* ex, c */ +#define R1_WP_VIOLATION (1 << 26) /* erx, c */ +#define R1_CARD_IS_LOCKED (1 << 25) /* sx, a */ +#define R1_LOCK_UNLOCK_FAILED (1 << 24) /* erx, c */ +#define R1_COM_CRC_ERROR (1 << 23) /* er, b */ +#define R1_ILLEGAL_COMMAND (1 << 22) /* er, b */ +#define R1_CARD_ECC_FAILED (1 << 21) /* ex, c */ +#define R1_CC_ERROR (1 << 20) /* erx, c */ +#define R1_ERROR (1 << 19) /* erx, c */ +#define R1_UNDERRUN (1 << 18) /* ex, c */ +#define R1_OVERRUN (1 << 17) /* ex, c */ +#define R1_CID_CSD_OVERWRITE (1 << 16) /* erx, c, CID/CSD overwrite */ +#define R1_WP_ERASE_SKIP (1 << 15) /* sx, c */ +#define R1_CARD_ECC_DISABLED (1 << 14) /* sx, a */ +#define R1_ERASE_RESET (1 << 13) /* sr, c */ +#define R1_STATUS(x) (x & 0xFFFFE000) +#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) /* sx, b (4 bits) */ +#define R1_READY_FOR_DATA (1 << 8) /* sx, a */ +#define R1_SWITCH_ERROR (1 << 7) /* sx, c */ +#define R1_EXCEPTION_EVENT (1 << 6) /* sr, a */ +#define R1_APP_CMD (1 << 5) /* sr, c */ + +#define R1_STATE_IDLE 0 +#define R1_STATE_READY 1 +#define R1_STATE_IDENT 2 +#define R1_STATE_STBY 3 +#define R1_STATE_TRAN 4 +#define R1_STATE_DATA 5 +#define R1_STATE_RCV 6 +#define R1_STATE_PRG 7 +#define R1_STATE_DIS 8 + +/* +* MMC/SD in SPI mode reports R1 status always, and R2 for SEND_STATUS +* R1 is the low order byte; R2 is the next highest byte, when present. +*/ +#define R1_SPI_IDLE (1 << 0) +#define R1_SPI_ERASE_RESET (1 << 1) +#define R1_SPI_ILLEGAL_COMMAND (1 << 2) +#define R1_SPI_COM_CRC (1 << 3) +#define R1_SPI_ERASE_SEQ (1 << 4) +#define R1_SPI_ADDRESS (1 << 5) +#define R1_SPI_PARAMETER (1 << 6) +/* R1 bit 7 is always zero */ +#define R2_SPI_CARD_LOCKED (1 << 8) +#define R2_SPI_WP_ERASE_SKIP (1 << 9) /* or lock/unlock fail */ +#define R2_SPI_LOCK_UNLOCK_FAIL R2_SPI_WP_ERASE_SKIP +#define R2_SPI_ERROR (1 << 10) +#define R2_SPI_CC_ERROR (1 << 11) +#define R2_SPI_CARD_ECC_ERROR (1 << 12) +#define R2_SPI_WP_VIOLATION (1 << 13) +#define R2_SPI_ERASE_PARAM (1 << 14) +#define R2_SPI_OUT_OF_RANGE (1 << 15) /* or CSD overwrite */ +#define R2_SPI_CSD_OVERWRITE R2_SPI_OUT_OF_RANGE + +/* +* OCR bits are mostly in host.h +*/ +#define MMC_CARD_BUSY 0x80000000 /* Card Power up status bit */ + +/* +* Card Command Classes (CCC) +*/ +#define CCC_BASIC (1<<0) /* (0) Basic protocol functions */ +/* (CMD0,1,2,3,4,7,9,10,12,13,15) */ +/* (and for SPI, CMD58,59) */ +#define CCC_STREAM_READ (1<<1) /* (1) Stream read commands */ +/* (CMD11) */ +#define CCC_BLOCK_READ (1<<2) /* (2) Block read commands */ +/* (CMD16,17,18) */ +#define CCC_STREAM_WRITE (1<<3) /* (3) Stream write commands */ +/* (CMD20) */ +#define CCC_BLOCK_WRITE (1<<4) /* (4) Block write commands */ +/* (CMD16,24,25,26,27) */ +#define CCC_ERASE (1<<5) /* (5) Ability to erase blocks */ +/* (CMD32,33,34,35,36,37,38,39) */ +#define CCC_WRITE_PROT (1<<6) /* (6) Able to write protect blocks */ +/* (CMD28,29,30) */ +#define CCC_LOCK_CARD (1<<7) /* (7) Able to lock down card */ +/* (CMD16,CMD42) */ +#define CCC_APP_SPEC (1<<8) /* (8) Application specific */ +/* (CMD55,56,57,ACMD*) */ +#define CCC_IO_MODE (1<<9) /* (9) I/O mode */ +/* (CMD5,39,40,52,53) */ +#define CCC_SWITCH (1<<10) /* (10) High speed switch */ +/* (CMD6,34,35,36,37,50) */ +/* (11) Reserved */ +/* (CMD?) */ + +/* +* CSD field definitions +*/ + +#define CSD_STRUCT_VER_1_0 0 /* Valid for system specification 1.0 - 1.2 */ +#define CSD_STRUCT_VER_1_1 1 /* Valid for system specification 1.4 - 2.2 */ +#define CSD_STRUCT_VER_1_2 2 /* Valid for system specification 3.1 - 3.2 - 3.31 - 4.0 - 4.1 */ +#define CSD_STRUCT_EXT_CSD 3 /* Version is coded in CSD_STRUCTURE in EXT_CSD */ + +#define CSD_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.2 */ +#define CSD_SPEC_VER_1 1 /* Implements system specification 1.4 */ +#define CSD_SPEC_VER_2 2 /* Implements system specification 2.0 - 2.2 */ +#define CSD_SPEC_VER_3 3 /* Implements system specification 3.1 - 3.2 - 3.31 */ +#define CSD_SPEC_VER_4 4 /* Implements system specification 4.0 - 4.1 */ + +/* +* EXT_CSD fields +*/ + +#define EXT_CSD_CMDQ_MODE_EN 15 /* R/W */ +#define EXT_CSD_FLUSH_CACHE 32 /* W */ +#define EXT_CSD_CACHE_CTRL 33 /* R/W */ +#define EXT_CSD_POWER_OFF_NOTIFICATION 34 /* R/W */ +#define EXT_CSD_PACKED_FAILURE_INDEX 35 /* RO */ +#define EXT_CSD_PACKED_CMD_STATUS 36 /* RO */ +#define EXT_CSD_EXP_EVENTS_STATUS 54 /* RO, 2 bytes */ +#define EXT_CSD_EXP_EVENTS_CTRL 56 /* R/W, 2 bytes */ +#define EXT_CSD_DATA_SECTOR_SIZE 61 /* R */ +#define EXT_CSD_GP_SIZE_MULT 143 /* R/W */ +#define EXT_CSD_PARTITION_SETTING_COMPLETED 155 /* R/W */ +#define EXT_CSD_PARTITION_ATTRIBUTE 156 /* R/W */ +#define EXT_CSD_PARTITION_SUPPORT 160 /* RO */ +#define EXT_CSD_HPI_MGMT 161 /* R/W */ +#define EXT_CSD_RST_N_FUNCTION 162 /* R/W */ +#define EXT_CSD_BKOPS_EN 163 /* R/W */ +#define EXT_CSD_BKOPS_START 164 /* W */ +#define EXT_CSD_SANITIZE_START 165 /* W */ +#define EXT_CSD_WR_REL_PARAM 166 /* RO */ +#define EXT_CSD_RPMB_MULT 168 /* RO */ +#define EXT_CSD_FW_CONFIG 169 /* R/W */ +#define EXT_CSD_BOOT_WP 173 /* R/W */ +#define EXT_CSD_ERASE_GROUP_DEF 175 /* R/W */ +#define EXT_CSD_PART_CONFIG 179 /* R/W */ +#define EXT_CSD_ERASED_MEM_CONT 181 /* RO */ +#define EXT_CSD_BUS_WIDTH 183 /* R/W */ +#define EXT_CSD_STROBE_SUPPORT 184 /* RO */ +#define EXT_CSD_HS_TIMING 185 /* R/W */ +#define EXT_CSD_POWER_CLASS 187 /* R/W */ +#define EXT_CSD_REV 192 /* RO */ +#define EXT_CSD_STRUCTURE 194 /* RO */ +#define EXT_CSD_CARD_TYPE 196 /* RO */ +#define EXT_CSD_DRIVER_STRENGTH 197 /* RO */ +#define EXT_CSD_OUT_OF_INTERRUPT_TIME 198 /* RO */ +#define EXT_CSD_PART_SWITCH_TIME 199 /* RO */ +#define EXT_CSD_PWR_CL_52_195 200 /* RO */ +#define EXT_CSD_PWR_CL_26_195 201 /* RO */ +#define EXT_CSD_PWR_CL_52_360 202 /* RO */ +#define EXT_CSD_PWR_CL_26_360 203 /* RO */ +#define EXT_CSD_SEC_CNT 212 /* RO, 4 bytes */ +#define EXT_CSD_S_A_TIMEOUT 217 /* RO */ +#define EXT_CSD_REL_WR_SEC_C 222 /* RO */ +#define EXT_CSD_HC_WP_GRP_SIZE 221 /* RO */ +#define EXT_CSD_ERASE_TIMEOUT_MULT 223 /* RO */ +#define EXT_CSD_HC_ERASE_GRP_SIZE 224 /* RO */ +#define EXT_CSD_BOOT_MULT 226 /* RO */ +#define EXT_CSD_SEC_TRIM_MULT 229 /* RO */ +#define EXT_CSD_SEC_ERASE_MULT 230 /* RO */ +#define EXT_CSD_SEC_FEATURE_SUPPORT 231 /* RO */ +#define EXT_CSD_TRIM_MULT 232 /* RO */ +#define EXT_CSD_PWR_CL_200_195 236 /* RO */ +#define EXT_CSD_PWR_CL_200_360 237 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_195 238 /* RO */ +#define EXT_CSD_PWR_CL_DDR_52_360 239 /* RO */ +#define EXT_CSD_BKOPS_STATUS 246 /* RO */ +#define EXT_CSD_POWER_OFF_LONG_TIME 247 /* RO */ +#define EXT_CSD_GENERIC_CMD6_TIME 248 /* RO */ +#define EXT_CSD_CACHE_SIZE 249 /* RO, 4 bytes */ +#define EXT_CSD_PWR_CL_DDR_200_360 253 /* RO */ +#define EXT_CSD_FIRMWARE_VERSION 254 /* RO, 8 bytes */ +#define EXT_CSD_DEVICE_VERSION 262 /* RO, 2 bytes */ +#define EXT_CSD_PRE_EOL_INFO 267 /* RO */ +#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_A 268 /* RO */ +#define EXT_CSD_DEVICE_LIFE_TIME_EST_TYP_B 269 /* RO */ +#define EXT_CSD_CMDQ_DEPTH 307 /* RO */ +#define EXT_CSD_CMDQ_SUPPORT 308 /* RO */ +#define EXT_CSD_SUPPORTED_MODE 493 /* RO */ +#define EXT_CSD_TAG_UNIT_SIZE 498 /* RO */ +#define EXT_CSD_DATA_TAG_SUPPORT 499 /* RO */ +#define EXT_CSD_MAX_PACKED_WRITES 500 /* RO */ +#define EXT_CSD_MAX_PACKED_READS 501 /* RO */ +#define EXT_CSD_BKOPS_SUPPORT 502 /* RO */ +#define EXT_CSD_HPI_FEATURES 503 /* RO */ + +/* +* EXT_CSD field definitions +*/ + +#define EXT_CSD_WR_REL_PARAM_EN (1<<2) + +#define EXT_CSD_BOOT_WP_B_PWR_WP_DIS (0x40) +#define EXT_CSD_BOOT_WP_B_PERM_WP_DIS (0x10) +#define EXT_CSD_BOOT_WP_B_PERM_WP_EN (0x04) +#define EXT_CSD_BOOT_WP_B_PWR_WP_EN (0x01) + +#define EXT_CSD_PART_CONFIG_ACC_MASK (0x7) +#define EXT_CSD_PART_CONFIG_ACC_BOOT0 (0x1) +#define EXT_CSD_PART_CONFIG_ACC_RPMB (0x3) +#define EXT_CSD_PART_CONFIG_ACC_GP0 (0x4) + +#define EXT_CSD_PART_SETTING_COMPLETED (0x1) +#define EXT_CSD_PART_SUPPORT_PART_EN (0x1) + +#define EXT_CSD_CMD_SET_NORMAL (1<<0) +#define EXT_CSD_CMD_SET_SECURE (1<<1) +#define EXT_CSD_CMD_SET_CPSECURE (1<<2) + +#define EXT_CSD_CARD_TYPE_HS_26 (1<<0) /* Card can run at 26MHz */ +#define EXT_CSD_CARD_TYPE_HS_52 (1<<1) /* Card can run at 52MHz */ +#define EXT_CSD_CARD_TYPE_HS (EXT_CSD_CARD_TYPE_HS_26 | \ + EXT_CSD_CARD_TYPE_HS_52) +#define EXT_CSD_CARD_TYPE_DDR_1_8V (1<<2) /* Card can run at 52MHz */ +/* DDR mode @1.8V or 3V I/O */ +#define EXT_CSD_CARD_TYPE_DDR_1_2V (1<<3) /* Card can run at 52MHz */ +/* DDR mode @1.2V I/O */ +#define EXT_CSD_CARD_TYPE_DDR_52 (EXT_CSD_CARD_TYPE_DDR_1_8V \ + | EXT_CSD_CARD_TYPE_DDR_1_2V) +#define EXT_CSD_CARD_TYPE_HS200_1_8V (1<<4) /* Card can run at 200MHz */ +#define EXT_CSD_CARD_TYPE_HS200_1_2V (1<<5) /* Card can run at 200MHz */ +/* SDR mode @1.2V I/O */ +#define EXT_CSD_CARD_TYPE_HS200 (EXT_CSD_CARD_TYPE_HS200_1_8V | \ + EXT_CSD_CARD_TYPE_HS200_1_2V) +#define EXT_CSD_CARD_TYPE_HS400_1_8V (1<<6) /* Card can run at 200MHz DDR, 1.8V */ +#define EXT_CSD_CARD_TYPE_HS400_1_2V (1<<7) /* Card can run at 200MHz DDR, 1.2V */ +#define EXT_CSD_CARD_TYPE_HS400 (EXT_CSD_CARD_TYPE_HS400_1_8V | \ + EXT_CSD_CARD_TYPE_HS400_1_2V) +#define EXT_CSD_CARD_TYPE_HS400ES (1<<8) /* Card can run at HS400ES */ + +#define EXT_CSD_BUS_WIDTH_1 0 /* Card is in 1 bit mode */ +#define EXT_CSD_BUS_WIDTH_4 1 /* Card is in 4 bit mode */ +#define EXT_CSD_BUS_WIDTH_8 2 /* Card is in 8 bit mode */ +#define EXT_CSD_DDR_BUS_WIDTH_4 5 /* Card is in 4 bit DDR mode */ +#define EXT_CSD_DDR_BUS_WIDTH_8 6 /* Card is in 8 bit DDR mode */ +#define EXT_CSD_BUS_WIDTH_STROBE (1<<7) /* Enhanced strobe mode */ + +#define EXT_CSD_TIMING_BC 0 /* Backwards compatility */ +#define EXT_CSD_TIMING_HS 1 /* High speed */ +#define EXT_CSD_TIMING_HS200 2 /* HS200 */ +#define EXT_CSD_TIMING_HS400 3 /* HS400 */ +#define EXT_CSD_DRV_STR_SHIFT 4 /* Driver Strength shift */ + +#define EXT_CSD_SEC_ER_EN (1<<0) +#define EXT_CSD_SEC_BD_BLK_EN (1<<2) +#define EXT_CSD_SEC_GB_CL_EN (1<<4) +#define EXT_CSD_SEC_SANITIZE (1<<6) /* v4.5 only */ + +#define EXT_CSD_RST_N_EN_MASK 0x3 +#define EXT_CSD_RST_N_ENABLED 1 /* RST_n is enabled on card */ + +#define EXT_CSD_NO_POWER_NOTIFICATION 0 +#define EXT_CSD_POWER_ON 1 +#define EXT_CSD_POWER_OFF_SHORT 2 +#define EXT_CSD_POWER_OFF_LONG 3 + +#define EXT_CSD_PWR_CL_8BIT_MASK 0xF0 /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_4BIT_MASK 0x0F /* 8 bit PWR CLS */ +#define EXT_CSD_PWR_CL_8BIT_SHIFT 4 +#define EXT_CSD_PWR_CL_4BIT_SHIFT 0 + +#define EXT_CSD_PACKED_EVENT_EN (1<<3) + +/* +* EXCEPTION_EVENT_STATUS field +*/ +#define EXT_CSD_URGENT_BKOPS (1<<0) +#define EXT_CSD_DYNCAP_NEEDED (1<<1) +#define EXT_CSD_SYSPOOL_EXHAUSTED (1<<2) +#define EXT_CSD_PACKED_FAILURE (1<<3) + +#define EXT_CSD_PACKED_GENERIC_ERROR (1<<0) +#define EXT_CSD_PACKED_INDEXED_ERROR (1<<1) + +/* +* BKOPS status level +*/ +#define EXT_CSD_BKOPS_LEVEL_2 0x2 + +/* +* BKOPS modes +*/ +#define EXT_CSD_MANUAL_BKOPS_MASK 0x01 +#define EXT_CSD_AUTO_BKOPS_MASK 0x02 + +/* +* Command Queue +*/ +#define EXT_CSD_CMDQ_MODE_ENABLED (1<<0) +#define EXT_CSD_CMDQ_DEPTH_MASK 0x1F +#define EXT_CSD_CMDQ_SUPPORTED (1<<0) + +/* +* MMC_SWITCH access modes +*/ +#define MMC_SWITCH_MODE_CMD_SET 0x00 /* Change the command set */ +#define MMC_SWITCH_MODE_SET_BITS 0x01 /* Set bits which are 1 in value */ +#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 /* Clear bits which are 1 in value */ +#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 /* Set target to value */ + +/* +* Erase/trim/discard +*/ +#define MMC_ERASE_ARG 0x00000000 +#define MMC_SECURE_ERASE_ARG 0x80000000 +#define MMC_TRIM_ARG 0x00000001 +#define MMC_DISCARD_ARG 0x00000003 +#define MMC_SECURE_TRIM1_ARG 0x80000001 +#define MMC_SECURE_TRIM2_ARG 0x80008000 +#define MMC_SECURE_ARGS 0x80000000 +#define MMC_TRIM_ARGS 0x00008001 + +#endif /* LINUX_MMC_MMC_H */ diff --git a/pins.h b/pins.h index 5d896a5..3a97287 100644 --- a/pins.h +++ b/pins.h @@ -30,4 +30,9 @@ #define NUVOTON_SPI_CLK 14 // FT2T4 #define NUVOTON_SPI_MOSI 15 // FT2T5 +#define MMC_RST_PIN 9 +#define MMC_CLK_PIN 8 +#define MMC_CMD_PIN 7 +#define MMC_DAT0_PIN 6 + #endif diff --git a/sd.h b/sd.h new file mode 100644 index 0000000..7eb7e72 --- /dev/null +++ b/sd.h @@ -0,0 +1,104 @@ +/* + * include/linux/mmc/sd.h + * + * Copyright (C) 2005-2007 Pierre Ossman, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + */ + +#ifndef LINUX_MMC_SD_H +#define LINUX_MMC_SD_H + +/* SD commands type argument response */ + /* class 0 */ +/* This is basically the same command as for MMC with some quirks. */ +#define SD_SEND_RELATIVE_ADDR 3 /* bcr R6 */ +#define SD_SEND_IF_COND 8 /* bcr [11:0] See below R7 */ +#define SD_SWITCH_VOLTAGE 11 /* ac R1 */ + + /* class 10 */ +#define SD_SWITCH 6 /* adtc [31:0] See below R1 */ + + /* class 5 */ +#define SD_ERASE_WR_BLK_START 32 /* ac [31:0] data addr R1 */ +#define SD_ERASE_WR_BLK_END 33 /* ac [31:0] data addr R1 */ + + /* Application commands */ +#define SD_APP_SET_BUS_WIDTH 6 /* ac [1:0] bus width R1 */ +#define SD_APP_SD_STATUS 13 /* adtc R1 */ +#define SD_APP_SEND_NUM_WR_BLKS 22 /* adtc R1 */ +#define SD_APP_OP_COND 41 /* bcr [31:0] OCR R3 */ +#define SD_APP_SEND_SCR 51 /* adtc R1 */ + +/* OCR bit definitions */ +#define SD_OCR_VDD_18 (1 << 7) /* VDD voltage 1.8 */ +#define SD_OCR_VDD_27_28 (1 << 15) /* VDD voltage 2.7 ~ 2.8 */ +#define SD_OCR_VDD_28_29 (1 << 16) /* VDD voltage 2.8 ~ 2.9 */ +#define SD_OCR_VDD_29_30 (1 << 17) /* VDD voltage 2.9 ~ 3.0 */ +#define SD_OCR_VDD_30_31 (1 << 18) /* VDD voltage 3.0 ~ 3.1 */ +#define SD_OCR_VDD_31_32 (1 << 19) /* VDD voltage 3.1 ~ 3.2 */ +#define SD_OCR_VDD_32_33 (1 << 20) /* VDD voltage 3.2 ~ 3.3 */ +#define SD_OCR_VDD_33_34 (1 << 21) /* VDD voltage 3.3 ~ 3.4 */ +#define SD_OCR_VDD_34_35 (1 << 22) /* VDD voltage 3.4 ~ 3.5 */ +#define SD_OCR_VDD_35_36 (1 << 23) /* VDD voltage 3.5 ~ 3.6 */ +#define SD_OCR_S18R (1 << 24) /* 1.8V switching request */ +#define SD_ROCR_S18A SD_OCR_S18R /* 1.8V switching accepted by card */ +#define SD_OCR_XPC (1 << 28) /* SDXC power control */ +#define SD_OCR_CCS (1 << 30) /* Card Capacity Status */ + +/* + * SD_SWITCH argument format: + * + * [31] Check (0) or switch (1) + * [30:24] Reserved (0) + * [23:20] Function group 6 + * [19:16] Function group 5 + * [15:12] Function group 4 + * [11:8] Function group 3 + * [7:4] Function group 2 + * [3:0] Function group 1 + */ + +/* + * SD_SEND_IF_COND argument format: + * + * [31:12] Reserved (0) + * [11:8] Host Voltage Supply Flags + * [7:0] Check Pattern (0xAA) + */ + +/* + * SCR field definitions + */ + +#define SCR_SPEC_VER_0 0 /* Implements system specification 1.0 - 1.01 */ +#define SCR_SPEC_VER_1 1 /* Implements system specification 1.10 */ +#define SCR_SPEC_VER_2 2 /* Implements system specification 2.00-3.0X */ + +/* + * SD bus widths + */ +#define SD_BUS_WIDTH_1 0 +#define SD_BUS_WIDTH_4 2 + +/* + * SD_SWITCH mode + */ +#define SD_SWITCH_CHECK 0 +#define SD_SWITCH_SET 1 + +/* + * SD_SWITCH function groups + */ +#define SD_SWITCH_GRP_ACCESS 0 + +/* + * SD_SWITCH access modes + */ +#define SD_SWITCH_ACCESS_DEF 0 +#define SD_SWITCH_ACCESS_HS 1 + +#endif /* LINUX_MMC_SD_H */ \ No newline at end of file diff --git a/sdio.c b/sdio.c new file mode 100644 index 0000000..568ec22 --- /dev/null +++ b/sdio.c @@ -0,0 +1,1023 @@ +/* + * Copyright (c) 2022 Balázs Triszka + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include "pico/stdlib.h" +#include "sdio.h" +#include "hardware/pio.h" +#include "hardware/gpio.h" +#include "hardware/dma.h" +#include "sdio.pio.h" +#include "crc7.h" +#include "crc-itu-t.h" +#include "pico/binary_info.h" +#include "pins.h" +#include "mmc_defs.h" +#include "sd.h" + +#define SD_CLK_SM 0u +#define SD_CMD_SM 1u +#define SD_DAT_SM 2u +const int sd_cmd_dma_channel = 11; +const int sd_data_dma_channel = 10; +const int sd_chain_dma_channel = 9; +const int sd_pio_dma_channel = 8; + +#if 0 +#define sd_debug(format, args...) printf(format, ##args) +#else +#define sd_debug(format, args...) (void)0 +#endif + +static inline uint32_t sd_pio_cmd(uint cmd, uint32_t param) +{ + assert(cmd <= sd_cmd_or_dat_program.length); + assert(param <= 0xffff); + return (pio_encode_jmp(cmd) << 16u) | param; +} + +PIO sd_pio = pio1; + +static uint8_t rca_high, rca_low; +uint8_t cid_raw[16]; +uint8_t csd_raw[16]; + +static uint32_t crcs[SDIO_MAX_BLOCK_COUNT * 2]; +static uint32_t ctrl_words[(SDIO_MAX_BLOCK_COUNT + 1) * 4]; +static uint32_t pio_cmd_buf[SDIO_MAX_BLOCK_COUNT * 3]; +uint32_t zeroes; +uint32_t start_bit = 0xfffffffe; + +inline static int safe_wait_tx_empty(pio_hw_t *pio, uint sm) +{ + int wooble = 0; + while (!pio_sm_is_tx_fifo_empty(pio, sm)) + { + wooble++; + if (wooble > 1000000) + { + printf("stuck %d @ %d\n", sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + +inline static int safe_wait_tx_not_full(pio_hw_t *pio, uint sm) +{ + int wooble = 0; + while (pio_sm_is_tx_fifo_full(pio, sm)) + { + wooble++; + if (wooble > 1000000) + { + printf("stuck %d @ %d\n", sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + +inline static int safe_dma_wait_for_finish(pio_hw_t *pio, uint sm, uint chan) +{ + int wooble = 0; + while (dma_channel_is_busy(chan)) + { + wooble++; + if (wooble > 8000000) + { + printf("stuck dma channel %d rem %08x %d @ %d\n", chan, (uint)dma_hw->ch[chan].transfer_count, sm, (int)pio->sm[sm].addr); + __breakpoint(); + return SD_ERR_STUCK; + } + } + return SD_OK; +} + +static inline int acquiesce_sm(int sm) +{ + int rc = safe_wait_tx_empty(sd_pio, sm); + if (rc) + return rc; + + uint32_t foo = 0; + uint32_t timeout = 1000000; + while (--timeout) + { + uint32_t addr = sd_pio->sm[sm].addr; + foo |= 1 << addr; + if (addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) + { + break; + } + // todo not forever + } + if (!timeout) + return SD_ERR_STUCK; + + return SD_OK; +} + +static int __time_critical_func(start_single_dma)(uint dma_channel, uint sm, uint32_t *buf, uint byte_length, bool bswap, bool sniff) +{ + gpio_set_mask(1); + uint word_length = (byte_length + 3) / 4; + dma_channel_config c = dma_channel_get_default_config(dma_channel); + channel_config_set_bswap(&c, bswap); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, DREQ_PIO1_RX0 + sm); + dma_channel_configure( + dma_channel, + &c, + buf, // dest + &sd_pio->rxf[sm], // src + word_length, + false); + if (sniff) + { + assert(sm == SD_DAT_SM); + dma_sniffer_enable(dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + dma_hw->sniff_data = 0; + } + dma_channel_start(dma_channel); + gpio_clr_mask(1); + return SD_OK; +} + +static void __time_critical_func(start_chain_dma_read_with_address_size_only)(uint sm, uint32_t *buf, bool bswap, bool sniff) +{ + assert(!sniff); // for now + dma_channel_config c = dma_channel_get_default_config(sd_data_dma_channel); + channel_config_set_bswap(&c, bswap); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_dreq(&c, DREQ_PIO1_RX0 + sm); + channel_config_set_chain_to(&c, sd_chain_dma_channel); // individual buffers chain back to master + channel_config_set_irq_quiet(&c, true); + + dma_channel_configure( + sd_data_dma_channel, + &c, + 0, // dest + &sd_pio->rxf[sm], // src + 0, + false); + + c = dma_channel_get_default_config(sd_chain_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + channel_config_set_ring(&c, 1, 3); // wrap the write at 8 bytes (so each transfer writes the same 2 word ctrl registers) + dma_channel_configure( + sd_chain_dma_channel, + &c, + &dma_channel_hw_addr(sd_data_dma_channel)->al1_write_addr, // dest + buf, // src + 2, // send 2 words to ctrl block of data chain per transfer + false); + + gpio_set_mask(1); + // if (sniff) + // { + // dma_enable_sniffer(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16); + // dma_hw->sniff_data = 0; + // } + dma_channel_start(sd_chain_dma_channel); + gpio_clr_mask(1); +} + +static __attribute__((used)) __noinline void spoop() +{ + int dma_channel = 3; + dma_channel_config config = dma_channel_get_default_config(dma_channel); + channel_config_set_read_increment(&config, true); + channel_config_set_write_increment(&config, true); + channel_config_set_dreq(&config, DREQ_SPI0_RX); + channel_config_set_transfer_data_size(&config, DMA_SIZE_8); + dma_channel_set_config(dma_channel, &config, false); + + *(volatile uint32_t *)(DMA_BASE + DMA_CH3_AL1_CTRL_OFFSET) = 0x00089831; +} + +static int __time_critical_func(start_read)(int sm, uint32_t *buf, uint byte_length, bool enable) +{ + spoop(); + int rc; + gpio_set_mask(1); + assert(!(3u & (uintptr_t)buf)); // in all fairness we should receive into a buffer from the pool + uint bit_length = byte_length * 8; + if (sm == SD_DAT_SM) + { + assert(!(bit_length & 31u)); + bit_length += 16; + } + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) + return rc; + + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_receive_bits, bit_length - 1)); + pio_sm_set_wrap(sd_pio, sm, sd_cmd_or_dat_wrap_target, sd_cmd_or_dat_wrap); + gpio_clr_mask(1); + gpio_set_mask(1); + if (enable) + pio_sm_set_enabled(sd_pio, sm, true); + if (bit_length & 31u) + { + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) + return rc; + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_in(pio_null, 32 - (bit_length & 31u)))); + } + // now go back to wait state + rc = safe_wait_tx_not_full(sd_pio, sm); + if (rc) + return rc; + pio_sm_put(sd_pio, sm, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sm == SD_DAT_SM ? sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd : sd_cmd_or_dat_offset_no_arg_state_wait_high))); + gpio_clr_mask(1); + return SD_OK; +} + +static int __time_critical_func(finish_read)(uint dma_channel, int sm, uint16_t *suffixed_crc, uint16_t *sniffed_crc) +{ + gpio_set_mask(1); + int rc = safe_dma_wait_for_finish(sd_pio, sm, dma_channel); + if (rc) + return rc; + if (sniffed_crc) + { + *sniffed_crc = (uint16_t)dma_hw->sniff_data; + } + if (sm == SD_DAT_SM) + { + // todo not forever + while (pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)) + ; + uint32_t w = sd_pio->rxf[SD_DAT_SM]; + if (suffixed_crc) + *suffixed_crc = w >> 16u; + } + assert(pio_sm_is_rx_fifo_empty(sd_pio, sm)); + gpio_clr_mask(1); + return SD_OK; +} + +int __time_critical_func(sd_response_dma)(uint dma_channel, uint sm, uint8_t *buf, uint byte_length, bool bswap, uint16_t *suffixed_crc, uint16_t *sniffed_crc, bool first, bool last, bool enable) +{ + int rc = SD_OK; + if (first) + { + rc = start_single_dma(dma_channel, sm, (uint32_t *)buf, byte_length, bswap, sniffed_crc != 0); + if (!rc) + rc = start_read(sm, (uint32_t *)buf, byte_length, enable); + } + + if (!rc) + rc = finish_read(dma_channel, sm, suffixed_crc, sniffed_crc); + + if (!last && !rc) + { + rc = start_single_dma(dma_channel, sm, (uint32_t *)buf, byte_length, bswap, sniffed_crc != 0); + if (!rc) + rc = start_read(sm, (uint32_t *)buf, byte_length, enable); + } + return rc; +} + +int __noinline sd_command(uint8_t cmd, uint32_t arg, void *response) +{ + int rc = acquiesce_sm(SD_CMD_SM); + if (rc) + return rc; + + sd_debug("SD command %d ARG: %x\n", cmd, arg); + + uint8_t b0 = arg >> 24; + uint8_t b1 = arg >> 16; + uint8_t b2 = arg >> 8; + uint8_t b3 = arg; + + cmd |= 0x40; + uint8_t crc = 0; + crc = crc7_table[crc ^ cmd]; + crc = crc7_table[crc ^ b0]; + crc = crc7_table[crc ^ b1]; + crc = crc7_table[crc ^ b2]; + crc = crc7_table[crc ^ b3]; + crc |= 1; + + uint32_t high = (cmd << 24) | (b0 << 16) | (b1 << 8) | b2; + uint32_t low = (b3 << 24) | (crc << 16); + + uint64_t packed_command = (((uint64_t)high) << 32) | low; + + cmd &= 0x3f; + + // disable SM so we don't have a race on us filling the FIFO - we must not stall or we will lose sync with clock + pio_sm_set_enabled(sd_pio, SD_CMD_SM, false); + pio_sm_put(sd_pio, SD_CMD_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 48 - 1)); + pio_sm_put(sd_pio, SD_CMD_SM, (uint32_t)(packed_command >> 32)); + pio_sm_put(sd_pio, SD_CMD_SM, (uint32_t)packed_command); + + uint byte_length = 6; + switch (cmd) + { + // R0 + case MMC_GO_IDLE_STATE: + case MMC_SET_DSR: + case MMC_GO_INACTIVE_STATE: + byte_length = 0; + break; + // R2 + case MMC_SEND_CSD: + case MMC_ALL_SEND_CID: + case MMC_SEND_CID: + byte_length = 17; + break; + } + + if (byte_length) + { + uint8_t receive_buf[20]; + rc = sd_response_dma(sd_cmd_dma_channel, SD_CMD_SM, receive_buf, byte_length, false, NULL, NULL, true, true, true); + if (!rc) + { + ((uint32_t *)receive_buf)[4] = __builtin_bswap32((((uint32_t *)receive_buf)[4] >> 1) | (((uint32_t *)receive_buf)[3] << 31)); + ((uint32_t *)receive_buf)[3] = __builtin_bswap32((((uint32_t *)receive_buf)[3] >> 1) | (((uint32_t *)receive_buf)[2] << 31)); + ((uint32_t *)receive_buf)[2] = __builtin_bswap32((((uint32_t *)receive_buf)[2] >> 1) | (((uint32_t *)receive_buf)[1] << 31)); + ((uint32_t *)receive_buf)[1] = __builtin_bswap32((((uint32_t *)receive_buf)[1] >> 1) | (((uint32_t *)receive_buf)[0] << 31)); + ((uint32_t *)receive_buf)[0] = __builtin_bswap32(((uint32_t *)receive_buf)[0] >> 1); + + bool ok = true; + switch (cmd) + { + // R2: + case MMC_SEND_CSD: + case MMC_ALL_SEND_CID: + case MMC_SEND_CID: + if (response) + memcpy(response, &receive_buf[1], 16); + break; + // R3: + case SD_APP_OP_COND: + case MMC_SEND_OP_COND: + ok = receive_buf[0] == 0x3f && (receive_buf[1] & 0x1e) == 0; + if (response) + *(uint32_t *)response = __builtin_bswap32(*(uint32_t *)&receive_buf[1]); + break; + // R1: + default: + { + if (cmd != receive_buf[0]) + { + printf("tsk\n"); + } + + uint8_t crc = crc7_table[receive_buf[0]]; + crc = crc7_table[crc ^ receive_buf[1]]; + crc = crc7_table[crc ^ receive_buf[2]]; + crc = crc7_table[crc ^ receive_buf[3]]; + crc = crc7_table[crc ^ receive_buf[4]]; + if ((crc | 1u) != receive_buf[5]) + { + panic("bad crc %02x != %02x\n", crc | 1u, receive_buf[5]); + ok = false; + } + + if (response) + *(uint32_t *)response = __builtin_bswap32(*(uint32_t *)&receive_buf[1]); + } + } + if (!ok) + { + printf("bad response from card\n"); + return SD_ERR_BAD_RESPONSE; + } + } + } + else + pio_sm_set_enabled(sd_pio, SD_CMD_SM, true); + + sd_debug("SD command done %d\n", cmd); + return SD_OK; +} + +int sd_wait() +{ + int rc = acquiesce_sm(SD_DAT_SM); + if (!rc) + { + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_set(pio_pindirs, 0))); + pio_sm_put(sd_pio, SD_DAT_SM, + sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_wait_pin(true, 0))); + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high))); + rc = acquiesce_sm(SD_DAT_SM); + } + return rc; +} + +const char *states[] = { "idle", "ready", "ident", "stby", "tran", "data", "rcv", "prg", "dis" }; + +void print_status(uint32_t response) +{ + if (response & R1_OUT_OF_RANGE) + printf(" ORANGE"); + if (response & R1_ADDRESS_ERROR) + printf(" ADDRESS"); + if (response & R1_BLOCK_LEN_ERROR) + printf(" BLEN"); + if (response & R1_ERASE_SEQ_ERROR) + printf(" ESEQ"); + if (response & R1_ERASE_PARAM) + printf(" EPARM"); + if (response & R1_WP_VIOLATION) + printf(" WPV"); + if (response & R1_CARD_IS_LOCKED) + printf(" LOCKED"); + if (response & R1_LOCK_UNLOCK_FAILED) + printf(" UNLOCK"); + if (response & R1_COM_CRC_ERROR) + printf(" CRC"); + if (response & R1_ILLEGAL_COMMAND) + printf(" ILLEGAL"); + if (response & R1_CARD_ECC_FAILED) + printf(" ECC"); + if (response & R1_CC_ERROR) + printf(" INTERNAL"); + if (response & R1_ERROR) + printf(" << ERRORS: "); + if (response & R1_UNDERRUN) + printf(" under_run"); + if (response & R1_OVERRUN) + printf(" over_run"); + if (response & R1_CID_CSD_OVERWRITE) + printf(" c_overwrite"); + if (response & R1_WP_ERASE_SKIP) + printf(" era_skip"); + if (response & R1_CARD_ECC_DISABLED) + printf(" ecc_dis"); + if (response & R1_ERASE_RESET) + printf(" era_reset"); + if (response & R1_SWITCH_ERROR) + printf(" switch"); + if (response & R1_EXCEPTION_EVENT) + printf(" exception"); + printf(" %s", states[R1_CURRENT_STATE(response)]); + printf((response & R1_READY_FOR_DATA) ? " ready" : " not-ready"); + if (response & R1_APP_CMD) + printf(" ACMD..."); + printf("\n"); +} + +void read_status(bool dump) +{ + uint32_t response; + int not_ready_retries = 3; + while (not_ready_retries--) + { + // let's see the status + sd_command(MMC_SEND_STATUS, (rca_high << 24) | (rca_low << 16), &response); + if (dump) + print_status(response); + + // Break if ready + if (response & R1_READY_FOR_DATA) + break; + + // Wait if not ready and try again + sleep_ms(1); + } +} + +int sd_set_clock_divider(uint div) +{ + pio_sm_set_clkdiv_int_frac(sd_pio, SD_CLK_SM, div, 0); + pio_sm_set_clkdiv_int_frac(sd_pio, SD_CMD_SM, div, 0); + pio_sm_set_clkdiv_int_frac(sd_pio, SD_DAT_SM, div, 0); + pio_clkdiv_restart_sm_mask(sd_pio, (1u << SD_CMD_SM) | (1u << SD_CLK_SM) | (1u << SD_DAT_SM)); + return SD_OK; +} + +int sd_init() +{ + int sd_clk_pin = MMC_CLK_PIN; + int sd_cmd_pin = MMC_CMD_PIN; + int sd_dat_pin_base = MMC_DAT0_PIN; + + gpio_set_function(sd_clk_pin, GPIO_FUNC_PIO1); + gpio_set_slew_rate(sd_clk_pin, GPIO_SLEW_RATE_FAST); + gpio_set_drive_strength(sd_clk_pin, GPIO_DRIVE_STRENGTH_12MA); + gpio_set_function(sd_cmd_pin, GPIO_FUNC_PIO1); + gpio_set_function(sd_dat_pin_base, GPIO_FUNC_PIO1); + gpio_set_pulls(sd_clk_pin, false, true); + gpio_set_pulls(sd_cmd_pin, true, false); + gpio_set_pulls(sd_dat_pin_base, true, false); + + static bool added; // todo this is a temporary hack as we don't free + static uint cmd_or_dat_offset; + static uint clk_program_offset; + + if (!added) + { + cmd_or_dat_offset = pio_add_program(sd_pio, &sd_cmd_or_dat_program); + assert(!cmd_or_dat_offset); // we don't add this later because it is assumed to be 0 + clk_program_offset = pio_add_program(sd_pio, &sd_clk_program); + added = true; + } + + pio_sm_config c = sd_clk_program_get_default_config(clk_program_offset); + sm_config_set_sideset_pins(&c, sd_clk_pin); + pio_sm_init(sd_pio, SD_CLK_SM, clk_program_offset, &c); + + c = sd_cmd_or_dat_program_get_default_config(cmd_or_dat_offset); + sm_config_set_out_pins(&c, sd_cmd_pin, 1); + sm_config_set_set_pins(&c, sd_cmd_pin, 1); + sm_config_set_in_pins(&c, sd_cmd_pin); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_shift(&c, false, true, 32); + pio_sm_init(sd_pio, SD_CMD_SM, cmd_or_dat_offset, &c); + + c = sd_cmd_or_dat_program_get_default_config(cmd_or_dat_offset); + sm_config_set_out_pins(&c, sd_dat_pin_base, 1); + sm_config_set_set_pins(&c, sd_dat_pin_base, 1); + sm_config_set_in_pins(&c, sd_dat_pin_base); + sm_config_set_in_shift(&c, false, true, 32); + sm_config_set_out_shift(&c, false, true, 32); + pio_sm_init(sd_pio, SD_DAT_SM, cmd_or_dat_offset, &c); + + int rc = sd_set_clock_divider(355); // 375KHz + if (rc) + return rc; + + pio_sm_exec(sd_pio, SD_CMD_SM, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + pio_sm_exec(sd_pio, SD_DAT_SM, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd)); + + uint32_t all_pin_mask = (1 << sd_dat_pin_base) | (1u << sd_cmd_pin) | (1u << sd_clk_pin); + pio_sm_set_pindirs_with_mask(sd_pio, SD_CLK_SM, all_pin_mask, all_pin_mask); + pio_sm_exec(sd_pio, SD_DAT_SM, pio_encode_set(pio_pins, 1)); + + // Reset hack for xbox + gpio_init(MMC_RST_PIN); + gpio_set_dir(MMC_RST_PIN, GPIO_OUT); + gpio_put(MMC_RST_PIN, 0); + sleep_ms(50); + gpio_put(MMC_RST_PIN, 1); + sleep_ms(50); + + pio_sm_put(sd_pio, SD_CMD_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 80 - 1)); + pio_sm_put(sd_pio, SD_CMD_SM, 0xffffffff); + pio_sm_put(sd_pio, SD_CMD_SM, 0xffffffff); + + pio_sm_put(sd_pio, SD_CMD_SM, 0xffff0000 | pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + pio_enable_sm_mask_in_sync(sd_pio, (1u << SD_CMD_SM) | (1u << SD_CLK_SM) | (1u << SD_DAT_SM)); + + sd_command(MMC_GO_IDLE_STATE, 0, 0); + + while (true) + { + uint32_t response; + sd_command(MMC_SEND_OP_COND, 0x40FF8000, &response); + if ((response & 0xFF000000) == (MMC_CARD_BUSY | SD_OCR_CCS)) + break; + } + sd_debug("Card ready\r\n"); + + sd_command(MMC_ALL_SEND_CID, 0, cid_raw); + sd_debug("CID: "); + for (int i = 0; i < 16; ++i) + { + sd_debug("%02X ", cid_raw[i]); + } + sd_debug("\n"); + + uint32_t response; + sd_command(MMC_SET_RELATIVE_ADDR, 0, &response); + rca_high = response >> 24; + rca_low = response >> 16; + + sd_command(MMC_SEND_CSD, (rca_high << 24) | (rca_low << 16), csd_raw); + sd_debug("CSD: "); + for (int i = 0; i < 16; ++i) + { + sd_debug("%02X ", csd_raw[i]); + } + sd_debug("\n"); + + sd_command(MMC_SELECT_CARD, (rca_high << 24) | (rca_low << 16), &response); + sd_wait(); + + return sd_set_clock_divider(4); +} + +static uint32_t *start_read_to_buf(int sm, uint32_t *buf, uint byte_length, bool first) +{ + uint bit_length = byte_length * 8; + if (sm == SD_DAT_SM) + { + assert(!(bit_length & 31u)); + bit_length += 16; + } + + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_receive_bits, bit_length - 1); + if (first) + pio_sm_set_wrap(sd_pio, sm, sd_cmd_or_dat_wrap_target, sd_cmd_or_dat_wrap); + + // add zero padding to word boundary if necessary + if (bit_length & 31u) + { + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_in(pio_null, 32 - (bit_length & 31u))); + } + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sm == SD_DAT_SM ? sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd : sd_cmd_or_dat_offset_no_arg_state_wait_high)); + return buf; +} + +// note caller must make space for CRC (2 word) in 4 bit mode +int sd_readblocks_scatter_async(uint32_t *control_words, uint32_t block, uint block_count) +{ + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + uint32_t total = 0; + uint32_t *p = control_words; + while (p[0]) + { + // printf("%p %08x %08x\n", p, (uint)p[0], (uint)p[1]); + assert(p[1]); + total += p[1]; + p += 2; + } + + // todo further state checks + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) + { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + assert(block_count <= SDIO_MAX_BLOCK_COUNT); + + assert(total == block_count * (128 + 1)); + start_chain_dma_read_with_address_size_only(SD_DAT_SM, control_words, true, false); + uint32_t *buf = pio_cmd_buf; + for (int i = 0; i < block_count; i++) + { + buf = start_read_to_buf(SD_DAT_SM, buf, 512, !i); + } + + dma_channel_config c = dma_channel_get_default_config(sd_pio_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_dreq(&c, DREQ_PIO1_TX0 + SD_DAT_SM); + dma_channel_configure( + sd_pio_dma_channel, + &c, + &sd_pio->txf[SD_DAT_SM], // dest + pio_cmd_buf, // src + buf - pio_cmd_buf, + true); + // todo decide timing of this - as long as dat lines are hi, this is fine. (note this comment now applies to the trigger true in the dma_channel_configure) + // dma_channel_start(sd_pio_dma_channel); + assert(block_count); + int rc; + if (block_count == 1) + { + uint32_t response; + rc = sd_command(MMC_READ_SINGLE_BLOCK, block, &response); + if (!rc) + rc = response & R1_OUT_OF_RANGE; + } + else + { + rc = sd_command(MMC_SET_BLOCK_COUNT, block_count, 0); + if (!rc) + { + uint32_t response; + rc = sd_command(MMC_READ_MULTIPLE_BLOCK, block, &response); + if (!rc) + rc = response & R1_OUT_OF_RANGE; + } + } + return rc; +} + +bool sd_scatter_read_complete(int *status) +{ + // printf("%d:%d %d:%d %d:%d %d\n", dma_channel_is_busy(sd_chain_dma_channel), (uint)dma_hw->ch[sd_chain_dma_channel].transfer_count, + // dma_channel_is_busy(sd_data_dma_channel), (uint)dma_hw->ch[sd_data_dma_channel].transfer_count, + // dma_channel_is_busy(sd_pio_dma_channel), (uint)dma_hw->ch[sd_pio_dma_channel].transfer_count, (uint)sd_pio->sm[SD_DAT_SM].addr); + // this is a bit half arsed atm + bool rc; + if (dma_channel_is_busy(sd_chain_dma_channel) || dma_channel_is_busy(sd_data_dma_channel) || dma_channel_is_busy(sd_pio_dma_channel)) + { + rc = false; + } + else + { + rc = (sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd && + pio_sm_is_tx_fifo_empty(sd_pio, SD_DAT_SM)); + } + if (status) + *status = SD_OK; + return rc; +} + +int sd_readblocks_async(void *buf, uint32_t block, uint block_count) +{ + assert(block_count <= SDIO_MAX_BLOCK_COUNT); + + uint32_t *p = ctrl_words; + uint crc_words = 1; + for (int i = 0; i < block_count; i++) + { + *p++ = (uintptr_t)((uint8_t *)buf + i * 512); + *p++ = 128; + // for now we read the CRCs also + *p++ = (uintptr_t)(crcs + i * crc_words); + *p++ = crc_words; + } + *p++ = 0; + *p++ = 0; + return sd_readblocks_scatter_async(ctrl_words, block, block_count); +} + +int sd_readblocks_sync(void *buf, uint32_t block, uint block_count) +{ + assert(block_count <= SDIO_MAX_BLOCK_COUNT); + + uint32_t *p = ctrl_words; + uint crc_words = 1; + for (int i = 0; i < block_count; i++) + { + *p++ = (uintptr_t)((uint8_t *)buf + i * 512); + *p++ = 128; + // for now we read the CRCs also + *p++ = (uintptr_t)(crcs + i * crc_words); + // printf("%08x\n", (uint)(uint32_t)(crcs + i * crc_words)); + *p++ = crc_words; + } + *p++ = 0; + *p++ = 0; + int rc = sd_readblocks_scatter_async(ctrl_words, block, block_count); + if (!rc) + { + while (!sd_scatter_read_complete(&rc)) + { + tight_loop_contents(); + } + } + return rc; +} + +static void __time_critical_func(start_chain_dma_write)(uint sm, uint32_t *buf) +{ + dma_channel_config c = dma_get_channel_config(sd_data_dma_channel); + channel_config_set_chain_to(&c, sd_chain_dma_channel); + channel_config_set_irq_quiet(&c, true); + dma_channel_set_config(sd_data_dma_channel, &c, false); + + c = dma_channel_get_default_config(sd_chain_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, true); + channel_config_set_ring(&c, 1, 4); // wrap the write at 16 bytes + dma_channel_configure( + sd_chain_dma_channel, + &c, + &dma_channel_hw_addr(sd_data_dma_channel)->read_addr, // ch DMA config (target "ring" buffer size 16) - this is (read_addr, write_addr, transfer_count, ctrl), // dest + buf, // src + 4, // send 4 words to ctrl block of data chain per transfer + false); + gpio_set_mask(1); + dma_channel_start(sd_chain_dma_channel); + gpio_clr_mask(1); +} + +static uint32_t dma_ctrl_for(enum dma_channel_transfer_size size, bool src_incr, bool dst_incr, uint dreq, + uint chain_to, bool ring_sel, uint ring_size, bool enable) +{ + dma_channel_config c = dma_channel_get_default_config(0); // channel doesn't matter as we set chain_to later (it is just use to pre-populate that) + channel_config_set_transfer_data_size(&c, size); + channel_config_set_read_increment(&c, src_incr); + channel_config_set_write_increment(&c, dst_incr); + channel_config_set_dreq(&c, dreq); + channel_config_set_chain_to(&c, chain_to); + channel_config_set_ring(&c, ring_sel, ring_size); + channel_config_set_enable(&c, enable); + return c.ctrl; +} + +int sd_writeblocks_async(const void *data, uint32_t sector_num, uint sector_count) +{ + uint32_t *buf = pio_cmd_buf; + for (int i = 0; i < sector_count; i++) + { + // we send an extra word even though the CRC is only 16 bits to make life easy... the receiver doesn't care + // todo that would need to work anyway for inline CRC (which can't include a pio_cmd) + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_send_bits, 512 * 8 + 32 + 32 - 1); + } + *buf++ = sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high)); + + if (sector_count > (SDIO_MAX_BLOCK_COUNT - 1) / 4) + { + panic("too many blocks for now"); + } + + assert(pio_sm_is_tx_fifo_empty(sd_pio, SD_DAT_SM)); + + uint32_t *p = ctrl_words; +#define build_transfer(src, words, size, flags) \ + *p++ = (uintptr_t)(src); \ + *p++ = (uintptr_t)(&sd_pio->txf[SD_DAT_SM]); \ + *p++ = words; \ + *p++ = dma_ctrl_for(size, true, false, DREQ_PIO1_TX0 + SD_DAT_SM, sd_chain_dma_channel, 0, 0, true) | (flags); + + for (int i = 0; i < sector_count; i++) + { + // first cb - zero out sniff data + *p++ = (uintptr_t)&zeroes; + *p++ = (uintptr_t)(&dma_hw->sniff_data); + *p++ = 1; + *p++ = dma_ctrl_for(DMA_SIZE_32, false, false, DREQ_FORCE, sd_chain_dma_channel, 0, 0, true); + // second cb - send bits command + build_transfer(pio_cmd_buf + i, 1, DMA_SIZE_32, 0); + build_transfer(&start_bit, 1, DMA_SIZE_32, 0); + // third cb - 128 words of sector data + build_transfer((uint8_t *)data + i * 512, 128, DMA_SIZE_32, DMA_CH0_CTRL_TRIG_BSWAP_BITS | DMA_CH0_CTRL_TRIG_SNIFF_EN_BITS); + // fourth cb - transfer sniff + // note offset of 2, since we bswap the data + build_transfer((uintptr_t)&dma_hw->sniff_data, 1, DMA_SIZE_16, 0); // DMA_CH0_CTRL_TRIG_BSWAP_BITS); + } + // final cb - return to wait state + build_transfer(pio_cmd_buf + sector_count, 1, DMA_SIZE_32, 0); + *p++ = 0; + *p++ = 0; + + // todo further state checks + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) + { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_tx_fifo_empty(sd_pio, SD_DAT_SM)); + pio_sm_put(sd_pio, SD_DAT_SM, sd_pio_cmd(sd_cmd_or_dat_offset_state_inline_instruction, pio_encode_jmp(sd_cmd_or_dat_offset_no_arg_state_wait_high))); + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) + { + } + + int rc = 0; + + assert(sector_count); + + if (sector_count == 1) + { + rc = sd_command(MMC_WRITE_BLOCK, sector_num, 0); + } + else + { + // todo this is only writing the first sector on SanDisk EDGE 16G right now - probably need a delay between sectors... works fine on a SAMSUNG EVO 32G + + rc = sd_command(MMC_SET_BLOCK_COUNT, sector_count, 0); + if (!rc) + rc = sd_command(MMC_WRITE_MULTIPLE_BLOCK, sector_num, 0); + } + + if (!rc) + { + pio_sm_set_enabled(sd_pio, SD_DAT_SM, false); + dma_sniffer_enable(sd_data_dma_channel, DMA_SNIFF_CTRL_CALC_VALUE_CRC16, true); + dma_sniffer_set_byte_swap_enabled(true); + start_chain_dma_write(SD_DAT_SM, ctrl_words); + pio_sm_set_enabled(sd_pio, SD_DAT_SM, true); + // printf("dma chain data (rem %04x @ %08x) data (rem %04x @ %08x) pio data (rem %04x @ %08x) datsm @ %d\n", + // (uint)dma_hw->ch[sd_chain_dma_channel].transfer_count, + // (uint)dma_hw->ch[sd_chain_dma_channel].read_addr, + // (uint)dma_hw->ch[sd_data_dma_channel].transfer_count, (uint)dma_hw->ch[sd_data_dma_channel].read_addr, + // (uint)dma_hw->ch[sd_pio_dma_channel].transfer_count, (uint)dma_hw->ch[sd_pio_dma_channel].read_addr, + // (int)sd_pio->sm[SD_DAT_SM].addr); + } + return rc; +} + +bool sd_write_complete(int *status) +{ + // printf("dma chain data (rem %04x @ %08x) data (rem %04x @ %08x) datsm @ %d\n", + // (uint)dma_hw->ch[sd_chain_dma_channel].transfer_count, (uint)dma_hw->ch[sd_chain_dma_channel].read_addr, + // (uint)dma_hw->ch[sd_data_dma_channel].transfer_count, (uint)dma_hw->ch[sd_data_dma_channel].read_addr, + // (int)sd_pio->sm[SD_DAT_SM].addr); + // this is a bit half arsed atm + bool rc; + if (dma_channel_is_busy(sd_chain_dma_channel) || dma_channel_is_busy(sd_data_dma_channel)) + rc = false; + else + rc = sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd; + if (rc) + { + // The pio finished sending the data, but the sd may still writes the data to the nand. + // Here we check if it's still in programming state. + uint32_t response; + sd_command(MMC_SEND_STATUS, (rca_high << 24) | (rca_low << 16), &response); + if (R1_CURRENT_STATE(response) == R1_STATE_PRG) + rc = false; + } + if (status) + *status = SD_OK; + return rc; +} + +int sd_writeblocks_sync(const void *data, uint32_t sector_num, uint sector_count) +{ + int rc = sd_writeblocks_async(data, sector_num, sector_count); + if (!rc) + { + while (!sd_write_complete(&rc)) + { + tight_loop_contents(); + } + } + return rc; +} + +void sd_read_cid(void *cid) +{ + memcpy(cid, cid_raw, sizeof(cid_raw)); +} + +void sd_read_csd(void *csd) +{ + memcpy(csd, csd_raw, sizeof(csd_raw)); +} + +int sd_read_ext_csd(void *ext_csd) +{ + uint32_t *p = ctrl_words; + + *p++ = (uintptr_t)ext_csd; + *p++ = 128; + + // for now we read the CRCs also + *p++ = (uintptr_t)crcs; + *p++ = 1; + + *p++ = 0; + *p++ = 0; + + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + + uint32_t total = 0; + + p = ctrl_words; + while (p[0]) + { + assert(p[1]); + total += p[1]; + p += 2; + } + + // todo further state checks + while (sd_pio->sm[SD_DAT_SM].addr != sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd) + { + printf("oops %d\n", (uint)sd_pio->sm[SD_DAT_SM].addr); + } + assert(sd_pio->sm[SD_DAT_SM].addr == sd_cmd_or_dat_offset_no_arg_state_waiting_for_cmd); + assert(pio_sm_is_rx_fifo_empty(sd_pio, SD_DAT_SM)); + + start_chain_dma_read_with_address_size_only(SD_DAT_SM, ctrl_words, true, false); + uint32_t *buf = pio_cmd_buf; + buf = start_read_to_buf(SD_DAT_SM, pio_cmd_buf, 512, true); + + dma_channel_config c = dma_channel_get_default_config(sd_pio_dma_channel); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_dreq(&c, DREQ_PIO1_TX0 + SD_DAT_SM); + dma_channel_configure( + sd_pio_dma_channel, + &c, + &sd_pio->txf[SD_DAT_SM], // dest + pio_cmd_buf, // src + buf - pio_cmd_buf, + true); + + uint32_t response; + int rc = sd_command(MMC_SEND_EXT_CSD, 0, &response); + if (!rc) + { + while (!sd_scatter_read_complete(&rc)) + { + tight_loop_contents(); + } + } + + return rc; +} \ No newline at end of file diff --git a/sdio.h b/sdio.h new file mode 100644 index 0000000..5c6ec4c --- /dev/null +++ b/sdio.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Balázs Triszka + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef _SDIO_H +#define _SDIO_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pico.h" + +#define SD_OK (0) +#define SD_ERR_STUCK (-1) +#define SD_ERR_BAD_RESPONSE (-2) +#define SD_ERR_CRC (-3) +#define SD_ERR_BAD_PARAM (-4) + +#define SDIO_MAX_BLOCK_COUNT 32 +#define SD_SECTOR_SIZE 512 + +// todo buffer pool +int sd_init(); +int sd_readblocks_sync(void *buf, uint32_t block, uint block_count); +int sd_readblocks_async(void *buf, uint32_t block, uint block_count); +bool sd_scatter_read_complete(int *status); +int sd_writeblocks_async(const void *data, uint32_t sector_num, uint sector_count); +int sd_writeblocks_sync(const void *data, uint32_t sector_num, uint sector_count); +bool sd_write_complete(int *status); +void sd_read_cid(void *cid); +void sd_read_csd(void *csd); +int sd_read_ext_csd(void *ext_csd); + +#endif + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/sdio.pio b/sdio.pio new file mode 100644 index 0000000..4ddafeb --- /dev/null +++ b/sdio.pio @@ -0,0 +1,67 @@ +; Copyright (c) 2022 Balázs Triszka +; +; This program is free software; you can redistribute it and/or modify it +; under the terms and conditions of the GNU General Public License, +; version 2, as published by the Free Software Foundation. +; +; This program is distributed in the hope it will be useful, but WITHOUT +; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +; more details. +; +; You should have received a copy of the GNU General Public License +; along with this program. If not, see . + +; NOTE IT IS IMPERATIVE THAT YOU DON"T SCREW UP THE CLOCK EVEN FOR ONE HALF CYCLE, OTHERWISE THE DEVICE WILL LIKELY BE DISPLEASED AND MAY SEND GARBAGE (EVEN IF IT ISN"T DOING ANYTHING IMPORTANT WHEN YOU DO SO) + +.define sd_irq_num 7 + +.program sd_clk +.side_set 1 +.wrap_target + irq sd_irq_num side 1 + irq clear sd_irq_num side 0 +.wrap + +.program sd_cmd_or_dat +.origin 0 ; must load at zero (offsets are hardcoded in instruction stream) +public no_arg_state_wait_high: ; this is a no arg state which means it must always appear in the second half of a word + ; make sure pins are hi when we set output dir (note if we are 1 bit we'll be configured for 1 pin only, so sending 0b1111 is fine) + set pins, 0b1111 + set pindirs, 0b1111 + +public no_arg_state_waiting_for_cmd: + out exec, 16 ; expected to be a jmp to a state + +public state_send_bits: + out x, 16 + wait 0 irq sd_irq_num +send_loop1: + out pins, 1 + jmp x-- send_loop1 + +public state_inline_instruction: + out exec, 16 ; may be any instruction +.wrap_target + out exec, 16 ; expected to be a jmp to a state + +public state_receive_bits: + out x, 16 + set pindirs, 0 + wait 1 pin, 0 + wait 0 pin, 0 + wait 0 irq sd_irq_num + ; note we use wrap setup to configure receive bit/nibble transfers +public wrap_for_4bit_receive: +receive_loop1: + in pins, 1 + jmp x-- receive_loop1 +.wrap + +; #if INCLUDE_4BIT +public wrap_target_for_4bit_receive: +receive_loop4: + in pins, 4 + jmp x-- receive_loop4 + out exec, 16 ; expected to be a jmp to a state +; #endif