From e2d49bac4f554bea1e02680caa7937d0d02fb7a8 Mon Sep 17 00:00:00 2001 From: Samuel Date: Sat, 14 Jun 2025 20:02:46 +0200 Subject: [PATCH] =?UTF-8?q?Code=20fonctionnel,=20on=20=C3=A9crit=20sur=20l?= =?UTF-8?q?a=20carte=20SD=20et=20sur=20la=20cl=C3=A9=20USB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/c_cpp_properties.json | 3 +- .vscode/settings.json | 7 +- CMakeLists.txt | 41 +- diskio_SDIO.c | 173 +++ diskio_USB.c | 3 +- lib/FatFs/source/diskio.c | 11 +- lib/FatFs/source/diskio.h | 8 + lib/sd_driver/SDIO/SdioCard.h | 214 +++ lib/sd_driver/SDIO/ZuluSCSI_platform.h | 181 +++ lib/sd_driver/SDIO/rp2040_sdio.c | 860 ++++++++++++ lib/sd_driver/SDIO/rp2040_sdio.h | 120 ++ lib/sd_driver/SDIO/rp2040_sdio.pio | 157 +++ lib/sd_driver/SDIO/sd_card_sdio.c | 664 +++++++++ lib/sd_driver/SPI/my_spi.c | 371 +++++ lib/sd_driver/SPI/my_spi.h | 124 ++ lib/sd_driver/SPI/sd_card_spi.c | 1731 ++++++++++++++++++++++++ lib/sd_driver/SPI/sd_card_spi.h | 29 + lib/sd_driver/SPI/sd_spi.c | 65 + lib/sd_driver/SPI/sd_spi.h | 135 ++ lib/sd_driver/crash.c | 312 +++++ lib/sd_driver/crash.h | 114 ++ lib/sd_driver/crc.c | 379 ++++++ lib/sd_driver/crc.h | 73 + lib/sd_driver/delays.h | 79 ++ lib/sd_driver/dma_interrupts.c | 85 ++ lib/sd_driver/dma_interrupts.h | 14 + lib/sd_driver/f_util.c | 155 +++ lib/sd_driver/f_util.h | 32 + lib/sd_driver/hw_config.h | 44 + lib/sd_driver/my_debug.c | 223 +++ lib/sd_driver/my_debug.h | 140 ++ lib/sd_driver/my_rtc.c | 159 +++ lib/sd_driver/my_rtc.h | 27 + lib/sd_driver/rp2040_sdio.pio.h | 124 ++ lib/sd_driver/sd_card.c | 386 ++++++ lib/sd_driver/sd_card.h | 163 +++ lib/sd_driver/sd_card_constants.h | 103 ++ lib/sd_driver/sd_regs.h | 152 +++ lib/sd_driver/sd_timeouts.c | 19 + lib/sd_driver/sd_timeouts.h | 23 + lib/sd_driver/util.h | 91 ++ main.c | 101 ++ msc_app.c | 2 +- 43 files changed, 7885 insertions(+), 12 deletions(-) create mode 100644 diskio_SDIO.c create mode 100755 lib/sd_driver/SDIO/SdioCard.h create mode 100755 lib/sd_driver/SDIO/ZuluSCSI_platform.h create mode 100755 lib/sd_driver/SDIO/rp2040_sdio.c create mode 100755 lib/sd_driver/SDIO/rp2040_sdio.h create mode 100755 lib/sd_driver/SDIO/rp2040_sdio.pio create mode 100644 lib/sd_driver/SDIO/sd_card_sdio.c create mode 100755 lib/sd_driver/SPI/my_spi.c create mode 100755 lib/sd_driver/SPI/my_spi.h create mode 100644 lib/sd_driver/SPI/sd_card_spi.c create mode 100644 lib/sd_driver/SPI/sd_card_spi.h create mode 100755 lib/sd_driver/SPI/sd_spi.c create mode 100755 lib/sd_driver/SPI/sd_spi.h create mode 100644 lib/sd_driver/crash.c create mode 100644 lib/sd_driver/crash.h create mode 100755 lib/sd_driver/crc.c create mode 100755 lib/sd_driver/crc.h create mode 100644 lib/sd_driver/delays.h create mode 100644 lib/sd_driver/dma_interrupts.c create mode 100644 lib/sd_driver/dma_interrupts.h create mode 100644 lib/sd_driver/f_util.c create mode 100755 lib/sd_driver/f_util.h create mode 100644 lib/sd_driver/hw_config.h create mode 100644 lib/sd_driver/my_debug.c create mode 100755 lib/sd_driver/my_debug.h create mode 100644 lib/sd_driver/my_rtc.c create mode 100755 lib/sd_driver/my_rtc.h create mode 100644 lib/sd_driver/rp2040_sdio.pio.h create mode 100755 lib/sd_driver/sd_card.c create mode 100644 lib/sd_driver/sd_card.h create mode 100644 lib/sd_driver/sd_card_constants.h create mode 100644 lib/sd_driver/sd_regs.h create mode 100644 lib/sd_driver/sd_timeouts.c create mode 100644 lib/sd_driver/sd_timeouts.h create mode 100755 lib/sd_driver/util.h diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 890144b..313a7c7 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -6,7 +6,8 @@ "${workspaceFolder}/build/generated/pico_base", "${env:PICO_SDK_PATH}/src/**/include", "${env:PICO_SDK_PATH}/lib/**/src", - "${workspaceFolder}/lib/FatFs/source" + "${workspaceFolder}/lib/FatFs/source", + "${workspaceFolder}/lib/sd_driver" ], "myCompilerPath": "/usr/bin/arm-none-eabi-gcc" diff --git a/.vscode/settings.json b/.vscode/settings.json index 8b1d923..16d2457 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,11 @@ "tusb_common.h": "c", "diskio.h": "c", "ff.h": "c", - "time.h": "c" + "time.h": "c", + "hw_config.h": "c", + "sd_card.h": "c", + "rp2040_sdio.h": "c", + "util.h": "c", + "stddef.h": "c" } } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 38698a9..b55ba65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,8 @@ pico_sdk_init() add_executable(host_cdc_msc_hid) +pico_generate_pio_header(host_cdc_msc_hid ${CMAKE_CURRENT_LIST_DIR}/lib/sd_driver/SDIO/rp2040_sdio.pio) + # Example source target_sources(host_cdc_msc_hid PUBLIC hid_app.c @@ -19,19 +21,52 @@ target_sources(host_cdc_msc_hid PUBLIC msc_app.c cdc_app.c diskio_USB.c + diskio_SDIO.c lib/FatFs/source/ff.c lib/FatFs/source/ffsystem.c lib/FatFs/source/diskio.c + lib/sd_driver/my_debug.c + lib/sd_driver/dma_interrupts.c + lib/sd_driver/sd_card.c + lib/sd_driver/crc.c + lib/sd_driver/crash.c + lib/sd_driver/f_util.c + lib/sd_driver/my_rtc.c + lib/sd_driver/sd_timeouts.c + lib/sd_driver/SDIO/rp2040_sdio.c + lib/sd_driver/SDIO/sd_card_sdio.c + lib/sd_driver/SPI/my_spi.c + lib/sd_driver/SPI/sd_card_spi.c + lib/sd_driver/SPI/sd_spi.c ) # Make sure TinyUSB can find tusb_config.h target_include_directories(host_cdc_msc_hid PUBLIC - ${CMAKE_CURRENT_LIST_DIR} - ${CMAKE_CURRENT_LIST_DIR}/lib/FatFs/source/) + ${CMAKE_CURRENT_LIST_DIR}/ + ${CMAKE_CURRENT_LIST_DIR}/lib/FatFs/source/ + ${CMAKE_CURRENT_LIST_DIR}/lib/sd_driver/) + +if(PICO_RISCV) + set(HWDEP_LIBS hardware_watchdog) +else() + set(HWDEP_LIBS cmsis_core) +endif() # In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_host # for TinyUSB device support and tinyusb_board for the additional board support library used by the example -target_link_libraries(host_cdc_msc_hid PUBLIC pico_stdlib tinyusb_host tinyusb_board) +target_link_libraries( host_cdc_msc_hid PUBLIC + pico_stdlib + tinyusb_host + tinyusb_board + hardware_dma + hardware_pio + hardware_spi + hardware_sync + pico_aon_timer + pico_stdlib + ${HWDEP_LIBS} + ) + pico_add_extra_outputs(host_cdc_msc_hid) diff --git a/diskio_SDIO.c b/diskio_SDIO.c new file mode 100644 index 0000000..dbeddbf --- /dev/null +++ b/diskio_SDIO.c @@ -0,0 +1,173 @@ +/* glue.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +/*-----------------------------------------------------------------------*/ +/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */ +/*-----------------------------------------------------------------------*/ +/* If a working storage control module is available, it should be */ +/* attached to the FatFs via a glue function rather than modifying it. */ +/* This is an example of glue functions to attach various exsisting */ +/* storage control modules to the FatFs module with a defined API. */ +/*-----------------------------------------------------------------------*/ +// +// +#include "lib/sd_driver/hw_config.h" +#include "lib/sd_driver/my_debug.h" +#include "lib/sd_driver/sd_card.h" +// +#include "diskio.h" /* Declarations of disk functions */ + +//#define TRACE_PRINTF(fmt, args...) +#define TRACE_PRINTF printf + +/*-----------------------------------------------------------------------*/ +/* Get Drive Status */ +/*-----------------------------------------------------------------------*/ + +DSTATUS SDIO_disk_status(BYTE pdrv /* Physical drive number to identify the drive */ +) { + TRACE_PRINTF(">>> %s\n", __FUNCTION__); + sd_card_t *sd_card_p = sd_get_by_num(pdrv); + if (!sd_card_p) return RES_PARERR; + sd_card_detect(sd_card_p); // Fast: just a GPIO read + return sd_card_p->state.m_Status; // See http://elm-chan.org/fsw/ff/doc/dstat.html +} + +/*-----------------------------------------------------------------------*/ +/* Inidialize a Drive */ +/*-----------------------------------------------------------------------*/ + +DSTATUS SDIO_disk_initialize( + BYTE pdrv /* Physical drive number to identify the drive */ +) { + TRACE_PRINTF(">>> %s\n", __FUNCTION__); + + bool ok = sd_init_driver(); + if (!ok) return RES_NOTRDY; + + sd_card_t *sd_card_p = sd_get_by_num(pdrv); + if (!sd_card_p) return RES_PARERR; + DSTATUS ds = disk_status(pdrv); + if (STA_NODISK & ds) + return ds; + // See http://elm-chan.org/fsw/ff/doc/dstat.html + return sd_card_p->init(sd_card_p); +} + +static int sdrc2dresult(int sd_rc) { + switch (sd_rc) { + case SD_BLOCK_DEVICE_ERROR_NONE: + return RES_OK; + case SD_BLOCK_DEVICE_ERROR_UNUSABLE: + case SD_BLOCK_DEVICE_ERROR_NO_RESPONSE: + case SD_BLOCK_DEVICE_ERROR_NO_INIT: + case SD_BLOCK_DEVICE_ERROR_NO_DEVICE: + return RES_NOTRDY; + case SD_BLOCK_DEVICE_ERROR_PARAMETER: + case SD_BLOCK_DEVICE_ERROR_UNSUPPORTED: + return RES_PARERR; + case SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED: + return RES_WRPRT; + case SD_BLOCK_DEVICE_ERROR_CRC: + case SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK: + case SD_BLOCK_DEVICE_ERROR_ERASE: + case SD_BLOCK_DEVICE_ERROR_WRITE: + default: + return RES_ERROR; + } + } + +/*-----------------------------------------------------------------------*/ +/* Read Sector(s) */ +/*-----------------------------------------------------------------------*/ + +DRESULT SDIO_disk_read(BYTE pdrv, /* Physical drive number to identify the drive */ + BYTE *buff, /* Data buffer to store read data */ + LBA_t sector, /* Start sector in LBA */ + UINT count /* Number of sectors to read */ +) { + TRACE_PRINTF(">>> %s\n", __FUNCTION__); + sd_card_t *sd_card_p = sd_get_by_num(pdrv); + if (!sd_card_p) { + TRACE_PRINTF("Error: !sd_card_p, pdrv: %d\n", pdrv); + return RES_PARERR; + } + int rc = sd_card_p->read_blocks(sd_card_p, buff, sector, count); + return sdrc2dresult(rc); +} + +/*-----------------------------------------------------------------------*/ +/* Write Sector(s) */ +/*-----------------------------------------------------------------------*/ + +#if FF_FS_READONLY == 0 + +DRESULT SDIO_disk_write(BYTE pdrv, /* Physical drive number to identify the drive */ + const BYTE *buff, /* Data to be written */ + LBA_t sector, /* Start sector in LBA */ + UINT count /* Number of sectors to write */ +) { + TRACE_PRINTF(">>> %s\n", __FUNCTION__); + sd_card_t *sd_card_p = sd_get_by_num(pdrv); + if (!sd_card_p) return RES_PARERR; + int rc = sd_card_p->write_blocks(sd_card_p, buff, sector, count); + return sdrc2dresult(rc); +} + +#endif + +/*-----------------------------------------------------------------------*/ +/* Miscellaneous Functions */ +/*-----------------------------------------------------------------------*/ + +DRESULT SDIO_disk_ioctl(BYTE pdrv, /* Physical drive number (0..) */ + BYTE cmd, /* Control code */ + void *buff /* Buffer to send/receive control data */ +) { + TRACE_PRINTF(">>> %s\n", __FUNCTION__); + sd_card_t *sd_card_p = sd_get_by_num(pdrv); + if (!sd_card_p) return RES_PARERR; + switch (cmd) { + case GET_SECTOR_COUNT: { // Retrieves number of available sectors, the + // largest allowable LBA + 1, on the drive + // into the LBA_t variable pointed by buff. + // This command is used by f_mkfs and f_fdisk + // function to determine the size of + // volume/partition to be created. It is + // required when FF_USE_MKFS == 1. + static LBA_t n; + n = sd_card_p->get_num_sectors(sd_card_p); + *(LBA_t *)buff = n; + if (!n) return RES_ERROR; + return RES_OK; + } + case GET_BLOCK_SIZE: { // Retrieves erase block size of the flash + // memory media in unit of sector into the DWORD + // variable pointed by buff. The allowable value + // is 1 to 32768 in power of 2. Return 1 if the + // erase block size is unknown or non flash + // memory media. This command is used by only + // f_mkfs function and it attempts to align data + // area on the erase block boundary. It is + // required when FF_USE_MKFS == 1. + static DWORD bs = 1; + *(DWORD *)buff = bs; + return RES_OK; + } + case CTRL_SYNC: + sd_card_p->sync(sd_card_p); + return RES_OK; + default: + return RES_PARERR; + } +} \ No newline at end of file diff --git a/diskio_USB.c b/diskio_USB.c index 30fd8eb..1050d44 100644 --- a/diskio_USB.c +++ b/diskio_USB.c @@ -110,6 +110,7 @@ DRESULT USB_disk_ioctl ( return RES_OK; } +/* DWORD get_fattime (void){ return (2025-1970) << 25 | // Année @@ -119,4 +120,4 @@ DWORD get_fattime (void){ 29 << 5 | // Minutes 30 << 0 // Secondes ; -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/lib/FatFs/source/diskio.c b/lib/FatFs/source/diskio.c index e767fe6..592175e 100644 --- a/lib/FatFs/source/diskio.c +++ b/lib/FatFs/source/diskio.c @@ -26,7 +26,7 @@ DSTATUS disk_status ( int result; if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){ - + SDIO_disk_status(pdrv - DEV_USB_MIN); return 0; // OK } @@ -50,8 +50,9 @@ DSTATUS disk_initialize ( { DSTATUS stat; int result; + printf("disk_initialize : %d\n", pdrv); if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){ - + SDIO_disk_initialize(pdrv - DEV_SDIO_MIN); return 0; // OK } @@ -79,7 +80,7 @@ DRESULT disk_read ( int result; if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){ - + SDIO_disk_read(pdrv - DEV_SDIO_MIN, buff, sector, count); return 0; // OK } @@ -110,7 +111,7 @@ DRESULT disk_write ( int result; if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){ - + result = SDIO_disk_write(pdrv - DEV_SDIO_MIN, buff, sector, count); return 0; // OK } @@ -140,7 +141,7 @@ DRESULT disk_ioctl ( if(pdrv >= DEV_SDIO_MIN && pdrv <= DEV_SDIO_MAX){ - + SDIO_disk_ioctl(pdrv - DEV_SDIO_MIN, cmd, buff); return 0; // OK } diff --git a/lib/FatFs/source/diskio.h b/lib/FatFs/source/diskio.h index 5ab2231..5fe2ea0 100644 --- a/lib/FatFs/source/diskio.h +++ b/lib/FatFs/source/diskio.h @@ -2,6 +2,8 @@ / Low level disk interface modlue include file (C)ChaN, 2019 / /-----------------------------------------------------------------------*/ +#include "ff.h" + #ifndef _DISKIO_DEFINED #define _DISKIO_DEFINED @@ -38,6 +40,12 @@ DRESULT USB_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); DRESULT USB_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); DRESULT USB_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); +DSTATUS SDIO_disk_initialize (BYTE pdrv); +DSTATUS SDIO_disk_status (BYTE pdrv); +DRESULT SDIO_disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); +DRESULT SDIO_disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); +DRESULT SDIO_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); + /* Disk Status Bits (DSTATUS) */ diff --git a/lib/sd_driver/SDIO/SdioCard.h b/lib/sd_driver/SDIO/SdioCard.h new file mode 100755 index 0000000..8a8ba18 --- /dev/null +++ b/lib/sd_driver/SDIO/SdioCard.h @@ -0,0 +1,214 @@ +/** + * Copyright (c) 2011-2022 Bill Greiman + * This file is part of the SdFat library for SD memory cards. + * + * MIT License + * + * 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. + */ +#ifndef sd_sdio_h +#define sd_sdio_h +#include "sd_card.h" + +/** Initialize the SD card. + * \return true for success or false for failure. + */ +bool sd_sdio_begin(sd_card_t *sd_card_p); +/** CMD6 Switch mode: Check Function Set Function. + * \param[in] arg CMD6 argument. + * \param[out] status return status data. + * + * \return true for success or false for failure. + */ +bool sd_sdio_cardCMD6(sd_card_t *sd_card_p, uint32_t arg, uint8_t *status); +/** Disable an SDIO card. + * not implemented. + */ +// void sd_sdio_end() {} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +uint32_t __attribute__((error("use sectorCount()"))) cardSize(); +#endif // DOXYGEN_SHOULD_SKIP_THIS +/** Erase a range of sectors. + * + * \param[in] firstSector The address of the first sector in the range. + * \param[in] lastSector The address of the last sector in the range. + * + * \note This function requests the SD card to do a flash erase for a + * range of sectors. The data on the card after an erase operation is + * either 0 or 1, depends on the card vendor. The card must support + * single sector erase. + * + * \return true for success or false for failure. + */ +bool sd_sdio_erase(sd_card_t *sd_card_p, uint32_t firstSector, uint32_t lastSector); +/** + * \return code for the last error. See SdCardInfo.h for a list of error codes. + */ +uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) /* const */; +/** \return error data for last error. */ +uint32_t sd_sdio_errorData() /* const */; +/** \return error line for last error. Tmp function for debug. */ +uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) /* const */; +/** + * Check for busy with CMD13. + * + * \return true if busy else false. + */ +bool sd_sdio_isBusy(sd_card_t *sd_card_p); +/** \return the SD clock frequency in kHz. */ +//uint32_t sd_sdio_kHzSdClk(); +/** + * Read a 512 byte sector from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ +bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst); +/** + * Read multiple 512 byte sectors from an SD card. + * + * \param[in] sector Logical sector to be read. + * \param[in] ns Number of sectors to be read. + * \param[out] dst Pointer to the location that will receive the data. + * \return true for success or false for failure. + */ +bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t *dst, size_t ns); +// Read 512-bit SD status +bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]); +/** Read one data sector in a multiple sector read sequence + * + * \param[out] dst Pointer to the location for the data to be read. + * + * \return true for success or false for failure. + */ +bool sd_sdio_readData(sd_card_t *sd_card_p, uint8_t *dst); +/** Read OCR register. + * + * \param[out] ocr Value of OCR register. + * \return true for success or false for failure. + */ +bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t *ocr); +/** Read SCR register. + * + * \param[out] scr Value of SCR register. + * \return true for success or false for failure. + */ +// bool sd_sdio_readSCR(sd_card_t *sd_card_p, scr_t *scr); +/** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ +// bool sd_sdio_readStart(uint32_t sector); +/** Start a read multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * \param[in] count Maximum sector count. + * \note This function is used with readData() and readStop() for optimized + * multiple sector reads. SPI chipSelect must be low for the entire sequence. + * + * \return true for success or false for failure. + */ +bool sd_sdio_readStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count); +/** End a read multiple sectors sequence. + * + * \return true for success or false for failure. + */ +bool sd_sdio_readStop(sd_card_t *sd_card_p); +/** \return SDIO card status. */ +uint32_t sd_sdio_status(sd_card_t *sd_card_p); +/** + * Determine the size of an SD flash memory card. + * + * \return The number of 512 byte data sectors in the card + * or zero if an error occurs. + */ +uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p); +/** + * Send CMD12 to stop read or write. + * + * \param[in] blocking If true, wait for command complete. + * + * \return true for success or false for failure. + */ +bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking); +/** \return success if sync successful. Not for user apps. */ +//bool sd_sdio_syncDevice(sd_card_t *sd_card_p); +/** Return the card type: SD V1, SD V2 or SDHC + * \return 0 - SD V1, 1 - SD V2, or 3 - SDHC. + */ +uint8_t sd_sdio_type(sd_card_t *sd_card_p) /* const */; +/** + * Writes a 512 byte sector to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ +bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src); +/** + * Write multiple 512 byte sectors to an SD card. + * + * \param[in] sector Logical sector to be written. + * \param[in] ns Number of sectors to be written. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ +bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t ns); +/** Write one data sector in a multiple sector write sequence. + * \param[in] src Pointer to the location of the data to be written. + * \return true for success or false for failure. + */ +bool sd_sdio_writeData(sd_card_t *sd_card_p, const uint8_t *src); +/** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ +// bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector); +/** Start a write multiple sectors sequence. + * + * \param[in] sector Address of first sector in sequence. + * \param[in] count Maximum sector count. + * \note This function is used with writeData() and writeStop() + * for optimized multiple sector writes. + * + * \return true for success or false for failure. + */ +bool sd_sdio_writeStart(sd_card_t *sd_card_p, uint32_t sector, uint32_t count); + +/** End a write multiple sectors sequence. + * + * \return true for success or false for failure. + */ +bool sd_sdio_writeStop(sd_card_t *sd_card_p); + +void sd_sdio_ctor(sd_card_t *sd_card_p); + +#endif // sd_sdio_h diff --git a/lib/sd_driver/SDIO/ZuluSCSI_platform.h b/lib/sd_driver/SDIO/ZuluSCSI_platform.h new file mode 100755 index 0000000..1356d4a --- /dev/null +++ b/lib/sd_driver/SDIO/ZuluSCSI_platform.h @@ -0,0 +1,181 @@ +// Platform-specific definitions for ZuluSCSI RP2040 hardware. + +#pragma once + +#include +// #include +// #include "ZuluSCSI_platform_gpio.h" +// #include "scsiHostPhy.h" +#include "SdioCard.h" + +#include "pico/stdlib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define delayMicroseconds sleep_us + +/* These are used in debug output and default SCSI strings */ +extern const char *g_azplatform_name; +#define PLATFORM_NAME "ZuluSCSI RP2040" +#define PLATFORM_REVISION "2.0" +#define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_SYNC_10 +#define PLATFORM_OPTIMAL_MIN_SD_WRITE_SIZE 32768 +#define PLATFORM_OPTIMAL_MAX_SD_WRITE_SIZE 65536 +#define PLATFORM_OPTIMAL_LAST_SD_WRITE_SIZE 8192 +#define SD_USE_SDIO 1 +#define PLATFORM_HAS_INITIATOR_MODE 1 + +// NOTE: The driver supports synchronous speeds higher than 10MB/s, but this +// has not been tested due to lack of fast enough SCSI adapter. +// #define PLATFORM_MAX_SCSI_SPEED S2S_CFG_SPEED_TURBO + +// Debug logging function, can be used to print to e.g. serial port. +// May get called from interrupt handlers. +void azplatform_log(const char *s); +void azplatform_emergency_log_save(); + +#if 0 +// Timing and delay functions. +// Arduino platform already provides these +// unsigned long millis(void); +void delay(unsigned long ms); + +// Short delays, can be called from interrupt mode +static inline void delay_ns(unsigned long ns) +{ + delayMicroseconds((ns + 999) / 1000); +} +#endif + +// Approximate fast delay +static inline void delay_100ns() +{ + asm volatile ("nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop \n nop"); +} + +// Initialize SD card and GPIO configuration +void azplatform_init(); + +// Initialization for main application, not used for bootloader +void azplatform_late_init(); + +// Disable the status LED +void azplatform_disable_led(void); + +// Query whether initiator mode is enabled on targets with PLATFORM_HAS_INITIATOR_MODE +bool azplatform_is_initiator_mode_enabled(); + +// Setup soft watchdog if supported +void azplatform_reset_watchdog(); + +// Set callback that will be called during data transfer to/from SD card. +// This can be used to implement simultaneous transfer to SCSI bus. +typedef void (*sd_callback_t)(uint32_t bytes_complete); +void azplatform_set_sd_callback(sd_callback_t func, const uint8_t *buffer); + +// Reprogram firmware in main program area. +#ifndef RP2040_DISABLE_BOOTLOADER +#define AZPLATFORM_BOOTLOADER_SIZE (128 * 1024) +#define AZPLATFORM_FLASH_TOTAL_SIZE (1024 * 1024) +#define AZPLATFORM_FLASH_PAGE_SIZE 4096 +bool azplatform_rewrite_flash_page(uint32_t offset, uint8_t buffer[AZPLATFORM_FLASH_PAGE_SIZE]); +void azplatform_boot_to_main_firmware(); +#endif + +// ROM drive in the unused external flash area +#ifndef RP2040_DISABLE_ROMDRIVE +#define PLATFORM_HAS_ROM_DRIVE 1 +// Check maximum available space for ROM drive in bytes +uint32_t azplatform_get_romdrive_maxsize(); + +// Read ROM drive area +bool azplatform_read_romdrive(uint8_t *dest, uint32_t start, uint32_t count); + +// Reprogram ROM drive area +#define AZPLATFORM_ROMDRIVE_PAGE_SIZE 4096 +bool azplatform_write_romdrive(const uint8_t *data, uint32_t start, uint32_t count); +#endif + +// Parity lookup tables for write and read from SCSI bus. +// These are used by macros below and the code in scsi_accel_rp2040.cpp +extern const uint16_t g_scsi_parity_lookup[256]; +extern const uint16_t g_scsi_parity_check_lookup[512]; + +// Below are GPIO access definitions that are used from scsiPhy.cpp. + +// Write a single SCSI pin. +// Example use: SCSI_OUT(ATN, 1) sets SCSI_ATN to low (active) state. +#define SCSI_OUT(pin, state) \ + *(state ? &sio_hw->gpio_clr : &sio_hw->gpio_set) = 1 << (SCSI_OUT_ ## pin) + +// Read a single SCSI pin. +// Example use: SCSI_IN(ATN), returns 1 for active low state. +#define SCSI_IN(pin) \ + ((sio_hw->gpio_in & (1 << (SCSI_IN_ ## pin))) ? 0 : 1) + +// Set pin directions for initiator vs. target mode +#define SCSI_ENABLE_INITIATOR() \ + (sio_hw->gpio_oe_set = (1 << SCSI_OUT_ACK) | \ + (1 << SCSI_OUT_ATN)), \ + (sio_hw->gpio_oe_clr = (1 << SCSI_IN_IO) | \ + (1 << SCSI_IN_CD) | \ + (1 << SCSI_IN_MSG) | \ + (1 << SCSI_IN_REQ)) + +// Enable driving of shared control pins +#define SCSI_ENABLE_CONTROL_OUT() \ + (sio_hw->gpio_oe_set = (1 << SCSI_OUT_CD) | \ + (1 << SCSI_OUT_MSG)) + +// Set SCSI data bus to output +#define SCSI_ENABLE_DATA_OUT() \ + (sio_hw->gpio_clr = (1 << SCSI_DATA_DIR), \ + sio_hw->gpio_oe_set = SCSI_IO_DATA_MASK) + +// Write SCSI data bus, also sets REQ to inactive. +#define SCSI_OUT_DATA(data) \ + gpio_put_masked(SCSI_IO_DATA_MASK | (1 << SCSI_OUT_REQ), \ + g_scsi_parity_lookup[(uint8_t)(data)] | (1 << SCSI_OUT_REQ)), \ + SCSI_ENABLE_DATA_OUT() + +// Release SCSI data bus and REQ signal +#define SCSI_RELEASE_DATA_REQ() \ + (sio_hw->gpio_oe_clr = SCSI_IO_DATA_MASK, \ + sio_hw->gpio_set = (1 << SCSI_DATA_DIR) | (1 << SCSI_OUT_REQ)) + +// Release all SCSI outputs +#define SCSI_RELEASE_OUTPUTS() \ + SCSI_RELEASE_DATA_REQ(), \ + sio_hw->gpio_oe_clr = (1 << SCSI_OUT_CD) | \ + (1 << SCSI_OUT_MSG), \ + sio_hw->gpio_set = (1 << SCSI_OUT_IO) | \ + (1 << SCSI_OUT_CD) | \ + (1 << SCSI_OUT_MSG) | \ + (1 << SCSI_OUT_RST) | \ + (1 << SCSI_OUT_BSY) | \ + (1 << SCSI_OUT_REQ) | \ + (1 << SCSI_OUT_SEL) + +// Read SCSI data bus +#define SCSI_IN_DATA() \ + (~sio_hw->gpio_in & SCSI_IO_DATA_MASK) >> SCSI_IO_SHIFT + +// SD card driver for SdFat + +#ifdef SD_USE_SDIO +struct SdioConfig; +// extern SdioConfig g_sd_sdio_config; +// #define SD_CONFIG g_sd_sdio_config +#define SD_CONFIG_CRASH g_sd_sdio_config +#else +struct SdSpiConfig; +extern SdSpiConfig g_sd_spi_config; +#define SD_CONFIG g_sd_spi_config +#define SD_CONFIG_CRASH g_sd_spi_config +#endif + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/SDIO/rp2040_sdio.c b/lib/sd_driver/SDIO/rp2040_sdio.c new file mode 100755 index 0000000..19423d8 --- /dev/null +++ b/lib/sd_driver/SDIO/rp2040_sdio.c @@ -0,0 +1,860 @@ +// Implementation of SDIO communication for RP2040 +// +// The RP2040 official work-in-progress code at +// https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card +// may be useful reference, but this is independent implementation. +// +// For official SDIO specifications, refer to: +// https://www.sdcard.org/downloads/pls/ +// "SDIO Physical Layer Simplified Specification Version 8.00" + +#include +#include +#include +// +#include "hardware/dma.h" +#include "hardware/gpio.h" +#include "hardware/pio.h" +#if !PICO_RISCV +# if PICO_RP2040 +# include "RP2040.h" +# endif +# if PICO_RP2350 +# include "RP2350.h" +# endif +#endif +// +#include "dma_interrupts.h" +#include "hw_config.h" +#include "rp2040_sdio.h" +#include "rp2040_sdio.pio.h" +#include "delays.h" +#include "sd_card.h" +#include "sd_timeouts.h" +#include "my_debug.h" +#include "util.h" +// +#include "rp2040_sdio.h" + +#define azdbg(arg1, ...) {\ + DBG_PRINTF("%s,%s:%d %s\n", __func__, __FILE__, __LINE__, arg1); \ +} + +#define STATE sd_card_p->sdio_if_p->state +#define SDIO_PIO sd_card_p->sdio_if_p->SDIO_PIO +#define SDIO_CMD_SM STATE.SDIO_CMD_SM +#define SDIO_DATA_SM STATE.SDIO_DATA_SM +#define SDIO_DMA_CH STATE.SDIO_DMA_CH +#define SDIO_DMA_CHB STATE.SDIO_DMA_CHB + +#define SDIO_CMD sd_card_p->sdio_if_p->CMD_gpio +#define SDIO_CLK sd_card_p->sdio_if_p->CLK_gpio +#define SDIO_D0 sd_card_p->sdio_if_p->D0_gpio +#define SDIO_D1 sd_card_p->sdio_if_p->D1_gpio +#define SDIO_D2 sd_card_p->sdio_if_p->D2_gpio +#define SDIO_D3 sd_card_p->sdio_if_p->D3_gpio + + +// Force everything to idle state +static sdio_status_t rp2040_sdio_stop(sd_card_t *sd_card_p); + +/******************************************************* + * Checksum algorithms + *******************************************************/ + +// Table lookup for calculating CRC-7 checksum that is used in SDIO command packets. +// Usage: +// uint8_t crc = 0; +// crc = crc7_table[crc ^ byte]; +// .. repeat for every byte .. +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 +}; + +// Calculate the CRC16 checksum for parallel 4 bit lines separately. +// When the SDIO bus operates in 4-bit mode, the CRC16 algorithm +// is applied to each line separately and generates total of +// 4 x 16 = 64 bits of checksum. +__attribute__((optimize("Ofast"))) +uint64_t sdio_crc16_4bit_checksum(uint32_t *data, uint32_t num_words) +{ + uint64_t crc = 0; + uint32_t *end = data + num_words; + while (data < end) + { + for (int unroll = 0; unroll < 4; unroll++) + { + // Each 32-bit word contains 8 bits per line. + // Reverse the bytes because SDIO protocol is big-endian. + uint32_t data_in = __builtin_bswap32(*data++); + + // Shift out 8 bits for each line + uint32_t data_out = crc >> 32; + crc <<= 32; + + // XOR outgoing data to itself with 4 bit delay + data_out ^= (data_out >> 16); + + // XOR incoming data to outgoing data with 4 bit delay + data_out ^= (data_in >> 16); + + // XOR outgoing and incoming data to accumulator at each tap + uint64_t xorred = data_out ^ data_in; + crc ^= xorred; + crc ^= xorred << (5 * 4); + crc ^= xorred << (12 * 4); + } + } + + return crc; +} + +/******************************************************* + * Basic SDIO command execution + *******************************************************/ + +static void sdio_send_command(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t response_bits) +{ + // azdbg("SDIO Command: ", (int)command, " arg ", arg); + + // Format the arguments in the way expected by the PIO code. + uint32_t word0 = + (47 << 24) | // Number of bits in command minus one + ( 1 << 22) | // Transfer direction from host to card + (command << 16) | // Command byte + (((arg >> 24) & 0xFF) << 8) | // MSB byte of argument + (((arg >> 16) & 0xFF) << 0); + + uint32_t word1 = + (((arg >> 8) & 0xFF) << 24) | + (((arg >> 0) & 0xFF) << 16) | // LSB byte of argument + ( 1 << 8); // End bit + + // Set number of bits in response minus one, or leave at 0 if no response expected + if (response_bits) + { + word1 |= ((response_bits - 1) << 0); + } + + // Calculate checksum in the order that the bytes will be transmitted (big-endian) + uint8_t crc = 0; + crc = crc7_table[crc ^ ((word0 >> 16) & 0xFF)]; + crc = crc7_table[crc ^ ((word0 >> 8) & 0xFF)]; + crc = crc7_table[crc ^ ((word0 >> 0) & 0xFF)]; + crc = crc7_table[crc ^ ((word1 >> 24) & 0xFF)]; + crc = crc7_table[crc ^ ((word1 >> 16) & 0xFF)]; + word1 |= crc << 8; + + // Transmit command + pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM); + pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word0); + pio_sm_put(SDIO_PIO, SDIO_CMD_SM, word1); +} + +sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response) +{ + sdio_send_command(sd_card_p, command, arg, response ? 48 : 0); + + // Wait for response + uint32_t start = millis(); + uint32_t wait_words = response ? 2 : 1; + while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < wait_words) + { + if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R1) + { + if (command != 8) // Don't log for missing SD card + { + azdbg("Timeout waiting for response in rp2040_sdio_command_R1(", (int)command, "), ", + "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset, + " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM), + " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM)); + EMSG_PRINTF("%s: Timeout waiting for response in rp2040_sdio_command_R1(0x%hx)\n", __func__, command); + } + + // Reset the state machine program + pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM); + pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset)); + return SDIO_ERR_RESPONSE_TIMEOUT; + } + } + + if (response) + { + // Read out response packet + uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM); + uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM); + // azdbg("SDIO R1 response: ", resp0, " ", resp1); + + // Calculate response checksum + uint8_t crc = 0; + crc = crc7_table[crc ^ ((resp0 >> 24) & 0xFF)]; + crc = crc7_table[crc ^ ((resp0 >> 16) & 0xFF)]; + crc = crc7_table[crc ^ ((resp0 >> 8) & 0xFF)]; + crc = crc7_table[crc ^ ((resp0 >> 0) & 0xFF)]; + crc = crc7_table[crc ^ ((resp1 >> 8) & 0xFF)]; + + uint8_t actual_crc = ((resp1 >> 0) & 0xFE); + if (crc != actual_crc) + { + // azdbg("rp2040_sdio_command_R1(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc); + EMSG_PRINTF("rp2040_sdio_command_R1(%d): CRC error, calculated 0x%hx, packet has 0x%hx\n", command, crc, actual_crc); + return SDIO_ERR_RESPONSE_CRC; + } + + uint8_t response_cmd = ((resp0 >> 24) & 0xFF); + if (response_cmd != command && command != 41) + { + // azdbg("rp2040_sdio_command_R1(", (int)command, "): received reply for ", (int)response_cmd); + EMSG_PRINTF("%d rp2040_sdio_command_R1(%d): received reply for %d\n", __LINE__, command, response_cmd); + return SDIO_ERR_RESPONSE_CODE; + } + + *response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF); + } + else + { + // Read out dummy marker + pio_sm_get(SDIO_PIO, SDIO_CMD_SM); + } + + return SDIO_OK; +} + +sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response) +{ + // The response is too long to fit in the PIO FIFO, so use DMA to receive it. + pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM); + uint32_t response_buf[5]; + dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH); + channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32); + channel_config_set_read_increment(&dmacfg, false); + channel_config_set_write_increment(&dmacfg, true); + channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_CMD_SM, false)); + dma_channel_configure(SDIO_DMA_CH, &dmacfg, &response_buf, &SDIO_PIO->rxf[SDIO_CMD_SM], 5, true); + + sdio_send_command(sd_card_p, command, arg, 136); + + uint32_t start = millis(); + while (dma_channel_is_busy(SDIO_DMA_CH)) + { + if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R2) + { + azdbg("Timeout waiting for response in rp2040_sdio_command_R2(", (int)command, "), ", + "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset, + " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM), + " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM)); + + // Reset the state machine program + dma_channel_abort(SDIO_DMA_CH); + pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM); + pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset)); + return SDIO_ERR_RESPONSE_TIMEOUT; + } + } + + dma_channel_abort(SDIO_DMA_CH); + + // Copy the response payload to output buffer + response[0] = ((response_buf[0] >> 16) & 0xFF); + response[1] = ((response_buf[0] >> 8) & 0xFF); + response[2] = ((response_buf[0] >> 0) & 0xFF); + response[3] = ((response_buf[1] >> 24) & 0xFF); + response[4] = ((response_buf[1] >> 16) & 0xFF); + response[5] = ((response_buf[1] >> 8) & 0xFF); + response[6] = ((response_buf[1] >> 0) & 0xFF); + response[7] = ((response_buf[2] >> 24) & 0xFF); + response[8] = ((response_buf[2] >> 16) & 0xFF); + response[9] = ((response_buf[2] >> 8) & 0xFF); + response[10] = ((response_buf[2] >> 0) & 0xFF); + response[11] = ((response_buf[3] >> 24) & 0xFF); + response[12] = ((response_buf[3] >> 16) & 0xFF); + response[13] = ((response_buf[3] >> 8) & 0xFF); + response[14] = ((response_buf[3] >> 0) & 0xFF); + response[15] = ((response_buf[4] >> 0) & 0xFF); + + // Calculate checksum of the payload + uint8_t crc = 0; + for (int i = 0; i < 15; i++) + { + crc = crc7_table[crc ^ response[i]]; + } + + uint8_t actual_crc = response[15] & 0xFE; + if (crc != actual_crc) + { + azdbg("rp2040_sdio_command_R2(", (int)command, "): CRC error, calculated ", crc, " packet has ", actual_crc); + return SDIO_ERR_RESPONSE_CRC; + } + + uint8_t response_cmd = ((response_buf[0] >> 24) & 0xFF); + if (response_cmd != 0x3F) + { + azdbg("rp2040_sdio_command_R2(", (int)command, "): Expected reply code 0x3F"); + return SDIO_ERR_RESPONSE_CODE; + } + + return SDIO_OK; +} + +sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response) +{ + sdio_send_command(sd_card_p, command, arg, 48); + + // Wait for response + uint32_t start = millis(); + while (pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM) < 2) + { + if ((uint32_t)(millis() - start) > sd_timeouts.rp2040_sdio_command_R3) + { + azdbg("Timeout waiting for response in rp2040_sdio_command_R3(", (int)command, "), ", + "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_CMD_SM) - (int)STATE.pio_cmd_clk_offset, + " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_CMD_SM), + " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_CMD_SM)); + + // Reset the state machine program + pio_sm_clear_fifos(SDIO_PIO, SDIO_CMD_SM); + pio_sm_exec(SDIO_PIO, SDIO_CMD_SM, pio_encode_jmp(STATE.pio_cmd_clk_offset)); + return SDIO_ERR_RESPONSE_TIMEOUT; + } + } + + // Read out response packet + uint32_t resp0 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM); + uint32_t resp1 = pio_sm_get(SDIO_PIO, SDIO_CMD_SM); + *response = ((resp0 & 0xFFFFFF) << 8) | ((resp1 >> 8) & 0xFF); + // azdbg("SDIO R3 response: ", resp0, " ", resp1); + + return SDIO_OK; +} + +/******************************************************* + * Data reception from SD card + *******************************************************/ + +sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size) +{ + // Buffer must be aligned + assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS); + + STATE.transfer_state = SDIO_RX; + STATE.transfer_start_time = millis(); + STATE.data_buf = (uint32_t*)buffer; + STATE.blocks_done = 0; + STATE.total_blocks = num_blocks; + STATE.blocks_checksumed = 0; + STATE.checksum_errors = 0; + + // Create DMA block descriptors to store each block of 512 bytes of data to buffer + // and then 8 bytes to STATE.received_checksums. + for (uint32_t i = 0; i < num_blocks; i++) + { + STATE.dma_blocks[i * 2].write_addr = buffer + i * block_size; + STATE.dma_blocks[i * 2].transfer_count = block_size / sizeof(uint32_t); + + STATE.dma_blocks[i * 2 + 1].write_addr = &STATE.received_checksums[i]; + STATE.dma_blocks[i * 2 + 1].transfer_count = 2; + } + STATE.dma_blocks[num_blocks * 2].write_addr = 0; + STATE.dma_blocks[num_blocks * 2].transfer_count = 0; + + // Configure first DMA channel for reading from the PIO RX fifo + dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH); + channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32); + channel_config_set_read_increment(&dmacfg, false); + channel_config_set_write_increment(&dmacfg, true); + channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false)); + channel_config_set_bswap(&dmacfg, true); + channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB); + dma_channel_configure(SDIO_DMA_CH, &dmacfg, 0, &SDIO_PIO->rxf[SDIO_DATA_SM], 0, false); + + // Configure second DMA channel for reconfiguring the first one + dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB); + channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32); + channel_config_set_read_increment(&dmacfg, true); + channel_config_set_write_increment(&dmacfg, true); + channel_config_set_ring(&dmacfg, true, 3); + dma_channel_configure(SDIO_DMA_CHB, &dmacfg, &dma_hw->ch[SDIO_DMA_CH].al1_write_addr, + STATE.dma_blocks, 2, false); + + // Initialize PIO state machine + pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_rx_offset, &STATE.pio_cfg_data_rx); + pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false); + + // Write number of nibbles to receive to Y register + pio_sm_put(SDIO_PIO, SDIO_DATA_SM, block_size * 2 + 16 - 1); + pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32)); + + // Enable RX FIFO join because we don't need the TX FIFO during transfer. + // This gives more leeway for the DMA block switching + SDIO_PIO->sm[SDIO_DATA_SM].shiftctrl |= PIO_SM0_SHIFTCTRL_FJOIN_RX_BITS; + + // Start PIO and DMA + dma_channel_start(SDIO_DMA_CHB); + pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true); + + return SDIO_OK; +} + +// Check checksums for received blocks +static void sdio_verify_rx_checksums(sd_card_t *sd_card_p, uint32_t maxcount, size_t block_size_words) +{ + while (STATE.blocks_checksumed < STATE.blocks_done && maxcount-- > 0) + { + // Calculate checksum from received data + int blockidx = STATE.blocks_checksumed++; + uint64_t checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * block_size_words, + block_size_words); + + // Convert received checksum to little-endian format + uint32_t top = __builtin_bswap32(STATE.received_checksums[blockidx].top); + uint32_t bottom = __builtin_bswap32(STATE.received_checksums[blockidx].bottom); + uint64_t expected = ((uint64_t)top << 32) | bottom; + + if (checksum != expected) + { + STATE.checksum_errors++; + if (STATE.checksum_errors == 1) + { + EMSG_PRINTF("SDIO checksum error in reception: block %d calculated 0x%llx expected 0x%llx\n", + blockidx, checksum, expected); + dump_bytes(block_size_words, (uint8_t *)STATE.data_buf + blockidx * block_size_words); + } + } + } +} + +sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words) +{ + // Was everything done when the previous rx_poll() finished? + if (STATE.blocks_done >= STATE.total_blocks) + { + STATE.transfer_state = SDIO_IDLE; + } + else + { + // Use the idle time to calculate checksums + sdio_verify_rx_checksums(sd_card_p, 4, block_size_words); + + // Check how many DMA control blocks have been consumed + uint32_t dma_ctrl_block_count = (dma_hw->ch[SDIO_DMA_CHB].read_addr - (uint32_t)&STATE.dma_blocks); + dma_ctrl_block_count /= sizeof(STATE.dma_blocks[0]); + + // Compute how many complete SDIO blocks have been transferred + // When transfer ends, dma_ctrl_block_count == STATE.total_blocks * 2 + 1 + STATE.blocks_done = (dma_ctrl_block_count - 1) / 2; + + // NOTE: When all blocks are done, rx_poll() still returns SDIO_BUSY once. + // This provides a chance to start the SCSI transfer before the last checksums + // are computed. Any checksum failures can be indicated in SCSI status after + // the data transfer has finished. + } + + if (STATE.transfer_state == SDIO_IDLE) + { + // Verify all remaining checksums. + sdio_verify_rx_checksums(sd_card_p, STATE.total_blocks, block_size_words); + + if (STATE.checksum_errors == 0) + return SDIO_OK; + else + return SDIO_ERR_DATA_CRC; + } + else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_rx_poll) + { + azdbg("rp2040_sdio_rx_poll() timeout, " + "PIO PC: ", (int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_rx_offset, + " RXF: ", (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM), + " TXF: ", (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM), + " DMA CNT: ", dma_hw->ch[SDIO_DMA_CH].al2_transfer_count); + rp2040_sdio_stop(sd_card_p); + return SDIO_ERR_DATA_TIMEOUT; + } + + return SDIO_BUSY; +} + + +/******************************************************* + * Data transmission to SD card + *******************************************************/ + +static void sdio_start_next_block_tx(sd_card_t *sd_card_p) +{ + // Initialize PIO + pio_sm_init(SDIO_PIO, SDIO_DATA_SM, STATE.pio_data_tx_offset, &STATE.pio_cfg_data_tx); + + // Configure DMA to send the data block payload (512 bytes) + dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CH); + channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32); + channel_config_set_read_increment(&dmacfg, true); + channel_config_set_write_increment(&dmacfg, false); + channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, true)); + channel_config_set_bswap(&dmacfg, true); + channel_config_set_chain_to(&dmacfg, SDIO_DMA_CHB); + dma_channel_configure(SDIO_DMA_CH, &dmacfg, + &SDIO_PIO->txf[SDIO_DATA_SM], STATE.data_buf + STATE.blocks_done * SDIO_WORDS_PER_BLOCK, + SDIO_WORDS_PER_BLOCK, false); + + // Prepare second DMA channel to send the CRC and block end marker + uint64_t crc = STATE.next_wr_block_checksum; + STATE.end_token_buf[0] = (uint32_t)(crc >> 32); + STATE.end_token_buf[1] = (uint32_t)(crc >> 0); + STATE.end_token_buf[2] = 0xFFFFFFFF; + channel_config_set_bswap(&dmacfg, false); + dma_channel_configure(SDIO_DMA_CHB, &dmacfg, + &SDIO_PIO->txf[SDIO_DATA_SM], STATE.end_token_buf, 3, false); + + // Enable IRQ to trigger when block is done + switch (sd_card_p->sdio_if_p->DMA_IRQ_num) { + case DMA_IRQ_0: + // Clear any pending interrupt service request: + dma_hw->ints0 = 1 << SDIO_DMA_CHB; + dma_channel_set_irq0_enabled(SDIO_DMA_CHB, true); + break; + case DMA_IRQ_1: + // Clear any pending interrupt service request: + dma_hw->ints1 = 1 << SDIO_DMA_CHB; + dma_channel_set_irq1_enabled(SDIO_DMA_CHB, true); + break; + default: + assert(false); + } + + // Initialize register X with nibble count and register Y with response bit count + pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 1048); + pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_x, 32)); + pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 31); + pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_out(pio_y, 32)); + + // Initialize pins to output and high + pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pins, 15)); + pio_sm_exec(SDIO_PIO, SDIO_DATA_SM, pio_encode_set(pio_pindirs, 15)); + + // Write start token and start the DMA transfer. + pio_sm_put(SDIO_PIO, SDIO_DATA_SM, 0xFFFFFFF0); + dma_channel_start(SDIO_DMA_CH); + + // Start state machine + pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, true); +} + +static void sdio_compute_next_tx_checksum(sd_card_t *sd_card_p) +{ + assert (STATE.blocks_done < STATE.total_blocks && STATE.blocks_checksumed < STATE.total_blocks); + int blockidx = STATE.blocks_checksumed++; + STATE.next_wr_block_checksum = sdio_crc16_4bit_checksum(STATE.data_buf + blockidx * SDIO_WORDS_PER_BLOCK, + SDIO_WORDS_PER_BLOCK); +} + +// Start transferring data from memory to SD card +sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks) +{ + // Buffer must be aligned + assert(((uint32_t)buffer & 3) == 0 && num_blocks <= SDIO_MAX_BLOCKS); + + STATE.transfer_state = SDIO_TX; + STATE.transfer_start_time = millis(); + STATE.data_buf = (uint32_t*)buffer; + STATE.blocks_done = 0; + STATE.total_blocks = num_blocks; + STATE.blocks_checksumed = 0; + STATE.checksum_errors = 0; + + // Compute first block checksum + sdio_compute_next_tx_checksum(sd_card_p); + + // Start first DMA transfer and PIO + sdio_start_next_block_tx(sd_card_p); + + if (STATE.blocks_checksumed < STATE.total_blocks) + { + // Precompute second block checksum + sdio_compute_next_tx_checksum(sd_card_p); + } + + return SDIO_OK; +} + +static sdio_status_t check_sdio_write_response(uint32_t card_response) +{ + // Shift card response until top bit is 0 (the start bit) + // The format of response is poorly documented in SDIO spec but refer to e.g. + // http://my-cool-projects.blogspot.com/2013/02/the-mysterious-sd-card-crc-status.html + uint32_t resp = card_response; + if (!(~resp & 0xFFFF0000)) resp <<= 16; + if (!(~resp & 0xFF000000)) resp <<= 8; + if (!(~resp & 0xF0000000)) resp <<= 4; + if (!(~resp & 0xC0000000)) resp <<= 2; + if (!(~resp & 0x80000000)) resp <<= 1; + + uint32_t wr_status = (resp >> 28) & 7; + + if (wr_status == 2) + { + return SDIO_OK; + } + else if (wr_status == 5) + { + EMSG_PRINTF("SDIO card reports write CRC error, status %lx\n", card_response); + return SDIO_ERR_WRITE_CRC; + } + else if (wr_status == 6) + { + EMSG_PRINTF("SDIO card reports write failure, status %lx\n", card_response); + return SDIO_ERR_WRITE_FAIL; + } + else + { + EMSG_PRINTF("SDIO card reports unknown write status %lx\n", card_response); + return SDIO_ERR_WRITE_FAIL; + } +} + +// When a block finishes, this IRQ handler starts the next one +void sdio_irq_handler(sd_card_t *sd_card_p) { + if (STATE.transfer_state == SDIO_TX) + { + if (!dma_channel_is_busy(SDIO_DMA_CH) && !dma_channel_is_busy(SDIO_DMA_CHB)) + { + // Main data transfer is finished now. + // When card is ready, PIO will put card response on RX fifo + STATE.transfer_state = SDIO_TX_WAIT_IDLE; + if (!pio_sm_is_rx_fifo_empty(SDIO_PIO, SDIO_DATA_SM)) + { + // Card is already idle + STATE.card_response = pio_sm_get(SDIO_PIO, SDIO_DATA_SM); + } + else + { + // Use DMA to wait for the response + dma_channel_config dmacfg = dma_channel_get_default_config(SDIO_DMA_CHB); + channel_config_set_transfer_data_size(&dmacfg, DMA_SIZE_32); + channel_config_set_read_increment(&dmacfg, false); + channel_config_set_write_increment(&dmacfg, false); + channel_config_set_dreq(&dmacfg, pio_get_dreq(SDIO_PIO, SDIO_DATA_SM, false)); + dma_channel_configure(SDIO_DMA_CHB, &dmacfg, + &STATE.card_response, &SDIO_PIO->rxf[SDIO_DATA_SM], 1, true); + } + } + } + + if (STATE.transfer_state == SDIO_TX_WAIT_IDLE) + { + if (!dma_channel_is_busy(SDIO_DMA_CHB)) + { + STATE.wr_status = check_sdio_write_response(STATE.card_response); + + if (STATE.wr_status != SDIO_OK) + { + rp2040_sdio_stop(sd_card_p); + return; + } + + STATE.blocks_done++; + if (STATE.blocks_done < STATE.total_blocks) + { + sdio_start_next_block_tx(sd_card_p); + STATE.transfer_state = SDIO_TX; + + if (STATE.blocks_checksumed < STATE.total_blocks) + { + // Precompute the CRC for next block so that it is ready when + // we want to send it. + sdio_compute_next_tx_checksum(sd_card_p); + } + } + else + { + rp2040_sdio_stop(sd_card_p); + } + } + } +} + +// Check if transmission is complete +sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete) +{ +#if !PICO_RISCV + if (SCB->ICSR & SCB_ICSR_VECTACTIVE_Msk) + { + // Verify that IRQ handler gets called even if we are in hardfault handler + sdio_irq_handler(sd_card_p); + } +#endif + + if (bytes_complete) + { + *bytes_complete = STATE.blocks_done * SDIO_BLOCK_SIZE; + } + + if (STATE.transfer_state == SDIO_IDLE) + { + rp2040_sdio_stop(sd_card_p); + return STATE.wr_status; + } + else if (millis() - STATE.transfer_start_time >= sd_timeouts.rp2040_sdio_tx_poll) + { + EMSG_PRINTF("rp2040_sdio_tx_poll() timeout\n"); + DBG_PRINTF("rp2040_sdio_tx_poll() timeout, " + "PIO PC: %d" + " RXF: %d" + " TXF: %d" + " DMA CNT: %lu\n", + (int)pio_sm_get_pc(SDIO_PIO, SDIO_DATA_SM) - (int)STATE.pio_data_tx_offset, + (int)pio_sm_get_rx_fifo_level(SDIO_PIO, SDIO_DATA_SM), + (int)pio_sm_get_tx_fifo_level(SDIO_PIO, SDIO_DATA_SM), + dma_hw->ch[SDIO_DMA_CH].al2_transfer_count + ); + rp2040_sdio_stop(sd_card_p); + return SDIO_ERR_DATA_TIMEOUT; + } + + return SDIO_BUSY; +} + +// Force everything to idle state +static sdio_status_t rp2040_sdio_stop(sd_card_t *sd_card_p) +{ + dma_channel_abort(SDIO_DMA_CH); + dma_channel_abort(SDIO_DMA_CHB); + switch (sd_card_p->sdio_if_p->DMA_IRQ_num) { + case DMA_IRQ_0: + dma_channel_set_irq0_enabled(SDIO_DMA_CHB, false); + break; + case DMA_IRQ_1: + dma_channel_set_irq1_enabled(SDIO_DMA_CHB, false); + break; + default: + myASSERT(false); + } + + pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false); + pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_DATA_SM, SDIO_D0, 4, false); + STATE.transfer_state = SDIO_IDLE; + return SDIO_OK; +} + +bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div) { + // Mark resources as being in use, unless it has been done already. + if (!STATE.resources_claimed) { + + if (!SDIO_PIO) + SDIO_PIO = pio0; // Default + if (!sd_card_p->sdio_if_p->DMA_IRQ_num) + sd_card_p->sdio_if_p->DMA_IRQ_num = DMA_IRQ_0; // Default + + // pio_sm_claim(SDIO_PIO, SDIO_CMD_SM); + // int pio_claim_unused_sm(PIO pio, bool required); + SDIO_CMD_SM = pio_claim_unused_sm(SDIO_PIO, true); + // pio_sm_claim(SDIO_PIO, SDIO_DATA_SM); + SDIO_DATA_SM = pio_claim_unused_sm(SDIO_PIO, true); + // dma_channel_claim(SDIO_DMA_CH); + SDIO_DMA_CH = dma_claim_unused_channel(true); + // dma_channel_claim(SDIO_DMA_CHB); + SDIO_DMA_CHB = dma_claim_unused_channel(true); + + /* Set up IRQ handler for when DMA completes. */ + dma_irq_add_handler(sd_card_p->sdio_if_p->DMA_IRQ_num, + sd_card_p->sdio_if_p->use_exclusive_DMA_IRQ_handler); + + STATE.resources_claimed = true; + } + + dma_channel_abort(SDIO_DMA_CH); + dma_channel_abort(SDIO_DMA_CHB); + pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, false); + pio_sm_set_enabled(SDIO_PIO, SDIO_DATA_SM, false); + + // Load PIO programs + pio_clear_instruction_memory(SDIO_PIO); + + // Command & clock state machine + STATE.pio_cmd_clk_offset = pio_add_program(SDIO_PIO, &sdio_cmd_clk_program); + pio_sm_config cfg = sdio_cmd_clk_program_get_default_config(STATE.pio_cmd_clk_offset); + sm_config_set_out_pins(&cfg, SDIO_CMD, 1); + sm_config_set_in_pins(&cfg, SDIO_CMD); + sm_config_set_set_pins(&cfg, SDIO_CMD, 1); + sm_config_set_jmp_pin(&cfg, SDIO_CMD); + sm_config_set_sideset_pins(&cfg, SDIO_CLK); + sm_config_set_out_shift(&cfg, false, true, 32); + sm_config_set_in_shift(&cfg, false, true, 32); + sm_config_set_clkdiv(&cfg, clk_div); + sm_config_set_mov_status(&cfg, STATUS_TX_LESSTHAN, 2); + + pio_sm_init(SDIO_PIO, SDIO_CMD_SM, STATE.pio_cmd_clk_offset, &cfg); + pio_sm_set_consecutive_pindirs(SDIO_PIO, SDIO_CMD_SM, SDIO_CLK, 1, true); + pio_sm_set_enabled(SDIO_PIO, SDIO_CMD_SM, true); + + // Data reception program + STATE.pio_data_rx_offset = pio_add_program(SDIO_PIO, &sdio_data_rx_program); + STATE.pio_cfg_data_rx = sdio_data_rx_program_get_default_config(STATE.pio_data_rx_offset); + sm_config_set_in_pins(&STATE.pio_cfg_data_rx, SDIO_D0); + sm_config_set_in_shift(&STATE.pio_cfg_data_rx, false, true, 32); + sm_config_set_out_shift(&STATE.pio_cfg_data_rx, false, true, 32); + sm_config_set_clkdiv(&STATE.pio_cfg_data_rx, clk_div); + + // Data transmission program + STATE.pio_data_tx_offset = pio_add_program(SDIO_PIO, &sdio_data_tx_program); + STATE.pio_cfg_data_tx = sdio_data_tx_program_get_default_config(STATE.pio_data_tx_offset); + sm_config_set_in_pins(&STATE.pio_cfg_data_tx, SDIO_D0); + sm_config_set_set_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4); + sm_config_set_out_pins(&STATE.pio_cfg_data_tx, SDIO_D0, 4); + sm_config_set_in_shift(&STATE.pio_cfg_data_tx, false, false, 32); + sm_config_set_out_shift(&STATE.pio_cfg_data_tx, false, true, 32); + sm_config_set_clkdiv(&STATE.pio_cfg_data_tx, clk_div); + + // Disable SDIO pins input synchronizer. + // This reduces input delay. + // Because the CLK is driven synchronously to CPU clock, + // there should be no metastability problems. + SDIO_PIO->input_sync_bypass |= (1 << SDIO_CLK) | (1 << SDIO_CMD) | (1 << SDIO_D0) | (1 << SDIO_D1) | (1 << SDIO_D2) | (1 << SDIO_D3); + + // Redirect GPIOs to PIO +#if PICO_SDK_VERSION_MAJOR < 2 + typedef enum gpio_function gpio_function_t; +#endif + gpio_function_t fn; + if (pio1 == SDIO_PIO) + fn = GPIO_FUNC_PIO1; + else + fn = GPIO_FUNC_PIO0; + gpio_set_function(SDIO_CMD, fn); + gpio_set_function(SDIO_CLK, fn); + gpio_set_function(SDIO_D0, fn); + gpio_set_function(SDIO_D1, fn); + gpio_set_function(SDIO_D2, fn); + gpio_set_function(SDIO_D3, fn); + + gpio_set_slew_rate(SDIO_CMD, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(SDIO_CLK, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(SDIO_D0, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(SDIO_D1, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(SDIO_D2, GPIO_SLEW_RATE_FAST); + gpio_set_slew_rate(SDIO_D3, GPIO_SLEW_RATE_FAST); + + if (sd_card_p->sdio_if_p->set_drive_strength) { + gpio_set_drive_strength(SDIO_CMD, sd_card_p->sdio_if_p->CMD_gpio_drive_strength); + gpio_set_drive_strength(SDIO_CLK, sd_card_p->sdio_if_p->CLK_gpio_drive_strength); + gpio_set_drive_strength(SDIO_D0, sd_card_p->sdio_if_p->D0_gpio_drive_strength); + gpio_set_drive_strength(SDIO_D1, sd_card_p->sdio_if_p->D1_gpio_drive_strength); + gpio_set_drive_strength(SDIO_D2, sd_card_p->sdio_if_p->D2_gpio_drive_strength); + gpio_set_drive_strength(SDIO_D3, sd_card_p->sdio_if_p->D3_gpio_drive_strength); + } + + return true; +} diff --git a/lib/sd_driver/SDIO/rp2040_sdio.h b/lib/sd_driver/SDIO/rp2040_sdio.h new file mode 100755 index 0000000..aa11e05 --- /dev/null +++ b/lib/sd_driver/SDIO/rp2040_sdio.h @@ -0,0 +1,120 @@ + +// SD card access using SDIO for RP2040 platform. +// This module contains the low-level SDIO bus implementation using +// the PIO peripheral. The high-level commands are in sd_card_sdio.cpp. + +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sd_card.h" + +//FIXME: why? +typedef struct sd_card_t sd_card_t; + +typedef +enum sdio_status_t { + SDIO_OK = 0, + SDIO_BUSY = 1, + SDIO_ERR_RESPONSE_TIMEOUT = 2, // Timed out waiting for response from card + SDIO_ERR_RESPONSE_CRC = 3, // Response CRC is wrong + SDIO_ERR_RESPONSE_CODE = 4, // Response command code does not match what was sent + SDIO_ERR_DATA_TIMEOUT = 5, // Timed out waiting for data block + SDIO_ERR_DATA_CRC = 6, // CRC for data packet is wrong + SDIO_ERR_WRITE_CRC = 7, // Card reports bad CRC for write + SDIO_ERR_WRITE_FAIL = 8, // Card reports write failure +} sdio_status_t; + +#define SDIO_BLOCK_SIZE 512 +#define SDIO_WORDS_PER_BLOCK (SDIO_BLOCK_SIZE / 4) // 128 + +// Maximum number of 512 byte blocks to transfer in one request +#define SDIO_MAX_BLOCKS 256 + +typedef enum sdio_transfer_state_t { SDIO_IDLE, SDIO_RX, SDIO_TX, SDIO_TX_WAIT_IDLE} sdio_transfer_state_t; + +typedef struct sd_sdio_if_state_t { + bool resources_claimed; + + uint32_t ocr; // Operating condition register from card + uint32_t rca; // Relative card address + int error_line; + sdio_status_t error; + uint32_t dma_buf[128]; + + int SDIO_DMA_CH; + int SDIO_DMA_CHB; + int SDIO_CMD_SM; + int SDIO_DATA_SM; + + uint32_t pio_cmd_clk_offset; + uint32_t pio_data_rx_offset; + pio_sm_config pio_cfg_data_rx; + uint32_t pio_data_tx_offset; + pio_sm_config pio_cfg_data_tx; + + sdio_transfer_state_t transfer_state; + uint32_t transfer_start_time; + uint32_t *data_buf; + uint32_t blocks_done; // Number of blocks transferred so far + uint32_t total_blocks; // Total number of blocks to transfer + uint32_t blocks_checksumed; // Number of blocks that have had CRC calculated + uint32_t checksum_errors; // Number of checksum errors detected + + // Variables for block writes + uint64_t next_wr_block_checksum; + uint32_t end_token_buf[3]; // CRC and end token for write block + sdio_status_t wr_status; + uint32_t card_response; + + // Variables for extended block writes + bool ongoing_wr_mlt_blk; + uint32_t wr_mlt_blk_cnt_sector; + + // Variables for block reads + // This is used to perform DMA into data buffers and checksum buffers separately. + struct { + void * write_addr; + uint32_t transfer_count; + } dma_blocks[SDIO_MAX_BLOCKS * 2]; + struct { + uint32_t top; + uint32_t bottom; + } received_checksums[SDIO_MAX_BLOCKS]; +} sd_sdio_if_state_t; + +// Execute a command that has 48-bit reply (response types R1, R6, R7) +// If response is NULL, does not wait for reply. +sdio_status_t rp2040_sdio_command_R1(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response); + +// Execute a command that has 136-bit reply (response type R2) +// Response buffer should have space for 16 bytes (the 128 bit payload) +sdio_status_t rp2040_sdio_command_R2(const sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint8_t *response); + +// Execute a command that has 48-bit reply but without CRC (response R3) +sdio_status_t rp2040_sdio_command_R3(sd_card_t *sd_card_p, uint8_t command, uint32_t arg, uint32_t *response); + +// Start transferring data from SD card to memory buffer +sdio_status_t rp2040_sdio_rx_start(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t num_blocks, size_t block_size); + +// Check if reception is complete +// Returns SDIO_BUSY while transferring, SDIO_OK when done and error on failure. +sdio_status_t rp2040_sdio_rx_poll(sd_card_t *sd_card_p, size_t block_size_words); + +// Start transferring data from memory to SD card +sdio_status_t rp2040_sdio_tx_start(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t num_blocks); + +// Check if transmission is complete +sdio_status_t rp2040_sdio_tx_poll(sd_card_t *sd_card_p, uint32_t *bytes_complete /* = nullptr */); + +// (Re)initialize the SDIO interface +bool rp2040_sdio_init(sd_card_t *sd_card_p, float clk_div); + +void __not_in_flash_func(sdio_irq_handler)(sd_card_t *sd_card_p); + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/SDIO/rp2040_sdio.pio b/lib/sd_driver/SDIO/rp2040_sdio.pio new file mode 100755 index 0000000..d40dcfb --- /dev/null +++ b/lib/sd_driver/SDIO/rp2040_sdio.pio @@ -0,0 +1,157 @@ +; RP2040 PIO program for implementing SD card access in SDIO mode +; Run "pioasm rp2040_sdio.pio rp2040_sdio.pio.h" to regenerate the C header from this. + +; The RP2040 official work-in-progress code at +; https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_sd_card +; may be useful reference, but this is independent implementation. +; +; For official SDIO specifications, refer to: +; https://www.sdcard.org/downloads/pls/ +; "SDIO Physical Layer Simplified Specification Version 8.00" + +; Clock settings +; For 3.3V communication the available speeds are: +; - Default speed: max. 25 MHz clock +; - High speed: max. 50 MHz clock +; +; From the default RP2040 clock speed of 125 MHz, the closest dividers +; are 3 for 41.7 MHz and 5 for 25 MHz. The CPU can apply further divider +; through state machine registers for the initial handshake. +; +; Because data is written on the falling edge and read on the rising +; edge, it is preferrable to have a long 0 state and short 1 state. +;.define CLKDIV 3 +;.define CLKDIV 5 +;.define D0 ((CLKDIV + 1) / 2 - 1) +;.define D1 (CLKDIV/2 - 1) + +.define D0 1 +.define D1 1 +.define PUBLIC CLKDIV D0 + 1 + D1 + 1 + +; .define PUBLIC SDIO_CLK_GPIO 17 + +; This is relative to D0 GPIO number. +; The pin is selected by adding Index to the +; PINCTRL_IN_BASE configuration, modulo 32. +; This is used as a WAIT index, and must be between 4 and 31. +; (Offsets 0-3 are D0, D1, D2, and D3.) +.define PUBLIC SDIO_CLK_PIN_D0_OFFSET 30 ; (-2 in mod32 arithmetic) + +; State machine 0 is used to: +; - generate continuous clock on SDIO_CLK +; - send CMD packets +; - receive response packets +; +; Pin mapping for this state machine: +; - Sideset : CLK +; - IN/OUT/SET : CMD +; - JMP_PIN : CMD +; +; The commands to send are put on TX fifo and must have two words: +; Word 0 bits 31-24: Number of bits in command minus one (usually 47) +; Word 0 bits 23-00: First 24 bits of the command packet, shifted out MSB first +; Word 1 bits 31-08: Last 24 bits of the command packet, shifted out MSB first +; Word 1 bits 07-00: Number of bits in response minus one (usually 47), or 0 if no response +; +; The response is put on RX fifo, starting with the MSB. +; Partial last word will be padded with zero bits at the top. +; +; The state machine EXECCTRL should be set so that STATUS indicates TX FIFO < 2 +; and that AUTOPULL and AUTOPUSH are enabled. + +.program sdio_cmd_clk + .side_set 1 + + mov OSR, NULL side 1 [D1] ; Make sure OSR is full of zeros to prevent autopull + +wait_cmd: + mov Y, !STATUS side 0 [D0] ; Check if TX FIFO has data + jmp !Y wait_cmd side 1 [D1] + +load_cmd: + out NULL, 32 side 0 [D0] ; Load first word (trigger autopull) + out X, 8 side 1 [D1] ; Number of bits to send + set pins, 1 side 0 [D0] ; Initial state of CMD is high + set pindirs, 1 side 1 [D1] ; Set SDIO_CMD as output + +send_cmd: + out pins, 1 side 0 [D0] ; Write output on falling edge of CLK + jmp X-- send_cmd side 1 [D1] + +prep_resp: + set pindirs, 0 side 0 [D0] ; Set SDIO_CMD as input + out X, 8 side 1 [D1] ; Get number of bits in response + nop side 0 [D0] ; For clock alignment + jmp !X resp_done side 1 [D1] ; Check if we expect a response + +wait_resp: + nop side 0 [D0] + jmp PIN wait_resp side 1 [D1] ; Loop until SDIO_CMD = 0 + + ; Note: input bits are read at the same time as we write CLK=0. + ; Because the host controls the clock, the read happens before + ; the card sees the falling clock edge. This gives maximum time + ; for the data bit to settle. +read_resp: + in PINS, 1 side 0 [D0] ; Read input data bit + jmp X-- read_resp side 1 [D1] ; Loop to receive all data bits + +resp_done: + push side 0 [D0] ; Push the remaining part of response + +; State machine 1 is used to send and receive data blocks. +; Pin mapping for this state machine: +; - IN / OUT: SDIO_D0-D3 +; - GPIO defined at beginning of this file: SDIO_CLK + +; Data reception program +; This program will wait for initial start of block token and then +; receive a data block. The application must set number of nibbles +; to receive minus 1 to Y register before running this program. +.program sdio_data_rx + +wait_start: + mov X, Y ; Reinitialize number of nibbles to receive + wait 0 pin 0 ; Wait for zero state on D0 + wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV-1] ; Wait for rising edge and then whole clock cycle + +rx_data: + in PINS, 4 [CLKDIV-2] ; Read nibble + jmp X--, rx_data + +; Data transmission program +; +; Before running this program, pindirs should be set as output +; and register X should be initialized with the number of nibbles +; to send minus 1 (typically 8 + 1024 + 16 + 1 - 1 = 1048) +; and register Y with the number of response bits minus 1 (typically 31). +; +; Words written to TX FIFO must be: +; - Word 0: start token 0xFFFFFFF0 +; - Word 1-128: transmitted data (512 bytes) +; - Word 129-130: CRC checksum +; - Word 131: end token 0xFFFFFFFF +; +; After the card reports idle status, RX FIFO will get a word that +; contains the D0 line response from card. + +.program sdio_data_tx + wait 0 pin SDIO_CLK_PIN_D0_OFFSET + wait 1 pin SDIO_CLK_PIN_D0_OFFSET [CLKDIV + D1 - 1]; Synchronize so that write occurs on falling edge + +tx_loop: + out PINS, 4 [D0] ; Write nibble and wait for whole clock cycle + jmp X-- tx_loop [D1] + + set pindirs, 0x00 [D0] ; Set data bus as input + +.wrap_target +response_loop: + in PINS, 1 [D1] ; Read D0 on rising edge + jmp Y--, response_loop [D0] + +wait_idle: + wait 1 pin 0 [D1] ; Wait for card to indicate idle condition + push [D0] ; Push the response token +.wrap \ No newline at end of file diff --git a/lib/sd_driver/SDIO/sd_card_sdio.c b/lib/sd_driver/SDIO/sd_card_sdio.c new file mode 100644 index 0000000..e137a32 --- /dev/null +++ b/lib/sd_driver/SDIO/sd_card_sdio.c @@ -0,0 +1,664 @@ +// Driver for accessing SD card in SDIO mode on RP2040. + +#include "ZuluSCSI_platform.h" + +#include +#include + +// +// Hardware +// +#include +#include +#include +#include + +// +// Platform +// +#include "pico/stdlib.h" + +// +// Project +// +#include "diskio.h" +#include "my_debug.h" +#include "delays.h" +#include "rp2040_sdio.h" +#include "rp2040_sdio.pio.h" // build\build\rp2040_sdio.pio.h +#include "sd_card_constants.h" +#include "sd_card.h" +#include "sd_timeouts.h" +#include "SdioCard.h" +#include "util.h" + +#define STATE sd_card_p->sdio_if_p->state + +static char const *errstr(sdio_status_t error) { + switch (error) { + case SDIO_OK: + return "SDIO: OK"; + case SDIO_BUSY: + return "SDIO: busy"; + case SDIO_ERR_RESPONSE_TIMEOUT: + return "SDIO: Timed out waiting for response from card"; + case SDIO_ERR_RESPONSE_CRC: + return "SDIO: Response CRC is wrong"; + case SDIO_ERR_RESPONSE_CODE: + return "SDIO: Response command code does not match what was sent"; + case SDIO_ERR_DATA_TIMEOUT: + return "SDIO: Timed out waiting for data block"; + case SDIO_ERR_DATA_CRC: + return "SDIO: CRC for data packet is wrong"; + case SDIO_ERR_WRITE_CRC: + return "SDIO: Card reports bad CRC for write"; + case SDIO_ERR_WRITE_FAIL: + return "SDIO: Card reports write failure"; + } + return "Unknown error"; +} + +//FIXME +#define azdbg(arg1, ...) {\ + DBG_PRINTF("%s,%d: %s\n", __func__, __LINE__, arg1); \ +} +#define TRACE_PRINTF(fmt, args...) +//#define TRACE_PRINTF DBG_PRINTF + +#define checkReturnOk(call) ((STATE.error = (call)) == SDIO_OK ? true : logSDError(sd_card_p, __LINE__)) + +static bool logSDError(sd_card_t *sd_card_p, int line) +{ + STATE.error_line = line; + EMSG_PRINTF("%s at line %d; error code %d\n", + errstr(STATE.error), line, (int)STATE.error); + return false; +} + +/* + CLKDIV is from sd_driver\SDIO\rp2040_sdio.pio + + baud = clk_sys / (CLKDIV * clk_div) + baud * CLKDIV * clk_div = clk_sys; + clk_div = clk_sys / (CLKDIV * baud) +*/ +static float calculate_clk_div(uint baud) { + float div = (float)clock_get_hz(clk_sys) / (CLKDIV * baud); + /* Baud rate cannot exceed clk_sys frequency divided by CLKDIV! */ + DBG_PRINTF("clk_div = %f\n", div); + myASSERT(div >= 1 && div <= 65536); + return div; +} + +bool sd_sdio_begin(sd_card_t *sd_card_p) +{ + uint32_t reply; + sdio_status_t status; + + // Initialize at 400 kHz clock speed + if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000))) + return false; + + // Establish initial connection with the card + for (int retries = 0; retries < 5; retries++) + { + delay_ms(1); + reply = 0; + rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE + status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND + + if (status == SDIO_OK && reply == 0x1AA) + { + break; + } + } + + if (reply != 0x1AA || status != SDIO_OK) + { + // azdbg("SDIO not responding to CMD8 SEND_IF_COND, status ", (int)status, " reply ", reply); + EMSG_PRINTF("%s,%d SDIO not responding to CMD8 SEND_IF_COND, status 0x%x reply 0x%lx\n", + __func__, __LINE__, status, reply); + return false; + } + + // Send ACMD41 to begin card initialization and wait for it to complete + uint32_t start = millis(); + do { + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, 0, &reply)) || // APP_CMD + !checkReturnOk(rp2040_sdio_command_R3(sd_card_p, ACMD41_SD_SEND_OP_COND, 0xD0040000, &STATE.ocr))) // 3.0V voltage + // !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD41, 0xC0100000, &STATE.ocr))) + { + return false; + } + + if ((uint32_t)(millis() - start) > sd_timeouts.sd_sdio_begin) + { + EMSG_PRINTF("SDIO card initialization timeout\n"); + return false; + } + } while (!(STATE.ocr & (1 << 31))); + + // Get CID + // CMD2 is valid only in "ready" state; + // Transitions to "ident" state + // Note: CMD10 is valid only in "stby" state + if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD2_ALL_SEND_CID, 0, (uint8_t *)&sd_card_p->state.CID))) + { + azdbg("SDIO failed to read CID"); + return false; + } + + // Get relative card address + // Valid in "ident" or "stby" state; transitions to "stby" + // Transitions from "card-identification-mode" to "data-transfer-mode" + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD3_SEND_RELATIVE_ADDR, 0, &STATE.rca))) + { + azdbg("SDIO failed to get RCA"); + return false; + } + + // Get CSD + // Valid in "stby" state; stays in "stby" state + if (!checkReturnOk(rp2040_sdio_command_R2(sd_card_p, CMD9_SEND_CSD, STATE.rca, sd_card_p->state.CSD))) + { + azdbg("SDIO failed to read CSD"); + return false; + } + sd_card_p->state.sectors = CSD_sectors(sd_card_p->state.CSD); + + // Select card + // Valid in "stby" state; + // If card is addressed, transitions to "tran" state + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD7_SELECT_CARD, STATE.rca, &reply))) + { + azdbg("SDIO failed to select card"); + return false; + } + + /* At power up CD/DAT3 has a 50KOhm pull up enabled in the card. + This resistor serves two functions Card detection and Mode Selection. + For Mode Selection, the host can drive the line high or let it be pulled high to select SD mode. + If the host wants to select SPI mode it should drive the line low. + For Card detection, the host detects that the line is pulled high. + This pull-up should be disconnected by the user, during regular data transfer, + with SET_CLR_CARD_DETECT (ACMD42) command. */ + // Disconnect the 50 KOhm pull-up resistor on CD/DAT3 + // Valid in "tran" state; stays in "tran" state + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) || + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, &reply))) + { + azdbg("SDIO failed to disconnect pull-up"); + return false; + } + + // Set 4-bit bus mode + // Valid in "tran" state; stays in "tran" state + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) || + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD6_SET_BUS_WIDTH, 2, &reply))) + { + azdbg("SDIO failed to set bus width"); + return false; + } + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply))) // SET_BLOCKLEN + { + EMSG_PRINTF("%s,%d SDIO failed to set BLOCKLEN\n", __func__, __LINE__); + return false; + } + // Increase to high clock rate + if (!sd_card_p->sdio_if_p->baud_rate) + sd_card_p->sdio_if_p->baud_rate = clock_get_hz(clk_sys) / 12; // Default + if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(sd_card_p->sdio_if_p->baud_rate))) + return false; + + return true; +} + +uint8_t sd_sdio_errorCode(sd_card_t *sd_card_p) // const +{ + return STATE.error; +} + +uint32_t sd_sdio_errorData() // const +{ + return 0; +} + +uint32_t sd_sdio_errorLine(sd_card_t *sd_card_p) // const +{ + return STATE.error_line; +} + +bool sd_sdio_isBusy(sd_card_t *sd_card_p) +{ + // return (sio_hw->gpio_in & (1 << SDIO_D0)) == 0; + return (sio_hw->gpio_in & (1 << sd_card_p->sdio_if_p->D0_gpio)) == 0; +} + +bool sd_sdio_readOCR(sd_card_t *sd_card_p, uint32_t* ocr) +{ + // SDIO mode does not have CMD58, but main program uses this to + // poll for card presence. Return status register instead. + return checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, ocr)); +} + +uint32_t sd_sdio_status(sd_card_t *sd_card_p) +{ + uint32_t reply; + if (checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply))) + return reply; + else + return 0; +} + +bool sd_sdio_stopTransmission(sd_card_t *sd_card_p, bool blocking) +{ + + STATE.ongoing_wr_mlt_blk = false; + + uint32_t reply; + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD12_STOP_TRANSMISSION, 0, &reply))) + { + return false; + } + + if (!blocking) + { + return true; + } + else + { + uint32_t start = millis(); + while (millis() - start < 200 && sd_sdio_isBusy(sd_card_p)); + if (sd_sdio_isBusy(sd_card_p)) + { + EMSG_PRINTF("sd_sdio_stopTransmission() timeout\n"); + return false; + } + else + { + return true; + } + } +} + +uint8_t sd_sdio_type(sd_card_t *sd_card_p) // const +{ + if (STATE.ocr & (1 << 30)) + return SDCARD_V2HC; + else + return SDCARD_V2; +} + + +/* Writing and reading */ + +bool sd_sdio_writeSector(sd_card_t *sd_card_p, uint32_t sector, const uint8_t* src) +{ + if (STATE.ongoing_wr_mlt_blk) + // Stop any ongoing write transmission + if (!sd_sdio_stopTransmission(sd_card_p, true)) return false; + + if (((uint32_t)src & 3) != 0) { + // Buffer is not aligned, need to memcpy() the data to a temporary buffer. + memcpy(STATE.dma_buf, src, sizeof(STATE.dma_buf)); + src = (uint8_t*)STATE.dma_buf; + } + + uint32_t reply; + if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */ + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD24_WRITE_BLOCK, sector, &reply)) || // WRITE_BLOCK + !checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, 1))) // Start transmission + { + return false; + } + + do { + uint32_t bytes_done; + STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done); + } while (STATE.error == SDIO_BUSY); + + if (STATE.error != SDIO_OK) + { + EMSG_PRINTF("sd_sdio_writeSector(%lu) failed: %s (%d)\n", + sector, errstr(STATE.error), (int)STATE.error); + } + + return STATE.error == SDIO_OK; +} + +bool sd_sdio_writeSectors(sd_card_t *sd_card_p, uint32_t sector, const uint8_t *src, size_t n) { + if (((uint32_t)src & 3) != 0) { + // Unaligned write, execute sector-by-sector + for (size_t i = 0; i < n; i++) { + if (!sd_sdio_writeSector(sd_card_p, sector + i, src + 512 * i)) { + return false; + } + } + return true; + } + if (STATE.ongoing_wr_mlt_blk && sector == STATE.wr_mlt_blk_cnt_sector) { + /* Continue a multiblock write */ + if (!checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission + return false; + } else { + // Stop any previous transmission + if (STATE.ongoing_wr_mlt_blk) { + if (!sd_sdio_stopTransmission(sd_card_p, true)) return false; + } + uint32_t reply; + if (!checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, sector, &reply)) || + !checkReturnOk(rp2040_sdio_tx_start(sd_card_p, src, n))) // Start transmission + { + return false; + } + } + + do { + uint32_t bytes_done; + STATE.error = rp2040_sdio_tx_poll(sd_card_p, &bytes_done); + } while (STATE.error == SDIO_BUSY); + + if (STATE.error != SDIO_OK) { + EMSG_PRINTF("sd_sdio_writeSectors(,%lu,,%zu) failed: %s (%d)\n", sector, n, errstr(STATE.error), (int)STATE.error); + sd_sdio_stopTransmission(sd_card_p, true); + return false; + } else { + STATE.wr_mlt_blk_cnt_sector = sector + n; + STATE.ongoing_wr_mlt_blk = true; + return true; + } + /* Optimization: + To optimize large contiguous writes, + postpone stopping transmission until it is + clear that the next operation is not a continuation. + + Any transactions other than a `sd_sdio_writeSectors` + continuation must stop any ongoing transmission + before proceding. + */ +} + +bool sd_sdio_readSector(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst) +{ + if (STATE.ongoing_wr_mlt_blk) + // Stop any ongoing transmission + if (!sd_sdio_stopTransmission(sd_card_p, true)) return false; + + uint8_t *real_dst = dst; + if (((uint32_t)dst & 3) != 0) + { + // Buffer is not aligned, need to memcpy() the data from a temporary buffer. + dst = (uint8_t*)STATE.dma_buf; + } + uint32_t reply; + if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */ + !checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, 1, SDIO_BLOCK_SIZE)) || // Prepare for reception + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD17_READ_SINGLE_BLOCK, sector, &reply))) // READ_SINGLE_BLOCK + { + return false; + } + + do { + STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK); + } while (STATE.error == SDIO_BUSY); + + if (STATE.error != SDIO_OK) + { + EMSG_PRINTF("sd_sdio_readSector(,%lu,) failed: %s (%d)\n", + sector, errstr(STATE.error), (int)STATE.error); + } + + if (dst != real_dst) + { + memcpy(real_dst, STATE.dma_buf, sizeof(STATE.dma_buf)); + } + + return STATE.error == SDIO_OK; +} + +bool sd_sdio_readSectors(sd_card_t *sd_card_p, uint32_t sector, uint8_t* dst, size_t n) +{ + if (STATE.ongoing_wr_mlt_blk) + // Stop any ongoing transmission + if (!sd_sdio_stopTransmission(sd_card_p, true)) return false; + + if (((uint32_t)dst & 3) != 0 || sector + n >= sd_card_p->state.sectors) + { + // Unaligned read or end-of-drive read, execute sector-by-sector + for (size_t i = 0; i < n; i++) + { + if (!sd_sdio_readSector(sd_card_p, sector + i, dst + 512 * i)) + { + return false; + } + } + return true; + } + + uint32_t reply; + if (/* !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, 16, 512, &reply)) || // SET_BLOCKLEN */ + !checkReturnOk(rp2040_sdio_rx_start(sd_card_p, dst, n, SDIO_BLOCK_SIZE)) || // Prepare for reception + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, sector, &reply))) // READ_MULTIPLE_BLOCK + { + return false; + } + + do { + STATE.error = rp2040_sdio_rx_poll(sd_card_p, SDIO_WORDS_PER_BLOCK); + } while (STATE.error == SDIO_BUSY); + + if (STATE.error != SDIO_OK) + { + EMSG_PRINTF("sd_sdio_readSectors(%ld,...,%d) failed: %s (%d)\n", + sector, n, errstr(STATE.error), STATE.error); + sd_sdio_stopTransmission(sd_card_p, true); + return false; + } + else + { + return sd_sdio_stopTransmission(sd_card_p, true); + } +} + +// Get 512 bit (64 byte) SD Status +bool rp2040_sdio_get_sd_status(sd_card_t *sd_card_p, uint8_t response[64]) { + uint32_t reply; + if (!checkReturnOk(rp2040_sdio_rx_start(sd_card_p, response, 1, 64)) || // Prepare for reception + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, CMD55_APP_CMD, STATE.rca, &reply)) || // APP_CMD + !checkReturnOk(rp2040_sdio_command_R1(sd_card_p, ACMD13_SD_STATUS, 0, &reply))) // SD Status + { + EMSG_PRINTF("ACMD13 failed\n"); + return false; + } + // Read 512 bit block on DAT bus (not CMD) + do { + STATE.error = rp2040_sdio_rx_poll(sd_card_p, 64 / 4); + } while (STATE.error == SDIO_BUSY); + + if (STATE.error != SDIO_OK) + { + EMSG_PRINTF("ACMD13 failed: %s (%d)\n", errstr(STATE.error), (int)STATE.error); + } + return STATE.error == SDIO_OK; + +} + +static bool sd_sdio_test_com(sd_card_t *sd_card_p) { + bool success = false; + + if (!(sd_card_p->state.m_Status & STA_NOINIT)) { + // SD card is currently initialized + + // Get status + uint32_t reply = 0; + sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD13_SEND_STATUS, STATE.rca, &reply); + + // Only care that communication succeeded + success = (status == SDIO_OK); + + if (!success) { + // Card no longer sensed - ensure card is initialized once re-attached + sd_card_p->state.m_Status |= STA_NOINIT; + } + } else { + // Do a "light" version of init, just enough to test com + + // Initialize at 400 kHz clock speed + if (!rp2040_sdio_init(sd_card_p, calculate_clk_div(400 * 1000))) + return false; + + // Establish initial connection with the card + rp2040_sdio_command_R1(sd_card_p, CMD0_GO_IDLE_STATE, 0, NULL); // GO_IDLE_STATE + uint32_t reply = 0; + sdio_status_t status = rp2040_sdio_command_R1(sd_card_p, CMD8_SEND_IF_COND, 0x1AA, &reply); // SEND_IF_COND + + success = (reply == 0x1AA && status == SDIO_OK); + } + + return success; +} + +#if PICO_SDK_VERSION_MAJOR < 2 + typedef enum gpio_function gpio_function_t; +#endif + +// Helper function to configure whole GPIO in one line +static void gpio_conf(uint gpio, gpio_function_t fn, bool pullup, bool pulldown, bool output, bool initial_state) +{ + gpio_put(gpio, initial_state); + gpio_set_dir(gpio, output); + gpio_set_pulls(gpio, pullup, pulldown); + gpio_set_function(gpio, fn); + + // See rp2040_sdio_init +} + +static DSTATUS sd_sdio_init(sd_card_t *sd_card_p) { + sd_lock(sd_card_p); + + // Make sure there's a card in the socket before proceeding + sd_card_detect(sd_card_p); + if (sd_card_p->state.m_Status & STA_NODISK) { + sd_unlock(sd_card_p); + return sd_card_p->state.m_Status; + } + // Make sure we're not already initialized before proceeding + if (!(sd_card_p->state.m_Status & STA_NOINIT)) { + sd_unlock(sd_card_p); + return sd_card_p->state.m_Status; + } + // Initialize the member variables + sd_card_p->state.card_type = SDCARD_NONE; + + // pin function pup pdown out state + gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_PIO1, true, false, true, true); + gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_PIO1, true, false, true, true); + gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_PIO1, true, false, false, true); + gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_PIO1, true, false, false, true); + gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_PIO1, true, false, false, true); + gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_PIO1, true, false, false, true); + + bool ok = sd_sdio_begin(sd_card_p); + if (ok) { + // The card is now initialized + sd_card_p->state.m_Status &= ~STA_NOINIT; + } + sd_unlock(sd_card_p); + return sd_card_p->state.m_Status; +} +static void sd_sdio_deinit(sd_card_t *sd_card_p) { + sd_lock(sd_card_p); + + sd_card_p->state.m_Status |= STA_NOINIT; + sd_card_p->state.card_type = SDCARD_NONE; + + // pin function pup pdown out state + gpio_conf(sd_card_p->sdio_if_p->CLK_gpio, GPIO_FUNC_NULL, false, false, false, false); + gpio_conf(sd_card_p->sdio_if_p->CMD_gpio, GPIO_FUNC_NULL, false, false, false, false); + gpio_conf(sd_card_p->sdio_if_p->D0_gpio, GPIO_FUNC_NULL, false, false, false, false); + gpio_conf(sd_card_p->sdio_if_p->D1_gpio, GPIO_FUNC_NULL, false, false, false, false); + gpio_conf(sd_card_p->sdio_if_p->D2_gpio, GPIO_FUNC_NULL, false, false, false, false); + gpio_conf(sd_card_p->sdio_if_p->D3_gpio, GPIO_FUNC_NULL, false, false, false, false); + + //TODO: free other resources: PIO, SMs, etc. + + sd_unlock(sd_card_p); +} + +uint32_t sd_sdio_sectorCount(sd_card_t *sd_card_p) { + myASSERT(!(sd_card_p->state.m_Status & STA_NOINIT)); + return CSD_sectors(sd_card_p->state.CSD); +} + +static block_dev_err_t sd_sdio_write_blocks(sd_card_t *sd_card_p, const uint8_t *buffer, uint32_t ulSectorNumber, + uint32_t blockCnt) { + TRACE_PRINTF("%s(,,,%zu)\n", __func__, blockCnt); + bool ok = true; + + sd_lock(sd_card_p); + + if (1 == blockCnt) + ok = sd_sdio_writeSector(sd_card_p, ulSectorNumber, buffer); + else + ok = sd_sdio_writeSectors(sd_card_p, ulSectorNumber, buffer, blockCnt); + + sd_unlock(sd_card_p); + + if (ok) + return SD_BLOCK_DEVICE_ERROR_NONE; + else + return SD_BLOCK_DEVICE_ERROR_WRITE; +} +static block_dev_err_t sd_sdio_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t ulSectorNumber, + uint32_t ulSectorCount) { + bool ok = true; + + sd_lock(sd_card_p); + + if (1 == ulSectorCount) + ok = sd_sdio_readSector(sd_card_p, ulSectorNumber, buffer); + else + ok = sd_sdio_readSectors(sd_card_p, ulSectorNumber, buffer, ulSectorCount); + + sd_unlock(sd_card_p); + + if (ok) + return SD_BLOCK_DEVICE_ERROR_NONE; + else + return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; +} +static block_dev_err_t sd_sync(sd_card_t *sd_card_p) { + sd_lock(sd_card_p); + block_dev_err_t err = SD_BLOCK_DEVICE_ERROR_NONE; + if (STATE.ongoing_wr_mlt_blk) + if (!sd_sdio_stopTransmission(sd_card_p, true)) + err = SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + sd_unlock(sd_card_p); + return err; +} +void sd_sdio_ctor(sd_card_t *sd_card_p) { + myASSERT(sd_card_p->sdio_if_p); // Must have an interface object + /* + Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio. + The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio. + */ + myASSERT(!sd_card_p->sdio_if_p->CLK_gpio); + myASSERT(!sd_card_p->sdio_if_p->D1_gpio); + myASSERT(!sd_card_p->sdio_if_p->D2_gpio); + myASSERT(!sd_card_p->sdio_if_p->D3_gpio); + + sd_card_p->sdio_if_p->CLK_gpio = (sd_card_p->sdio_if_p->D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32; + sd_card_p->sdio_if_p->D1_gpio = sd_card_p->sdio_if_p->D0_gpio + 1; + sd_card_p->sdio_if_p->D2_gpio = sd_card_p->sdio_if_p->D0_gpio + 2; + sd_card_p->sdio_if_p->D3_gpio = sd_card_p->sdio_if_p->D0_gpio + 3; + + sd_card_p->state.m_Status = STA_NOINIT; + + sd_card_p->init = sd_sdio_init; + sd_card_p->deinit = sd_sdio_deinit; + sd_card_p->write_blocks = sd_sdio_write_blocks; + sd_card_p->read_blocks = sd_sdio_read_blocks; + sd_card_p->sync = sd_sync; + sd_card_p->get_num_sectors = sd_sdio_sectorCount; + sd_card_p->sd_test_com = sd_sdio_test_com; +} diff --git a/lib/sd_driver/SPI/my_spi.c b/lib/sd_driver/SPI/my_spi.c new file mode 100755 index 0000000..64d97de --- /dev/null +++ b/lib/sd_driver/SPI/my_spi.c @@ -0,0 +1,371 @@ +/* spi.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#include +#include +#include +// +#include "hardware/clocks.h" +#include "hardware/spi.h" +#include "hardware/structs/clocks.h" +//#include "hardware/structs/dma_debug.h" +#include "pico.h" +#include "pico/mutex.h" +#include "pico/platform.h" +#include "pico/stdlib.h" +// +#include "delays.h" +#include "hw_config.h" +#include "my_debug.h" +#include "util.h" +// +#include "my_spi.h" + +#ifndef USE_DBG_PRINTF +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +static bool chk_spi(spi_t *spi_p) { + spi_inst_t *hw_spi = spi_p->hw_inst; + bool ok = true; + if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_BSY_BITS) { + DBG_PRINTF("SPI is busy\n"); + ok = false; + } + if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RFF_BITS) { + DBG_PRINTF("SPI Receive FIFO full\n"); + ok = false; + } + if (spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_RNE_BITS) { + DBG_PRINTF("SPI Receive FIFO not empty\n"); + ok = false; + } + if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TNF_BITS)) { + DBG_PRINTF("SPI Transmit FIFO is full\n"); + ok = false; + } + if (!(spi_get_const_hw(hw_spi)->sr & SPI_SSPSR_TFE_BITS)) { + DBG_PRINTF("SPI Transmit FIFO is not empty\n"); + ok = false; + } + return ok; +} + +static bool chk_dma(uint chn) { + dma_channel_hw_t *channel = &dma_hw->ch[chn]; + bool ok = true; + uint32_t ctrl = channel->ctrl_trig; + if (ctrl & DMA_CH0_CTRL_TRIG_AHB_ERROR_BITS) { + DBG_PRINTF("\tDMA bus error\n"); + ok = false; + } + if (ctrl & DMA_CH0_CTRL_TRIG_READ_ERROR_BITS) { + DBG_PRINTF("\tDMA read error\n"); + ok = false; + } + if (ctrl & DMA_CH0_CTRL_TRIG_WRITE_ERROR_BITS) { + DBG_PRINTF("\tDMA write error\n"); + ok = false; + } + if (ctrl & DMA_CH0_CTRL_TRIG_BUSY_BITS) { + DBG_PRINTF("\tDMA is busy\n"); + ok = false; + } + //if (!ok) { + // dma_debug_channel_hw_t *dbg_ch_p = &dma_debug_hw->ch[chn]; + // DBG_PRINTF("\tTRANSFER_COUNT: %lu\n", channel->transfer_count); + // DBG_PRINTF("\tTRANS_COUNT reload value (DBG_TCR): %lu\n", dbg_ch_p->dbg_tcr); + // DBG_PRINTF("\tDREQ counter: %lu\n", dbg_ch_p->dbg_ctdreq); + //} + return ok; +} + +static bool chk_dmas(spi_t *spi_p) { + bool tx_ok = chk_dma(spi_p->tx_dma); + if (!tx_ok) DBG_PRINTF("TX DMA error\n"); + bool rx_ok = chk_dma(spi_p->rx_dma); + if (!rx_ok) DBG_PRINTF("RX DMA error\n"); + return tx_ok && rx_ok; +} + +/** + * @brief Start a SPI transfer by configuring and starting the DMA channels. + * + * @param spi_p Pointer to the SPI object. + * @param tx Pointer to the transmit buffer. If NULL, data will be filled with SPI_FILL_CHAR. + * @param rx Pointer to the receive buffer. If NULL, data will be ignored. + * @param length Length of the transfer. + */ +void spi_transfer_start(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length) { + myASSERT(spi_p); + myASSERT(tx || rx); + + // tx write increment is already false + if (tx) { + channel_config_set_read_increment(&spi_p->tx_dma_cfg, true); + } else { + static const uint8_t dummy __attribute__((section(".time_critical."))) = SPI_FILL_CHAR; + tx = &dummy; + channel_config_set_read_increment(&spi_p->tx_dma_cfg, false); + } + // rx read increment is already false + if (rx) { + channel_config_set_write_increment(&spi_p->rx_dma_cfg, true); + } else { + static uint8_t dummy = 0xA5; + rx = &dummy; + channel_config_set_write_increment(&spi_p->rx_dma_cfg, false); + } + + dma_channel_configure(spi_p->tx_dma, &spi_p->tx_dma_cfg, + &spi_get_hw(spi_p->hw_inst)->dr, // write address + tx, // read address + length, // element count (each element is of + // size transfer_data_size) + false); // start + dma_channel_configure(spi_p->rx_dma, &spi_p->rx_dma_cfg, + rx, // write address + &spi_get_hw(spi_p->hw_inst)->dr, // read address + length, // element count (each element is of + // size transfer_data_size) + false); // start + + myASSERT(chk_dmas(spi_p)); + myASSERT(chk_spi(spi_p)); + + // Start the DMA channels: + // start them exactly simultaneously to avoid races (in extreme cases + // the FIFO could overflow) + dma_start_channel_mask((1u << spi_p->tx_dma) | (1u << spi_p->rx_dma)); +} + +/** + * Calculate the time in milliseconds to transfer the given number of blocks + * over the SPI bus at the given baud rate. + * @param block_count The number of blocks to transfer, each 512 bytes. + * @param spi_p Pointer to the SPI object. + * @return The time in milliseconds to transfer the given number of blocks. + */ +uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes) { + // Calculate the total number of bits to transfer + uint32_t total_bits = bytes * 8; + + // Get the baud rate from the SPI interface + uint32_t baud_rate = spi_get_baudrate(spi_p->hw_inst); + + // Calculate the time to transfer all bits in seconds + float transfer_time_sec = (double)total_bits / baud_rate; + + // Convert the time to milliseconds + float transfer_time_ms = transfer_time_sec * 1000; + + transfer_time_ms *= 1.5f; // Add 50% for overhead + transfer_time_ms += 4.0f; // For fixed overhead + + return (uint32_t)transfer_time_ms; +} + +/** + * @brief Wait until SPI transfer is complete. + * @details This function waits until the SPI master completes the transfer + * or a timeout has occurred. The timeout is specified in milliseconds. + * If the timeout is reached the function will return false. + * This function uses busy waiting to check for completion of the transfer. + * + * @param spi_p The SPI configuration. + * @param timeout_ms The timeout in milliseconds. + * @return true if the transfer is complete, false if the timeout is reached. + */ +bool __not_in_flash_func(spi_transfer_wait_complete)(spi_t *spi_p, uint32_t timeout_ms) { + myASSERT(spi_p); + bool timed_out = false; + + // Record the start time in milliseconds + uint32_t start = millis(); + + // Wait until DMA channels are not busy or timeout is reached + while ((dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma)) && + millis() - start < timeout_ms) + tight_loop_contents(); + + // Check if the DMA channels are still busy + timed_out = dma_channel_is_busy(spi_p->rx_dma) || dma_channel_is_busy(spi_p->tx_dma); + + // Print debug information if the DMA channels are still busy + if (timed_out) { + DBG_PRINTF("DMA busy wait timed out in %s\n", __FUNCTION__); + } else { + // If the DMA channels are not busy, wait for the SPI peripheral to become idle + start = millis(); + while (spi_is_busy(spi_p->hw_inst) && millis() - start < timeout_ms) + tight_loop_contents(); + + // Check if the SPI peripheral is still busy + timed_out = spi_is_busy(spi_p->hw_inst); + + // Print debug information if the SPI peripheral is still busy + if (timed_out) { + DBG_PRINTF("SPI busy wait timed out in %s\n", __FUNCTION__); + } + } + + // Check the status of the SPI peripheral + bool spi_ok = chk_spi(spi_p); + + if (timed_out || !spi_ok) { + chk_dmas(spi_p); + DBG_PRINTF("DMA_INTR: 0b%s\n", uint_binary_str(dma_hw->intr)); + DBG_PRINTF("TX DMA CTRL_TRIG: 0b%s\n", + uint_binary_str(dma_hw->ch[spi_p->tx_dma].ctrl_trig)); + DBG_PRINTF("RX DMA CTRL_TRIG: 0b%s\n", + uint_binary_str(dma_hw->ch[spi_p->rx_dma].ctrl_trig)); + DBG_PRINTF("SPI SSPCR0: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr0)); + DBG_PRINTF("SPI SSPCR1: 0b%s\n", uint_binary_str(spi_get_hw(spi_p->hw_inst)->cr1)); + DBG_PRINTF("SPI_SSPSR: 0b%s\n", uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->sr)); + DBG_PRINTF("SPI_SSPDMACR: 0b%s\n", + uint_binary_str(spi_get_const_hw(spi_p->hw_inst)->dmacr)); + + dma_channel_abort(spi_p->rx_dma); + dma_channel_abort(spi_p->tx_dma); + } + // Return true if the transfer is complete and the SPI peripheral is in a good state + return !(timed_out || !spi_ok); +} + +/** + * SPI Transfer: Read & Write (simultaneously) on SPI bus + * @param spi_p Pointer to the SPI object. + * @param tx Pointer to the transmit buffer. If NULL, SPI_FILL_CHAR is sent as each data + * element. + * @param rx Pointer to the receive buffer. If NULL, data is ignored. + * @param length Number of data elements to transfer. + * @return true if the transfer is completed successfully within the timeout. + * @return false if the transfer times out or encounters an error. + */ +bool __not_in_flash_func(spi_transfer)(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, + size_t length) { + spi_transfer_start(spi_p, tx, rx, length); + // Related to timeouts in spi_lock and sd_lock + uint32_t timeout = calculate_transfer_time_ms(spi_p, length); + return spi_transfer_wait_complete(spi_p, timeout); +} + +/** + * @brief Initialize the SPI peripheral and DMA channels. + * + * @param spi_p Pointer to the SPI object. + * @return true if the initialization is successful, false otherwise. + */ +bool my_spi_init(spi_t *spi_p) { + auto_init_mutex(my_spi_init_mutex); + mutex_enter_blocking(&my_spi_init_mutex); + if (!spi_p->initialized) { + //// The SPI may be shared (using multiple SSs); protect it + if (!mutex_is_initialized(&spi_p->mutex)) mutex_init(&spi_p->mutex); + spi_lock(spi_p); + + // Defaults: + if (!spi_p->hw_inst) spi_p->hw_inst = spi0; + if (!spi_p->baud_rate) spi_p->baud_rate = clock_get_hz(clk_sys) / 12; + + /* Configure component */ + // Enable SPI at 100 kHz and connect to GPIOs + spi_init(spi_p->hw_inst, 100 * 1000); + + myASSERT(spi_p->spi_mode < 4); + switch (spi_p->spi_mode) { + case 0: + spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + break; + case 1: + spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST); + break; + case 2: + spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_0, SPI_MSB_FIRST); + break; + case 3: + spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + break; + default: + spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + break; + } + gpio_set_function(spi_p->miso_gpio, GPIO_FUNC_SPI); + gpio_set_function(spi_p->mosi_gpio, GPIO_FUNC_SPI); + gpio_set_function(spi_p->sck_gpio, GPIO_FUNC_SPI); + // ss_gpio is initialized in sd_spi_ctor() + + // Slew rate limiting levels for GPIO outputs. + // enum gpio_slew_rate { GPIO_SLEW_RATE_SLOW = 0, GPIO_SLEW_RATE_FAST = 1 } + // void gpio_set_slew_rate (uint gpio,enum gpio_slew_rate slew) + // Default appears to be GPIO_SLEW_RATE_SLOW. + gpio_set_slew_rate(spi_p->sck_gpio, GPIO_SLEW_RATE_FAST); + + /* Drive strength levels for GPIO outputs: + enum gpio_drive_strength { + GPIO_DRIVE_STRENGTH_2MA = 0, + GPIO_DRIVE_STRENGTH_4MA = 1, + GPIO_DRIVE_STRENGTH_8MA = 2, + GPIO_DRIVE_STRENGTH_12MA = 3 } + enum gpio_drive_strength gpio_get_drive_strength (uint gpio) + */ + if (spi_p->set_drive_strength) { + gpio_set_drive_strength(spi_p->mosi_gpio, spi_p->mosi_gpio_drive_strength); + gpio_set_drive_strength(spi_p->sck_gpio, spi_p->sck_gpio_drive_strength); + } + + // SD cards' DO MUST be pulled up. However, it might be done externally. + if (!spi_p->no_miso_gpio_pull_up) gpio_pull_up(spi_p->miso_gpio); + + // gpio_set_input_hysteresis_enabled(spi_p->miso_gpio, false); + + // Check if the user has provided DMA channels + if (spi_p->use_static_dma_channels) { + // Claim the channels provided + dma_channel_claim(spi_p->tx_dma); + dma_channel_claim(spi_p->rx_dma); + } else { + // Grab some unused dma channels + spi_p->tx_dma = dma_claim_unused_channel(true); + spi_p->rx_dma = dma_claim_unused_channel(true); + } + spi_p->tx_dma_cfg = dma_channel_get_default_config(spi_p->tx_dma); + spi_p->rx_dma_cfg = dma_channel_get_default_config(spi_p->rx_dma); + channel_config_set_transfer_data_size(&spi_p->tx_dma_cfg, DMA_SIZE_8); + channel_config_set_transfer_data_size(&spi_p->rx_dma_cfg, DMA_SIZE_8); + + // We set the outbound DMA to transfer from a memory buffer to the SPI + // transmit FIFO paced by the SPI TX FIFO DREQ The default is for the + // read address to increment every element (in this case 1 byte - + // DMA_SIZE_8) and for the write address to remain unchanged. + channel_config_set_dreq(&spi_p->tx_dma_cfg, spi_get_dreq(spi_p->hw_inst, true)); + channel_config_set_write_increment(&spi_p->tx_dma_cfg, false); + + // We set the inbound DMA to transfer from the SPI receive FIFO to a + // memory buffer paced by the SPI RX FIFO DREQ We configure the read + // address to remain unchanged for each element, but the write address + // to increment (so data is written throughout the buffer) + channel_config_set_dreq(&spi_p->rx_dma_cfg, spi_get_dreq(spi_p->hw_inst, false)); + channel_config_set_read_increment(&spi_p->rx_dma_cfg, false); + + LED_INIT(); + + spi_p->initialized = true; + spi_unlock(spi_p); + } + mutex_exit(&my_spi_init_mutex); + return true; +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/SPI/my_spi.h b/lib/sd_driver/SPI/my_spi.h new file mode 100755 index 0000000..5cb967a --- /dev/null +++ b/lib/sd_driver/SPI/my_spi.h @@ -0,0 +1,124 @@ +/* spi.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#pragma once + +#include +#include +#include +// +// Pico includes +#include "pico/stdlib.h" +#include "pico/mutex.h" +#include "pico/types.h" +// +#include "hardware/dma.h" +#include "hardware/gpio.h" +#include "hardware/irq.h" +#include "hardware/spi.h" +// +#include "my_debug.h" +#include "sd_timeouts.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPI_FILL_CHAR (0xFF) + +// "Class" representing SPIs +typedef struct spi_t { + spi_inst_t *hw_inst; // SPI HW + uint miso_gpio; // SPI MISO GPIO number (not pin number) + uint mosi_gpio; + uint sck_gpio; + uint baud_rate; + + /* The different modes of the Motorola SPI protocol are: + - Mode 0: When CPOL and CPHA are both 0, data sampled at the leading rising edge of the + clock pulse and shifted out on the falling edge. This is the most common mode for SPI bus + communication. + - Mode 1: When CPOL is 0 and CPHA is 1, data sampled at the trailing falling edge and + shifted out on the rising edge. + - Mode 2: When CPOL is 1 and CPHA is 0, data sampled at the leading falling edge + and shifted out on the rising edge. + - Mode 3: When CPOL is 1 and CPHA is 1, data sampled at the trailing rising edge and + shifted out on the falling edge. */ + uint spi_mode; + + bool no_miso_gpio_pull_up; + + /* Drive strength levels for GPIO outputs: + GPIO_DRIVE_STRENGTH_2MA, + GPIO_DRIVE_STRENGTH_4MA, + GPIO_DRIVE_STRENGTH_8MA, + GPIO_DRIVE_STRENGTH_12MA */ + bool set_drive_strength; + enum gpio_drive_strength mosi_gpio_drive_strength; + enum gpio_drive_strength sck_gpio_drive_strength; + + bool use_static_dma_channels; + uint tx_dma; + uint rx_dma; + + /* The following fields are not part of the configuration. They are dynamically assigned. */ + dma_channel_config tx_dma_cfg; + dma_channel_config rx_dma_cfg; + mutex_t mutex; + bool initialized; +} spi_t; + +void spi_transfer_start(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length); +uint32_t calculate_transfer_time_ms(spi_t *spi_p, uint32_t bytes); +bool spi_transfer_wait_complete(spi_t *spi_p, uint32_t timeout_ms); +bool spi_transfer(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length); +bool my_spi_init(spi_t *spi_p); + +static inline void spi_lock(spi_t *spi_p) { + myASSERT(mutex_is_initialized(&spi_p->mutex)); + mutex_enter_blocking(&spi_p->mutex); +} +static inline void spi_unlock(spi_t *spi_p) { + myASSERT(mutex_is_initialized(&spi_p->mutex)); + mutex_exit(&spi_p->mutex); +} + +/* +This uses the Pico LED to show SD card activity. +You can use it to get a rough idea of utilization. +Warning: Pico W uses GPIO 25 for SPI communication to the CYW43439. + +You can enable this by putting something like + add_compile_definitions(USE_LED=1) +in CMakeLists.txt, for example. +*/ +#if !defined(NO_PICO_LED) && defined(USE_LED) && USE_LED && defined(PICO_DEFAULT_LED_PIN) +# define LED_INIT() \ + { \ + gpio_init(PICO_DEFAULT_LED_PIN); \ + gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); \ + } +# define LED_ON() gpio_put(PICO_DEFAULT_LED_PIN, 1) +# define LED_OFF() gpio_put(PICO_DEFAULT_LED_PIN, 0) +#else +# define LED_ON() +# define LED_OFF() +# define LED_INIT() +#endif + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/SPI/sd_card_spi.c b/lib/sd_driver/SPI/sd_card_spi.c new file mode 100644 index 0000000..389f95a --- /dev/null +++ b/lib/sd_driver/SPI/sd_card_spi.c @@ -0,0 +1,1731 @@ +/** + * @file sd_card_spi.c + * @brief SD Card SPI Driver + * + * @section License + * + * Copyright 2021 Carl John Kugler III + * + * Licensed under the Apache License, Version 2.0 (the License); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +/* + * This code borrows heavily from the Mbed SDBlockDevice: + * https://os.mbed.com/docs/mbed-os/v5.15/apis/sdblockdevice.html + * mbed-os/components/storage/blockdevice/COMPONENT_SD/SDBlockDevice.cpp + * + * Editor: Carl Kugler (carlk3@gmail.com) + * + * Remember your ABCs: "Always Be Cobbling!" + */ + +/* mbed Microcontroller Library + * Copyright (c) 2006-2013 ARM Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Introduction + * ------------ + * SD and MMC cards support a number of interfaces, but common to them all + * is one based on SPI. Since we already have the mbed SPI Interface, it will + * be used for SD cards. + * + * The main reference I'm using is Chapter 7, "SPI Mode" of: + * http://www.sdcard.org/developers/tech/sdcard/pls/Simplified_Physical_Layer_Spec.pdf + * + * SPI Startup + * ----------- + * The SD card powers up in SD mode. The start-up procedure is complicated + * by the requirement to support older SDCards in a backwards compatible + * way with the new higher capacity variants SDHC and SDHC. + * + * The following figures from the specification with associated text describe + * the SPI mode initialisation process: + * - Figure 7-1: SD Memory Card State Diagram (SPI mode) + * - Figure 7-2: SPI Mode Initialization Flow + * + * Firstly, a low initial clock should be selected (in the range of 100- + * 400kHZ). After initialisation has been completed, the switch to a + * higher clock speed can be made (e.g. 1MHz). Newer cards will support + * higher speeds than the default _transfer_sck defined here. + * + * Next, note the following from the SDCard specification (note to + * Figure 7-1): + * + * In any of the cases CMD1 is not recommended because it may be difficult for + * the host to distinguish between MultiMediaCard and SD Memory Card + * + * Hence CMD1 is not used for the initialisation sequence. + * + * The SPI interface mode is selected by asserting CS low and sending the + * reset command (CMD0). The card will respond with a (R1) response. + * In practice many cards initially respond with 0xff or invalid data + * which is ignored. Data is read until a valid response is received + * or the number of re-reads has exceeded a maximim count. If a valid + * response is not received then the CMD0 can be retried. This + * has been found to successfully initialise cards where the SPI master + * (on MCU) has been reset but the SDCard has not, so the first + * CMD0 may be lost. + * + * CMD8 is optionally sent to determine the voltage range supported, and + * indirectly determine whether it is a version 1.x SD/non-SD card or + * version 2.x. I'll just ignore this for now. + * + * ACMD41 is repeatedly issued to initialise the card, until "in idle" + * (bit 0) of the R1 response goes to '0', indicating it is initialised. + * + * You should also indicate whether the host supports High Capicity cards, + * and check whether the card is high capacity - i'll also ignore this. + * + * SPI Protocol + * ------------ + * The SD SPI protocol is based on transactions made up of 8-bit words, with + * the host starting every bus transaction by asserting the CS signal low. The + * card always responds to commands, data blocks and errors. + * + * The protocol supports a CRC, but by default it is off (except for the + * first reset CMD0, where the CRC can just be pre-calculated, and CMD8) + * I'll leave the CRC off I think! + * + * Standard capacity cards have variable data block sizes, whereas High + * Capacity cards fix the size of data block to 512 bytes. I'll therefore + * just always use the Standard Capacity cards with a block size of 512 bytes. + * This is set with CMD16. + * + * You can read and write single blocks (CMD17, CMD25) or multiple blocks + * (CMD18, CMD25). For simplicity, I'll just use single block accesses. When + * the card gets a read command, it responds with a response token, and then + * a data token or an error. + * + * SPI Command Format + * ------------------ + * Commands are 6-bytes long, containing the command, 32-bit argument, and CRC. + * + * +---------------+------------+------------+-----------+----------+--------------+ + * | 01 | cmd[5:0] | arg[31:24] | arg[23:16] | arg[15:8] | arg[7:0] | crc[6:0] | + * 1 | + * +---------------+------------+------------+-----------+----------+--------------+ + * + * As I'm not using CRC, I can fix that byte to what is needed for CMD0 (0x95) + * + * All Application Specific commands shall be preceded with APP_CMD (CMD55). + * + * SPI Response Format + * ------------------- + * The main response format (R1) is a status byte (normally zero). Key flags: + * idle - 1 if the card is in an idle state/initialising + * cmd - 1 if an illegal command code was detected + * + * +-------------------------------------------------+ + * R1 | 0 | arg | addr | seq | crc | cmd | erase | idle | + * +-------------------------------------------------+ + * + * R1b is the same, except it is followed by a busy signal (zeros) until + * the first non-zero byte when it is ready again. + * + * Data Response Token + * ------------------- + * Every data block written to the card is acknowledged by a byte + * response token + * + * +----------------------+ + * | xxx | 0 | status | 1 | + * +----------------------+ + * 010 - OK! + * 101 - CRC Error + * 110 - Write Error + * + * Single Block Read and Write + * --------------------------- + * + * Block transfers have a byte header, followed by the data, followed + * by a 16-bit CRC. In our case, the data will always be 512 bytes. + * + * +------+---------+---------+- - - -+---------+-----------+----------+ + * | 0xFE | data[0] | data[1] | | data[n] | crc[15:8] | crc[7:0] | + * +------+---------+---------+- - - -+---------+-----------+----------+ + */ + +/* Standard includes. */ +#include +#include +#include +#include +// +#include "crc.h" +#include "diskio.h" /* Declarations of disk functions */ // Needed for STA_NOINIT, ... +#include "hw_config.h" // Hardware Configuration of the SPI and SD Card "objects" +#include "my_debug.h" +#include "delays.h" +#include "sd_card.h" +#include "sd_card_constants.h" +#include "sd_spi.h" +#include "sd_timeouts.h" +#include "util.h" +// +#include "sd_card_spi.h" + +#if defined(NDEBUG) || !USE_DBG_PRINTF +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif + +#ifndef TRACE +# define TRACE 0 +#endif + +#ifndef SD_CRC_ENABLED +#define SD_CRC_ENABLED 1 +#endif + +#if SD_CRC_ENABLED +static bool crc_on = true; +#else +static bool crc_on = false; +#endif + +#define TRACE_PRINTF(fmt, args...) +//#define TRACE_PRINTF DBG_PRINTF + +/** + * @brief Control Tokens + */ +typedef enum { + SPI_DATA_RESPONSE_MASK = 0x1F, + SPI_DATA_ACCEPTED = 0x05, + SPI_DATA_CRC_ERROR = 0x0B, + SPI_DATA_WRITE_ERROR = 0x0D, + SPI_START_BLOCK = 0xFE, + SPI_START_BLK_MUL_WRITE = 0xFC, + SPI_STOP_TRAN = 0xFD, +} spi_control_t; + +/** + * @brief Data Error Token Mask + */ +typedef enum { + SPI_DATA_READ_ERROR_MASK = 0xF, +} spi_data_read_error_mask_t; + +/** + * @brief Data Error Token Flags + */ +typedef enum { + SPI_READ_ERROR = 0x1 << 0, + SPI_READ_ERROR_CC = 0x1 << 1, + SPI_READ_ERROR_ECC_C = 0x1 << 2, + SPI_READ_ERROR_OFR = 0x1 << 3, +} spi_data_read_error_t; + +/** + * @brief R1 Response Format + */ +typedef enum { + R1_NO_RESPONSE = 0xFF, + R1_RESPONSE_RECV = 0x80, + R1_IDLE_STATE = 1 << 0, + R1_ERASE_RESET = 1 << 1, + R1_ILLEGAL_COMMAND = 1 << 2, + R1_COM_CRC_ERROR = 1 << 3, + R1_ERASE_SEQUENCE_ERROR = 1 << 4, + R1_ADDRESS_ERROR = 1 << 5, + R1_PARAMETER_ERROR = 1 << 6, +} spi_r1_response_t; + +/* SIZE in Bytes */ +#define PACKET_SIZE 6 /*!< SD Packet size CMD+ARG+CRC */ +/** + * @brief OCR Register Flags + */ +typedef enum { + OCR_HCS_CCS = 0x1 << 30, + OCR_LOW_VOLTAGE = 0x01 << 24, + OCR_3_3V = 0x1 << 20, +} spi_ocr_register_t; + +/** + * @brief Control Command + * + * @param x Command + */ +#define SPI_CMD(x) (0x40 | (x & 0x3f)) + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-enum" + +/** + * @brief Send a command over SPI interface + * + * @param sd_card_p Pointer to the sd card structure + * @param cmd Command to send + * @param arg Argument to send with the command + * + * @return The response from the card + * + * @details + * This function sends a command over the SPI interface and waits for the + * response. The command is prepared by setting the correct bits in the packet + * and calculating the CRC if necessary. The command is then sent and the + * response is received and returned. + * + * @note + * For CMD12_STOP_TRANSMISSION, the first byte received is a stuff byte and + * should be discarded. + */ +static uint8_t sd_cmd_spi(sd_card_t *sd_card_p, cmdSupported cmd, uint32_t arg) { + uint8_t cmd_packet[PACKET_SIZE] = { + SPI_CMD(cmd), + (arg >> 24), + (arg >> 16), + (arg >> 8), + (arg >> 0), + }; + + if (crc_on) { + cmd_packet[5] = (crc7(cmd_packet, 5) << 1) | 0x01; + } else { + // CMD0 is executed in SD mode, hence should have correct CRC + // CMD8 CRC verification is always enabled + switch (cmd) { + case CMD0_GO_IDLE_STATE: + cmd_packet[5] = 0x95; + break; + case CMD8_SEND_IF_COND: + cmd_packet[5] = 0x87; + break; + default: + // Make sure bit 0-End bit is high + cmd_packet[5] = 0xFF; + break; + } + } + // send a command + for (size_t i = 0; i < PACKET_SIZE; i++) { + sd_spi_write(sd_card_p, cmd_packet[i]); + } + + // The received byte immediately following CMD12 is a stuff byte, + // it should be discarded before receive the response of the CMD12. + if (CMD12_STOP_TRANSMISSION == cmd) { + sd_spi_write(sd_card_p, SPI_FILL_CHAR); + } + + // Loop for response: Response is sent back within command response time + // (NCR), 0 to 8 bytes for SDC + uint8_t response; + for (size_t i = 0; i < 0x10; i++) { + response = sd_spi_read(sd_card_p); + // Got the response + if (!(response & R1_RESPONSE_RECV)) { + break; + } + } + + return response; +} +#pragma GCC diagnostic pop + +/** + * @brief Wait for the SD card to be ready for the next command. + * + * Sends dummy clocks with DI held high until the card releases the DO line. + * + * @param sd_card_p Pointer to the sd_card_t struct. + * @param timeout The maximum time to wait for the card to become ready. + * + * @return true if the card is ready, false otherwise. + */ +static bool sd_wait_ready(sd_card_t *sd_card_p, uint32_t timeout) { + char resp; + + // Keep sending dummy clocks with DI held high until the card releases the + // DO line + uint32_t start = millis(); + do { + resp = sd_spi_write_read(sd_card_p, 0xFF); + } while (resp != 0xFF && millis() - start < timeout); + /* Checking for 0xFF provides a little extra margin to + make sure that DO has gone high and stayed there. + (the alternative is to accept the first non-zero byte) */ + if (resp != 0xFF) DBG_PRINTF("%s failed\n", __FUNCTION__); + + // Return success/failure + return (0xFF == resp); +} + +/* Locks the SD card and acquires its SPI +Potential optimization: the SPI could be locked separately, +so if there are multiple SD cards on one SPI, +another SD card could use the SPI during any gaps +in the first SD card's utilization. +However, these gaps are generally small. +*/ +static void sd_acquire(sd_card_t *sd_card_p) { + sd_lock(sd_card_p); + sd_spi_acquire(sd_card_p); +} +static void sd_release(sd_card_t *sd_card_p) { + sd_spi_release(sd_card_p); + sd_unlock(sd_card_p); +} + +#if TRACE +static const char *cmd2str(const cmdSupported cmd) { + switch (cmd) { + default: + return "CMD_NOT_SUPPORTED"; + case CMD0_GO_IDLE_STATE: + return "CMD0_GO_IDLE_STATE"; + case CMD1_SEND_OP_COND: + return "CMD1_SEND_OP_COND"; + case CMD6_SWITCH_FUNC: + return "CMD6_SWITCH_FUNC"; + case CMD8_SEND_IF_COND: + return "CMD8_SEND_IF_COND"; + case CMD9_SEND_CSD: + return "CMD9_SEND_CSD"; + case CMD10_SEND_CID: + return "CMD10_SEND_CID"; + case CMD12_STOP_TRANSMISSION: + return "CMD12_STOP_TRANSMISSION"; + case CMD13_SEND_STATUS: + return "CMD13_SEND_STATUS or ACMD6_SET_BUS_WIDTH or " + "ACMD13_SD_STATUS"; + case CMD16_SET_BLOCKLEN: + return "CMD16_SET_BLOCKLEN"; + case CMD17_READ_SINGLE_BLOCK: + return "CMD17_READ_SINGLE_BLOCK"; + case CMD18_READ_MULTIPLE_BLOCK: + return "CMD18_READ_MULTIPLE_BLOCK"; + case CMD24_WRITE_BLOCK: + return "CMD24_WRITE_BLOCK"; + case CMD25_WRITE_MULTIPLE_BLOCK: + return "CMD25_WRITE_MULTIPLE_BLOCK"; + case CMD27_PROGRAM_CSD: + return "CMD27_PROGRAM_CSD"; + case CMD32_ERASE_WR_BLK_START_ADDR: + return "CMD32_ERASE_WR_BLK_START_ADDR"; + case CMD33_ERASE_WR_BLK_END_ADDR: + return "CMD33_ERASE_WR_BLK_END_ADDR"; + case CMD38_ERASE: + return "CMD38_ERASE"; + case CMD55_APP_CMD: + return "CMD55_APP_CMD"; + case CMD56_GEN_CMD: + return "CMD56_GEN_CMD"; + case CMD58_READ_OCR: + return "CMD58_READ_OCR"; + case CMD59_CRC_ON_OFF: + return "CMD59_CRC_ON_OFF"; + // case ACMD6_SET_BUS_WIDTH: + // case ACMD13_SD_STATUS: + case ACMD22_SEND_NUM_WR_BLOCKS: + return "ACMD22_SEND_NUM_WR_BLOCKS"; + case ACMD23_SET_WR_BLK_ERASE_COUNT: + return "ACMD23_SET_WR_BLK_ERASE_COUNT"; + case ACMD41_SD_SEND_OP_COND: + return "ACMD41_SD_SEND_OP_COND"; + case ACMD42_SET_CLR_CARD_DETECT: + return "ACMD42_SET_CLR_CARD_DETECT"; + case ACMD51_SEND_SCR: + return "ACMD51_SEND_SCR"; + } +} +#endif + +/** + * @brief Check the response of the card status command (CMD13) and set + * the status bit accordingly. + * + * @param response The response of the card status command (CMD13). + * + * @return The status of the SD card. + */ +static int chk_CMD13_response(uint32_t response) { + int32_t status = 0; + DBG_PRINTF("Card Status: R2: 0x%" PRIx32 "\n", response); + if (response & 0x01 << 0) { + DBG_PRINTF("Card is Locked\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE; + } + if (response & 0x01 << 1) { + DBG_PRINTF("WP Erase Skip, Lock/Unlock Cmd Failed\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED; + } + if (response & 0x01 << 2) { + DBG_PRINTF("Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE; + } + if (response & 0x01 << 3) { + DBG_PRINTF("CC Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE; + } + if (response & 0x01 << 4) { + DBG_PRINTF("Card ECC Failed\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE; + } + if (response & 0x01 << 5) { + DBG_PRINTF("WP Violation\n"); + status |= SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED; + } + if (response & 0x01 << 6) { + DBG_PRINTF("Erase Param\n"); + status |= SD_BLOCK_DEVICE_ERROR_ERASE; + } + if (response & 0x01 << 7) { + DBG_PRINTF("Out of Range, CSD_Overwrite\n"); + status |= SD_BLOCK_DEVICE_ERROR_PARAMETER; + } + if (response & 0x01 << 8) { + DBG_PRINTF("In Idle State\n"); + status |= SD_BLOCK_DEVICE_ERROR_NONE; + } + if (response & 0x01 << 9) { + DBG_PRINTF("Erase Reset\n"); + status |= SD_BLOCK_DEVICE_ERROR_ERASE; + } + if (response & 0x01 << 10) { + DBG_PRINTF("Illegal Command\n"); + status |= SD_BLOCK_DEVICE_ERROR_UNSUPPORTED; + } + if (response & 0x01 << 11) { + DBG_PRINTF("Com CRC Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_CRC; + } + if (response & 0x01 << 12) { + DBG_PRINTF("Erase Sequence Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_ERASE; + } + if (response & 0x01 << 13) { + DBG_PRINTF("Address Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_PARAMETER; + } + if (response & 0x01 << 14) { + DBG_PRINTF("Parameter Error\n"); + status |= SD_BLOCK_DEVICE_ERROR_PARAMETER; + } + return status; +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wswitch-enum" + +/** + * @brief Send a command to the SD card. + * @param sd_card_p pointer to sd_card_t structure + * @param cmd command to send + * @param arg argument for the command + * @param isAcmd true if this is an application command + * @param resp pointer to a uint32_t to save the response + * @return error code + * + * This function sends a command to the SD card and waits for the response. + * It will retry the command up to sd_timeouts.sd_command_retries times if there is no response. + * The response is stored in the @p resp variable if it is not NULL. + * The function will return SD_BLOCK_DEVICE_ERROR_NONE if the command was successful, + * SD_BLOCK_DEVICE_ERROR_NO_RESPONSE if there was no response, + * SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error, + * SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command was not supported, + * SD_BLOCK_DEVICE_ERROR_PARAMETER if there was a parameter error, + * SD_BLOCK_DEVICE_ERROR_ERASE if there was an erase error. + */ +static block_dev_err_t sd_cmd(sd_card_t *sd_card_p, const cmdSupported cmd, uint32_t arg, + bool isAcmd, uint32_t *resp) { + // TRACE_PRINTF("%s(%s(0x%08lx)): ", __FUNCTION__, cmd2str(cmd), arg); + myASSERT(sd_is_locked(sd_card_p)); + myASSERT(0 == gpio_get(sd_card_p->spi_if_p->ss_gpio)); + + int32_t status = SD_BLOCK_DEVICE_ERROR_NONE; + uint32_t response = 0; + + // No need to wait for card to be ready when sending the stop command + if (CMD12_STOP_TRANSMISSION != cmd && CMD0_GO_IDLE_STATE != cmd) { + if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) { + DBG_PRINTF("Card not ready yet\n"); + return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + } + } + for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) { + // Send CMD55 for APP command first + if (isAcmd) { + response = sd_cmd_spi(sd_card_p, CMD55_APP_CMD, 0x0); + // Wait for card to be ready after CMD55 + if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) { + DBG_PRINTF("Card not ready yet\n"); + } + } + // Send command over SPI interface + response = sd_cmd_spi(sd_card_p, cmd, arg); + if (R1_NO_RESPONSE == response) { + DBG_PRINTF("No response CMD:%d\n", cmd); + // Re-try command + continue; + } + break; + } + // Pass the response to the command call if required + if (NULL != resp) { + *resp = response; + } + // Process the response R1 : Exit on CRC/Illegal command error/No response + if (R1_NO_RESPONSE == response) { + DBG_PRINTF("No response CMD:%d response: 0x%" PRIx32 "\n", cmd, response); + return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + } + if (response & R1_COM_CRC_ERROR && ACMD23_SET_WR_BLK_ERASE_COUNT != cmd) { + DBG_PRINTF("CRC error CMD:%d response 0x%" PRIx32 "\n", cmd, response); + return SD_BLOCK_DEVICE_ERROR_CRC; // CRC error + } + if (response & R1_ILLEGAL_COMMAND) { + if (ACMD23_SET_WR_BLK_ERASE_COUNT != cmd) + DBG_PRINTF("Illegal command CMD:%d response 0x%" PRIx32 "\n", cmd, response); + if (CMD8_SEND_IF_COND == cmd) { + // Illegal command is for Ver1 or not SD Card + sd_card_p->state.card_type = CARD_UNKNOWN; + } + return SD_BLOCK_DEVICE_ERROR_UNSUPPORTED; // Command not supported + } + + // DBG_PRINTF("CMD:%d \t arg:0x%" PRIx32 " \t Response:0x%" PRIx32 "\n", + // cmd, arg, response); + // Set status for other errors + if ((response & R1_ERASE_RESET) || (response & R1_ERASE_SEQUENCE_ERROR)) { + status = SD_BLOCK_DEVICE_ERROR_ERASE; // Erase error + } else if ((response & R1_ADDRESS_ERROR) || (response & R1_PARAMETER_ERROR)) { + // Misaligned address / invalid address block length + status = SD_BLOCK_DEVICE_ERROR_PARAMETER; + } + + // Get rest of the response part for other commands + switch (cmd) { + case CMD8_SEND_IF_COND: // Response R7 + DBG_PRINTF("V2-Version Card\n"); + sd_card_p->state.card_type = SDCARD_V2; // fallthrough + // Note: No break here, need to read rest of the response + case CMD58_READ_OCR: // Response R3 + response = (sd_spi_read(sd_card_p) << 24); + response |= (sd_spi_read(sd_card_p) << 16); + response |= (sd_spi_read(sd_card_p) << 8); + response |= sd_spi_read(sd_card_p); + DBG_PRINTF("R3/R7: 0x%" PRIx32 "\n", response); + break; + case CMD12_STOP_TRANSMISSION: // Response R1b + case CMD38_ERASE: + sd_wait_ready(sd_card_p, sd_timeouts.sd_command); + break; + case CMD13_SEND_STATUS: // Response R2 + response <<= 8; + response |= sd_spi_read(sd_card_p); + if (response) status = chk_CMD13_response(response); + default:; + } + // Pass the updated response to the command + if (NULL != resp) { + *resp = response; + } + return status; +} +#pragma GCC diagnostic pop + +/* R7 response pattern for CMD8 */ +#define CMD8_PATTERN (0xAA) + +/** + * @brief Send CMD8 to check if the card supports version 2.0 of the SD spec. + * + * @param sd_card_p Pointer to the SD card information structure. + * @return sd_block_dev_err_t Returns SD_BLOCK_DEVICE_ERROR_NONE if the card + * supports version 2.0 of the SD spec, SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the + * card does not support version 2.0 of the SD spec, or other error codes as + * defined in sd_block_dev_err_t. + * + * @details CMD8 is sent to check if the card supports version 2.0 of the SD + * spec. The response from the card is checked to see if the card supports + * version 2.0 of the SD spec. If the card does not support version 2.0 of the SD + * spec, the card type is set to CARD_UNKNOWN and the card is considered + * unreadable. + */ +static block_dev_err_t sd_cmd8(sd_card_t *sd_card_p) { + uint32_t arg = (CMD8_PATTERN << 0); // [7:0]check pattern + uint32_t response = 0; + int32_t status = SD_BLOCK_DEVICE_ERROR_NONE; + + arg |= (0x1 << 8); // 2.7-3.6V // [11:8]supply voltage(VHS) + + status = sd_cmd(sd_card_p, CMD8_SEND_IF_COND, arg, false, &response); + // Verify voltage and pattern for V2 version of card + if ((SD_BLOCK_DEVICE_ERROR_NONE == status) && (SDCARD_V2 == sd_card_p->state.card_type)) { + // If check pattern is not matched, CMD8 communication is not valid + if ((response & 0xFFF) != arg) { + DBG_PRINTF("CMD8 Pattern mismatch 0x%" PRIx32 " : 0x%" PRIx32 "\n", arg, response); + sd_card_p->state.card_type = CARD_UNKNOWN; + status = SD_BLOCK_DEVICE_ERROR_UNUSABLE; + } + } + return status; +} + +static block_dev_err_t read_bytes(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t length); + +/** + * @brief Get the number of sectors on an SD card. + * + * @param sd_card_p A pointer to the sd_card_t structure for the card. + * + * @return The number of sectors on the card, or 0 if an error occurred. + * + * @details This function sends a CMD9 command to the card to get the Card Specific + * Data (CSD) and then extracts the number of sectors from the CSD. + */ +static uint32_t in_sd_spi_sectors(sd_card_t *sd_card_p) { + // CMD9, Response R2 (R1 byte + 16-byte block read) + if (sd_cmd(sd_card_p, CMD9_SEND_CSD, 0x0, false, 0) != 0x0) { + DBG_PRINTF("Didn't get a response from the disk\n"); + return 0; + } + if (read_bytes(sd_card_p, sd_card_p->state.CSD, 16) != 0) { + DBG_PRINTF("Couldn't read CSD response from disk\n"); + return 0; + } + return CSD_sectors(sd_card_p->state.CSD); +} +/** + * @brief Get the number of sectors on an SD card. + * + * @param sd_card_p A pointer to the sd_card_t structure for the card. + * + * @return The number of sectors on the card, or 0 if an error occurred. + * + * @details This function gets the number of sectors by first acquiring the card, + * then calling in_sd_spi_sectors to get the number of sectors, and finally + * releasing the card. + */ +uint32_t sd_spi_sectors(sd_card_t *sd_card_p) { + sd_acquire(sd_card_p); + uint32_t sectors = in_sd_spi_sectors(sd_card_p); + sd_release(sd_card_p); + return sectors; +} + +/** + * @brief Wait for the card to become ready and send a given token. + * + * @param sd_card_p A pointer to the sd_card_t structure for the card. + * @param token The token to be sent once the card is ready. + * + * @return True if the card became ready and the token was sent, false otherwise. + * + * @details The function waits until the card is ready and then sends the given + * token. If the card doesn't become ready within the timeout period, + * the function returns false. + */ +static bool sd_wait_token(sd_card_t *sd_card_p, uint8_t token) { + //TRACE_PRINTF("%s(0x%02x)\n", __FUNCTION__, token); + + uint32_t start = millis(); + do { + if (token == sd_spi_read(sd_card_p)) { + return true; + } + } while (millis() - start < sd_timeouts.sd_command); + + DBG_PRINTF("sd_wait_token: timeout\n"); + return false; +} + +static bool chk_crc16(uint8_t *buffer, size_t length, uint16_t crc) { + if (crc_on) { + uint16_t crc_result; + // Compute and verify checksum + crc_result = crc16(buffer, length); + if (crc_result != crc) + DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 " computed: 0x%" PRIx16 "\n", + __func__, crc, crc_result); + return (crc_result == crc); + } + return true; +} + +#define SPI_START_BLOCK (0xFE) /* For Single Block Read/Write and Multiple Block Read */ + +static block_dev_err_t stop_wr_tran(sd_card_t *sd_card_p); + +static block_dev_err_t read_bytes(sd_card_t *sd_card_p, uint8_t *buffer, uint32_t length) { + uint16_t crc; + + // read until start byte (0xFE) + if (false == sd_wait_token(sd_card_p, SPI_START_BLOCK)) { + DBG_PRINTF("%s:%d Read timeout\n", __func__, __LINE__); + return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + } + bool ok = sd_spi_transfer(sd_card_p, NULL, buffer, length); + if (!ok) return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + + // Read the CRC16 checksum for the data block + crc = (sd_spi_read(sd_card_p) << 8); + crc |= sd_spi_read(sd_card_p); + + if (!chk_crc16(buffer, length, crc)) { + DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__, crc); + return SD_BLOCK_DEVICE_ERROR_CRC; + } + return 0; +} +/** + * @brief Read a block of data from the SD card. + * + * @param sd_card_p pointer to sd_card_t structure + * @param buffer pointer to the buffer to store the data + * @param data_address the address of the block to read + * @param num_rd_blks the number of blocks to read + * + * @return error code + * + * @details + * This function checks if the SD card is initialized and has a valid disk, + * and if the number of blocks to read is not zero and is within the range of + * the card's sectors. If not, it returns SD_BLOCK_DEVICE_ERROR_PARAMETER. + * If there is an ongoing write transmission, it stops it. + * It then sends a command to receive data based on + * the number of blocks to read. It reads the data from the SD card and checks + * the CRC16 checksum for each block. If the two match, the function continues + * to the next block. If the number of blocks to read is greater than 1, it + * sends CMD12 to stop the transmission after all blocks have been + * read. It then checks the CRC16 checksum for the last block and returns the + * error code. + */ +static block_dev_err_t in_sd_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, + const uint32_t data_address, + const uint32_t num_rd_blks) { + if (sd_card_p->state.m_Status & (STA_NOINIT | STA_NODISK)) + return SD_BLOCK_DEVICE_ERROR_PARAMETER; + if (!num_rd_blks) return SD_BLOCK_DEVICE_ERROR_PARAMETER; + if (data_address + num_rd_blks > sd_card_p->state.sectors) + return SD_BLOCK_DEVICE_ERROR_PARAMETER; + + block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE; + + // Stop any ongoing write transmission + if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) { + status = stop_wr_tran(sd_card_p); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + } + + // Send command to receive data + if (num_rd_blks == 1) + status = sd_cmd(sd_card_p, CMD17_READ_SINGLE_BLOCK, data_address, false, 0); + else + status = sd_cmd(sd_card_p, CMD18_READ_MULTIPLE_BLOCK, data_address, false, 0); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + + /* Optimization: + While the DMA is busy transfering the block data, + use the some of the wait time to check the CRC + for the previous block. + */ + uint16_t prev_block_crc = 0; + uint8_t *prev_buffer_addr = 0; + uint32_t blk_cnt = num_rd_blks; + + // receive the data : one block at a time + while (blk_cnt) { + // read until start byte (0xFE) + if (!sd_wait_token(sd_card_p, SPI_START_BLOCK)) { + DBG_PRINTF("%s:%d Read timeout\n", __func__, __LINE__); + return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + } + // read data + sd_spi_transfer_start(sd_card_p, NULL, buffer, sd_block_size); + + // Check the CRC16 checksum for the previous data block + if (prev_buffer_addr) { + // Check previous block's CRC: + if (!chk_crc16(prev_buffer_addr, sd_block_size, prev_block_crc)) { + DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__, + prev_block_crc); + return SD_BLOCK_DEVICE_ERROR_CRC; + } + } + + uint32_t timeout = calculate_transfer_time_ms(sd_card_p->spi_if_p->spi, sd_block_size); + bool ok = sd_spi_transfer_wait_complete(sd_card_p, timeout); + if (!ok) return SD_BLOCK_DEVICE_ERROR_NO_RESPONSE; + + // Read the CRC16 checksum for the data block + prev_block_crc = sd_spi_read(sd_card_p) << 8; + prev_block_crc |= sd_spi_read(sd_card_p); + prev_buffer_addr = buffer; + buffer += sd_block_size; + --blk_cnt; + } + + if (num_rd_blks > 1) { + // Send CMD12(0x00000000) to stop the transmission for multi-block transfer + status = sd_cmd(sd_card_p, CMD12_STOP_TRANSMISSION, 0x0, false, 0); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + } + // Check final block's CRC: + if (!chk_crc16(prev_buffer_addr, sd_block_size, prev_block_crc)) { + DBG_PRINTF("%s: Invalid CRC received: 0x%" PRIx16 "\n", __func__, prev_block_crc); + return SD_BLOCK_DEVICE_ERROR_CRC; + } + return status; +} +static block_dev_err_t sd_read_blocks(sd_card_t *sd_card_p, uint8_t *buffer, + uint32_t data_address, uint32_t num_rd_blks) { + TRACE_PRINTF("sd_read_blocks(0x%p, 0x%lx, 0x%lx)\n", buffer, data_address, num_rd_blks); + sd_acquire(sd_card_p); + unsigned retries = sd_timeouts.sd_command_retries; + block_dev_err_t status; + do { + status = in_sd_read_blocks(sd_card_p, buffer, data_address, num_rd_blks); + if (status != SD_BLOCK_DEVICE_ERROR_NONE) { + if (SD_BLOCK_DEVICE_ERROR_NONE != + sd_cmd(sd_card_p, CMD12_STOP_TRANSMISSION, 0x0, false, 0)) + break; + } + } while (--retries && status != SD_BLOCK_DEVICE_ERROR_NONE); + sd_release(sd_card_p); + return status; +} + +/** + * @brief Send the numbers of the well written (without errors) blocks. + * + * This function sends the ACMD22 command to the SD card to get the number of + * blocks that were successfully written without errors. It then reads the + * response from the SD card and returns it. + * + * @param sd_card_p Pointer to the SD card object. + * @param num_p Pointer to a variable to store the number of blocks. + * + * @return Block device error code. Returns SD_BLOCK_DEVICE_ERROR_NONE on success, + * SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the SD card is not responding, + * SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error reading the response, + * SD_BLOCK_DEVICE_ERROR_PARAMETER if an invalid parameter was passed. + */ +static block_dev_err_t get_num_wr_blocks(sd_card_t *sd_card_p, uint32_t *num_p) { + // Send the ACMD22 command to get the number of written blocks + block_dev_err_t err = sd_cmd(sd_card_p, ACMD22_SEND_NUM_WR_BLOCKS, 0, true, NULL); + + // If the command was not successful, return the error code + if (SD_BLOCK_DEVICE_ERROR_NONE != err) { + DBG_PRINTF("Didn't get a response from the disk\n"); + return err; + } + + // Read the response from the SD card and store it in the num_p variable + err = read_bytes(sd_card_p, (uint8_t *)num_p, sizeof(uint32_t)); + *num_p = __builtin_bswap32(*num_p); + + // If there was an error reading the response, return the error code + if (SD_BLOCK_DEVICE_ERROR_NONE != err) { + DBG_PRINTF("Couldn't read NUM_WR_BLOCKS response from disk\n"); + return err; + } + + // Return success + return SD_BLOCK_DEVICE_ERROR_NONE; +} + +/** + * @brief Send a single block of data to the SD card. + * + * @param sd_card_p Pointer to the SD card object. + * @param buffer Pointer to the buffer containing the data to be sent. + * @param token The token to be sent before the data. + * @param length The length of the data to be sent. + * + * @return Block device error code. + * + * @details + * The function sends a single block of data to the SD card. It starts by sending the start block + * token, then writes the data using the SPI transfer function. While the SPI transfer is ongoing, + * the function computes the CRC16 checksum of the data if CRC checking is enabled. After the SPI + * transfer is complete, the function writes the CRC16 checksum to the SD card. Finally, the function + * checks the response token and returns an error code if the data was not accepted. + */ +static block_dev_err_t send_block(sd_card_t *sd_card_p, const uint8_t *buffer, uint8_t token, + uint32_t length) +{ + uint8_t response; + + /* Indicate start of block - Start Block Token */ + response = sd_spi_write_read(sd_card_p, token); + if (!response) { + DBG_PRINTF("Start Block Token not accepted. Response: 0x%x\n", response); + return SD_BLOCK_DEVICE_ERROR_WRITE; + } + + // Write the data + sd_spi_transfer_start(sd_card_p, buffer, NULL, length); + + /* Optimization: + While the DMA is busy transfering the block data, + use the some of the wait time to calculate the CRC. + Typically, DMA transfer of the block data takes about 244 us, + but the CRC16 calculation takes only about 66 us. + */ + + uint16_t crc = (~0); + // While DMA transfers the block, compute CRC: + if (crc_on) { + // Compute CRC + crc = crc16((void *)buffer, length); + } + uint32_t timeout = calculate_transfer_time_ms(sd_card_p->spi_if_p->spi, length); + bool ok = sd_spi_transfer_wait_complete(sd_card_p, timeout); + if (!ok) return SD_BLOCK_DEVICE_ERROR_WRITE; + + // Write the checksum CRC16 + sd_spi_write(sd_card_p, crc >> 8); + sd_spi_write(sd_card_p, crc); + + block_dev_err_t rc = SD_BLOCK_DEVICE_ERROR_NONE; + + // Check the response token + response = sd_spi_read(sd_card_p); + + // Only CRC and general write error are communicated via response token + if ((response & SPI_DATA_RESPONSE_MASK) != SPI_DATA_ACCEPTED) { + EMSG_PRINTF("%s: Block Write not accepted. Response token: 0x%x, " + "status bits: %d%d%d\n", + sd_get_drive_prefix(sd_card_p), + response, + response & 0b1000 ? 1 : 0, + response & 0b0100 ? 1 : 0, + response & 0b0010 ? 1 : 0 + ); + /* + * The meaning of the status bits (bits 3, 2 & 1) + * is defined as follows: + * '010' - Data accepted. + * '101' - Data rejected due to a CRC error. + * '110' - Data Rejected due to a Write Error + * In case of any error (CRC or Write Error) during Write Multiple Block operation, the + * host shall stop the data transmission using CMD12. In case of a Write Error (response + * '110'), the host may send CMD13 (SEND_STATUS) in order to get the cause of the write + * problem. ACMD22 can be used to find the number of well written write blocks. + */ + + rc = SD_BLOCK_DEVICE_ERROR_WRITE; + } + // Wait while card is busy programming + if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) { + DBG_PRINTF("%s:%d: Card not ready yet\n", __func__, __LINE__); + rc = SD_BLOCK_DEVICE_ERROR_WRITE; + } + return rc; +} +/** + * @brief Send all blocks of data, one block at a time. + * + * The function performs the following steps: + * - Sends each block of data using the send_block() function + * - Checks the status of each send operation and stop if there is an error + * - Updates the buffer pointer and data address after each send operation + * - Sets the ongoing_mlt_blk_wrt flag to true if all blocks are sent successfully + * - Otherwise, stops the ongoing multiblock write and resets the number of + * blocks requested + * + * @param sd_card_p Pointer to the SD card object. + * @param buffer_p Pointer to the array of const uint8_t pointers. Each pointer + * points to the start of the block of data to be written. + * @param data_address_p Pointer to the address of the first block of data to be + * written. + * @param num_wrt_blks_p Pointer to the number of blocks to be written. + * + * @return SD_BLOCK_DEVICE_ERROR_NONE if all blocks are sent successfully, + * otherwise an error code. + */ +static block_dev_err_t send_all_blocks(sd_card_t *sd_card_p, const uint8_t *buffer_p[], + uint32_t * const data_address_p, + uint32_t * const num_wrt_blks_p) +{ + block_dev_err_t status; + do { + status = send_block(sd_card_p, *buffer_p, SPI_START_BLK_MUL_WRITE, sd_block_size); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) break; + *buffer_p += sd_block_size; + ++*data_address_p; + } while (--*num_wrt_blks_p); + if (SD_BLOCK_DEVICE_ERROR_NONE == status) { + myASSERT(!*num_wrt_blks_p); + sd_card_p->spi_if_p->state.cont_sector_wrt = *data_address_p; + sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt = true; + } else { + // sd_card_p->spi_if_p->state.n_wrt_blks_reqd cleared in stop_wr_tran + uint32_t n_wrt_blks_reqd = sd_card_p->spi_if_p->state.n_wrt_blks_reqd; + stop_wr_tran(sd_card_p); // Ignore return value + uint32_t nw; + block_dev_err_t err = get_num_wr_blocks(sd_card_p, &nw); + if (SD_BLOCK_DEVICE_ERROR_NONE == err) { + DBG_PRINTF("blocks_requested: %lu, NUM_WR_BLOCKS: %lu\n", + n_wrt_blks_reqd, nw); + *num_wrt_blks_p = n_wrt_blks_reqd - nw; + } + } + return status; +} +/** + * @brief Write multiple blocks of data to the SD card. + * + * If there is an ongoing multiblock write and the next write is contiguous, + * this function will continue the write operation without stopping the + * transmission. Otherwise, it will stop any ongoing write transmission + * and send the command to perform the write operation. + * + * @param sd_card_p Pointer to the SD card object. + * @param buffer_p Pointer to the array of const uint8_t pointers. Each pointer + * points to the data buffer for a block. + * @param data_address_p Pointer to the integer storing the data address. + * @param num_wrt_blks_p Pointer to the integer storing the number of blocks to + * write. + * @return block_dev_err_t Error code indicating the status of the write operation. + */ +static block_dev_err_t in_sd_write_blocks(sd_card_t *sd_card_p, + const uint8_t *buffer_p[], + uint32_t * const data_address_p, + uint32_t * const num_wrt_blks_p) +{ + block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE; + + /* Continue a multiblock write */ + if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt && + sd_card_p->spi_if_p->state.cont_sector_wrt == *data_address_p) { + // Update the number of blocks requested for write + sd_card_p->spi_if_p->state.n_wrt_blks_reqd += *num_wrt_blks_p; + // Send all blocks of data + return send_all_blocks(sd_card_p, buffer_p, data_address_p, num_wrt_blks_p); + } + + // Stop any ongoing write transmission + if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) { + status = stop_wr_tran(sd_card_p); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + } + + // Send command to perform write operation + status = sd_cmd(sd_card_p, CMD25_WRITE_MULTIPLE_BLOCK, *data_address_p, false, 0); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + + // Update the number of blocks requested for write + sd_card_p->spi_if_p->state.n_wrt_blks_reqd = *num_wrt_blks_p; + // Send all blocks of data + return send_all_blocks(sd_card_p, buffer_p, data_address_p, num_wrt_blks_p); + /* Optimization: + To optimize large contiguous writes, + postpone stopping transmission until it is + clear that the next operation is not a continuation. + + Any transactions other than a `sd_write_blocks` + continuation must stop any ongoing transmission + before proceeding. + */ +} +static block_dev_err_t stop_wr_tran(sd_card_t *sd_card_p) { + sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt = false; + /* In a Multiple Block write operation, the stop transmission will be + * done by sending 'Stop Tran' token instead of 'Start Block' token at + * the beginning of the next block + */ + sd_spi_write(sd_card_p, SPI_STOP_TRAN); + /* + Once the programming operation is completed, the + host must check the results of the programming + using the SEND_STATUS command (CMD13). + Some errors (e.g. address out of range, write + protect violation, etc.) are detected during + programming only. The only validation check + performed on the data block and communicated to + the host via the data-response token is CRC. + */ + + if (false == sd_wait_ready(sd_card_p, sd_timeouts.sd_command)) { + DBG_PRINTF("Card not ready yet\n"); + } + + uint32_t stat = 0; + sd_card_p->spi_if_p->state.n_wrt_blks_reqd = 0; + return sd_cmd(sd_card_p, CMD13_SEND_STATUS, 0, false, &stat); +} + +/** + * @brief Writes a single block to the SD card + * + * @param[in] sd_card_p Pointer to the SD card + * @param[in] buffer Buffer of data to write to the block + * @param[in] address Logical Address of the block to write to (LBA) + * + * @return + * - SD_BLOCK_DEVICE_ERROR_NONE on success + * - error code on failure + */ +static block_dev_err_t write_block(sd_card_t *sd_card_p, const uint8_t *buffer, + uint32_t const address) +{ + // Stop any ongoing multiple block write transmission + block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE; + if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) { + status = stop_wr_tran(sd_card_p); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + } + + // Send command to perform the write operation + status = sd_cmd(sd_card_p, CMD24_WRITE_BLOCK, address, false, 0); + if (SD_BLOCK_DEVICE_ERROR_NONE != status) return status; + + // Write data + send_block(sd_card_p, buffer, SPI_START_BLOCK, sd_block_size); + + /* + Once the programming operation is completed, the + host must check the results of the programming + using the SEND_STATUS command (CMD13). + Some errors (e.g. address out of range, write + protect violation, etc.) are detected during + programming only. The only validation check + performed on the data block and communicated to + the host via the data-response token is CRC. + */ + + // Send command to get the status of the card + uint32_t stat = 0; + status = sd_cmd(sd_card_p, CMD13_SEND_STATUS, 0, false, &stat); + + return status; +} +/** + * @brief Programs blocks to a block device + * + * @param[in] sd_card_p Pointer to the SD card + * @param[in] buffer Buffer of data to write to blocks + * @param[in] data_address Logical Address of block to begin writing to (LBA) + * @param[in] num_wrt_blks Size to write in blocks + * + * @return + * - SD_BLOCK_DEVICE_ERROR_NONE on success + * - SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the device (SD card) is missing or not connected + * - SD_BLOCK_DEVICE_ERROR_CRC if there was a CRC error + * - SD_BLOCK_DEVICE_ERROR_PARAMETER if an invalid parameter was passed + * - SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command is unsupported + * - SD_BLOCK_DEVICE_ERROR_NO_INIT if the device is not initialized + * - SD_BLOCK_DEVICE_ERROR_WRITE if there was an SPI write error + * - SD_BLOCK_DEVICE_ERROR_ERASE if there was an erase error + */ +static block_dev_err_t sd_write_blocks(sd_card_t *sd_card_p, uint8_t const buffer[], + uint32_t data_address, uint32_t num_wrt_blks) +{ + TRACE_PRINTF("%s(0x%p, 0x%lx, 0x%lx)\n", __func__, buffer, data_address, num_wrt_blks); + // Check if the SD card pointer is valid + if (NULL == sd_card_p) + return SD_BLOCK_DEVICE_ERROR_PARAMETER; + + // Check if the device is initialized and not missing + if (sd_card_p->state.m_Status & (STA_NOINIT | STA_NODISK)) + return SD_BLOCK_DEVICE_ERROR_PARAMETER; + + // Check if the number of blocks to write is valid + if (!num_wrt_blks) return SD_BLOCK_DEVICE_ERROR_PARAMETER; + + // Calculate the end address + uint32_t end_address = data_address + num_wrt_blks; + + // Check if the end address is within the device's boundaries + if (end_address >= sd_card_p->state.sectors) + return SD_BLOCK_DEVICE_ERROR_PARAMETER; + + // Acquire the SD card + sd_acquire(sd_card_p); + + block_dev_err_t status; + + // If writing only one block, use the optimized function + if (1 == num_wrt_blks) { + status = write_block(sd_card_p, buffer, data_address); + } else { + // If writing multiple blocks, retry the operation until it succeeds or reaches the maximum number of retries + unsigned retries = sd_timeouts.sd_command_retries; + do { + if (retries < sd_timeouts.sd_command_retries) DBG_PRINTF("Retrying\n"); + status = in_sd_write_blocks(sd_card_p, &buffer, &data_address, &num_wrt_blks); + if (SD_BLOCK_DEVICE_ERROR_WRITE == status) + DBG_PRINTF("%s status=0x%x data_address=%lu num_wrt_blks=%lu\n", sd_get_drive_prefix(sd_card_p), status, data_address, num_wrt_blks); + } while (SD_BLOCK_DEVICE_ERROR_WRITE == status && --retries && num_wrt_blks); + } + + // Release the SD card + sd_release(sd_card_p); + + return status; +} + +/** + * @brief Synchronize the SD card + * + * This function is used to ensure that any ongoing write operations are + * completed before the SD card is released. This is necessary to ensure + * that the SD card is not released while it is still busy with a write + * operation. + * + * @param sd_card_p Pointer to the SD card object. + * + * @return + * - SD_BLOCK_DEVICE_ERROR_NONE on success + * - SD_BLOCK_DEVICE_ERROR_WRITE if there was a write error + * - SD_BLOCK_DEVICE_ERROR_UNSUPPORTED if the command is unsupported + * - SD_BLOCK_DEVICE_ERROR_NO_INIT if the device is not initialized + * - SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the device (SD card) is missing or not connected + */ +static block_dev_err_t sd_sync(sd_card_t *sd_card_p) { + block_dev_err_t status = SD_BLOCK_DEVICE_ERROR_NONE; + sd_acquire(sd_card_p); + // Stop any ongoing transmission + if (sd_card_p->spi_if_p->state.ongoing_mlt_blk_wrt) status = stop_wr_tran(sd_card_p); + sd_release(sd_card_p); + return status; +} + +/*!< Number of retries for sending CMDO */ +#define SD_CMD0_GO_IDLE_STATE_RETRIES 10 + +/** + * @brief Resets the SD card to the idle state. + * + * This function sends the initializing sequence to the SD card and waits for it to enter the idle state. + * + * @param sd_card_p Pointer to the SD card object. + * @return The response from the SD card. + * @retval R1_IDLE_STATE if the SD card successfully entered the idle state. + * @retval R1_NO_RESPONSE if the SD card did not respond. + */ +static uint32_t in_sd_go_idle_state(sd_card_t *sd_card_p) { + /* + Power ON or card insertion + After supply voltage reached above 2.2 volts, + wait for one millisecond at least. + Set SPI clock rate between 100 kHz and 400 kHz. + Set DI and CS high and apply 74 or more clock pulses to SCLK. + The card will enter its native operating mode and go ready to accept native + command. + */ + sd_spi_go_low_frequency(sd_card_p); + + uint32_t response = R1_NO_RESPONSE; + + /* Resetting the MCU SPI master may not reset the on-board SDCard, in which + * case when MCU power-on occurs the SDCard will resume operations as + * though there was no reset. In this scenario the first CMD0 will + * not be interpreted as a command and get lost. For some cards retrying + * the command overcomes this situation. */ + for (int i = 0; i < SD_CMD0_GO_IDLE_STATE_RETRIES; i++) { + /* + After power up, the host starts the clock and sends the initializing sequence on the CMD line. + This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec, + 74 clocks or the supply-ramp-uptime; the additional 10 clocks + (over the 64 clocks after what the card should be ready for communication) is + provided to eliminate power-up synchronization problems. + */ + // Set DI and CS high and apply 74 or more clock pulses to SCLK: + sd_spi_deselect(sd_card_p); + uint8_t ones[10]; + memset(ones, 0xFF, sizeof ones); + uint32_t start = millis(); + do { + spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones); + } while (millis() - start < 1); + sd_spi_select(sd_card_p); + + sd_cmd(sd_card_p, CMD0_GO_IDLE_STATE, 0x0, false, &response); + if (R1_IDLE_STATE == response) { + break; + } + } + return response; +} +/** + * @brief Resets the SD card to the idle state. + * + * This function sends the initializing sequence to the SD card and waits for it to enter the idle state. + * + * @param sd_card_p Pointer to the SD card object. + * @return The response from the SD card. + * @retval R1_IDLE_STATE if the SD card successfully entered the idle state. + * @retval R1_NO_RESPONSE if the SD card did not respond. + */ +uint32_t sd_go_idle_state(sd_card_t *sd_card_p) { + sd_spi_lock(sd_card_p); + uint32_t response = in_sd_go_idle_state(sd_card_p); + sd_spi_release(sd_card_p); + return response; +} +/** + * @brief Initializes the SD card medium. + * + * This function initializes the SD card medium by following the SD card initialization sequence. + * It transitions the card from SD card mode to SPI mode by sending the CMD0 command and applying + * the initializing sequence. It then sends the CMD8 command to check if the card supports the + * SD version 2.0 specification. If the card rejects the command, it assumes the card is using the + * legacy protocol or is a MMC card. It then enables or disables CRC based on the crc_on flag. + * It reads the OCR (Operating Conditions Register) using the CMD58 command and checks if the card + * supports the voltage range of 3.3V. If not, it sets the card type to CARD_UNKNOWN and returns + * SD_BLOCK_DEVICE_ERROR_UNUSABLE. It sets the card type based on the response of the ACMD41 command. + * If the initialization is successful, it disables or enables CRC and sets the card type based on + * the response of the CMD58 command. It then disconnects the 50 KOhm pull-up resistor on CS (pin 1) + * of the card using the ACMD42_SET_CLR_CARD_DETECT command. + * + * @param sd_card_p Pointer to the SD card object. + * @return The block device error status. + * @retval SD_BLOCK_DEVICE_ERROR_NONE if the initialization is successful. + * @retval SD_BLOCK_DEVICE_ERROR_NO_DEVICE if the card did not respond. + * @retval SD_BLOCK_DEVICE_ERROR_UNUSABLE if the card does not support the voltage range. + */ +static block_dev_err_t sd_init_medium(sd_card_t *sd_card_p) { + int32_t status = SD_BLOCK_DEVICE_ERROR_NONE; + uint32_t response, arg; + + // The card is transitioned from SDCard mode to SPI mode by sending the CMD0 + // + CS Asserted("0") + if (in_sd_go_idle_state(sd_card_p) != R1_IDLE_STATE) { + EMSG_PRINTF("No disk, or could not put SD card in to SPI idle state\n"); + return SD_BLOCK_DEVICE_ERROR_NO_DEVICE; + } + + // Send CMD8, if the card rejects the command then it's probably using the + // legacy protocol, or is a MMC, or just flat-out broken + status = sd_cmd8(sd_card_p); + if (SD_BLOCK_DEVICE_ERROR_NONE != status && SD_BLOCK_DEVICE_ERROR_UNSUPPORTED != status) { + return status; + } + + if (crc_on) { + size_t retries = 3; + do { + // Enable CRC + status = sd_cmd(sd_card_p, CMD59_CRC_ON_OFF, 1, false, 0); + } while (--retries && (SD_BLOCK_DEVICE_ERROR_NONE != status)); + } + + // Read OCR - CMD58 Response contains OCR register + if (SD_BLOCK_DEVICE_ERROR_NONE != + (status = sd_cmd(sd_card_p, CMD58_READ_OCR, 0x0, false, &response))) { + return status; + } + // Check if card supports voltage range: 3.3V + if (!(response & OCR_3_3V)) { + sd_card_p->state.card_type = CARD_UNKNOWN; + status = SD_BLOCK_DEVICE_ERROR_UNUSABLE; + return status; + } + + // HCS is set 1 for HC/XC capacity cards for ACMD41, if supported + arg = 0x0; + if (SDCARD_V2 == sd_card_p->state.card_type) { + arg |= OCR_HCS_CCS; + } + /* Idle state bit in the R1 response of ACMD41 is used by the card to inform + * the host if initialization of ACMD41 is completed. "1" indicates that the + * card is still initializing. "0" indicates completion of initialization. + * The host repeatedly issues ACMD41 until this bit is set to "0". + */ + uint32_t start = millis(); + do { + status = sd_cmd(sd_card_p, ACMD41_SD_SEND_OP_COND, arg, true, &response); + } while (response & R1_IDLE_STATE && millis() - start < sd_timeouts.sd_command); + // Initialization complete: ACMD41 successful + if ((SD_BLOCK_DEVICE_ERROR_NONE != status) || (0x00 != response)) { + sd_card_p->state.card_type = CARD_UNKNOWN; + EMSG_PRINTF("Timeout waiting for card\n"); + return status; + } + + if (SDCARD_V2 == sd_card_p->state.card_type) { + // Get the card capacity CCS: CMD58 + if (SD_BLOCK_DEVICE_ERROR_NONE == + (status = sd_cmd(sd_card_p, CMD58_READ_OCR, 0x0, false, &response))) { + // High Capacity card + if (response & OCR_HCS_CCS) { + sd_card_p->state.card_type = SDCARD_V2HC; + DBG_PRINTF("Card Initialized: High Capacity Card\n"); + } else { + DBG_PRINTF("Card Initialized: Standard Capacity Card: Version 2.x\n"); + } + } + } else { + sd_card_p->state.card_type = SDCARD_V1; + DBG_PRINTF("Card Initialized: Version 1.x Card\n"); + } + + if (!crc_on) { + // Disable CRC + status = sd_cmd(sd_card_p, CMD59_CRC_ON_OFF, 0, false, 0); + } + + /* Disconnect the 50 KOhm pull-up resistor on CS (pin 1) of the card. + The pull-up may be used for card detection. + + At power up this line has a 50KOhm pull up enabled in the card. + This resistor serves two functions Card detection and Mode Selection. + For Mode Selection, the host can drive the line high or let it be pulled high to select SD + mode. If the host wants to select SPI mode it should drive the line low. For Card detection, + the host detects that the line is pulled high. This pull-up should be disconnected by the + user, during regular data transfer, with SET_CLR_CARD_DETECT (ACMD42) command. */ + + status = sd_cmd(sd_card_p, ACMD42_SET_CLR_CARD_DETECT, 0, true, NULL); + + return status; +} + +/** + * @brief Tests the communication with the SD card. + * + * This function is used to test the communication with the SD card. It first checks if the + * SD card is already initialized, and if so, it sends a command to get the card status. If the + * card status is not received, it assumes that the card is no longer present and sets the + * `STA_NOINIT` flag in the card status. If the card is not initialized, it performs a light + * version of the initialization to test the communication. It sends the initializing sequence + * and waits for the card to go idle. If the card responds with a response status, it assumes + * that the communication is successful and returns `true`. If the card does not respond, it + * assumes that something is holding the DO line and returns `false`. + * + * @param sd_card_p Pointer to the SD card object. + * @return `true` if the communication with the SD card is successful, `false` otherwise. + */ +static bool sd_spi_test_com(sd_card_t *sd_card_p) { + // This is allowed to be called before initialization, so ensure mutex is created + if (!mutex_is_initialized(&sd_card_p->state.mutex)) mutex_init(&sd_card_p->state.mutex); + + sd_acquire(sd_card_p); + + bool success = false; + + if (!(sd_card_p->state.m_Status & STA_NOINIT)) { + // SD card is currently initialized + + // Timeout of 0 means only check once + if (sd_wait_ready(sd_card_p, 0)) { + // DO has been released, try to get status + uint32_t response; + for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) { + // Send command over SPI interface + response = sd_cmd_spi(sd_card_p, CMD13_SEND_STATUS, 0); + if (R1_NO_RESPONSE != response) { + // Got a response! + success = true; + break; + } + } + + if (!success) { + // Card no longer sensed - ensure card is initialized once re-attached + sd_card_p->state.m_Status |= STA_NOINIT; + } + } else { + // SD card is currently holding DO which is sufficient enough to know it's still + // there + success = true; + } + } else { + // Do a "light" version of init, just enough to test com + + // Initialize the member variables + sd_card_p->state.card_type = SDCARD_NONE; + + sd_spi_go_low_frequency(sd_card_p); + sd_spi_send_initializing_sequence(sd_card_p); + + if (sd_wait_ready(sd_card_p, 0)) { + // DO has been released, try to make SD card go idle + uint32_t response; + for (unsigned i = 0; i < sd_timeouts.sd_command_retries; i++) { + // Send command over SPI interface + response = sd_cmd_spi(sd_card_p, CMD0_GO_IDLE_STATE, 0); + if (R1_NO_RESPONSE != response) { + // Got a response! + success = true; + break; + } + } + } else { + // Something is holding DO - better to return false and allow user to try again + // later + success = false; + } + } + + sd_release(sd_card_p); + + return success; +} + +/** + * @brief Initializes the SD card over SPI. + * + * This function initializes the SD card over SPI. It first checks if the SD card is already + * initialized, and if so, it returns the current status. If the card is not initialized, it + * performs the initialization sequence and returns the status of the card. The card is + * initialized by sending the initializing sequence, checking the card type, setting the SCK + * for data transfer, and setting the block length to 512. The card is now considered initialized + * and its status is returned. + * + * @param sd_card_p Pointer to the SD card object. + * @return The status of the SD card: + * STA_NOINIT = 0x01, // Drive not initialized + * STA_NODISK = 0x02, // No medium in the drive + * STA_PROTECT = 0x04 // Write protected + */ +DSTATUS sd_card_spi_init(sd_card_t *sd_card_p) { + TRACE_PRINTF("> %s\n", __FUNCTION__); + + // Acquire the lock + sd_lock(sd_card_p); + + // Check if there's a card in the socket before proceeding + sd_card_detect(sd_card_p); + if (sd_card_p->state.m_Status & STA_NODISK) { + // Release the lock and return the current status + sd_unlock(sd_card_p); + return sd_card_p->state.m_Status; + } + // Check if we're not already initialized before proceeding + if (!(sd_card_p->state.m_Status & STA_NOINIT)) { + // Release the lock and return the current status + sd_unlock(sd_card_p); + return sd_card_p->state.m_Status; + } + + // Initialize the member variables + sd_card_p->state.card_type = SDCARD_NONE; + + // Acquire the SD card + sd_spi_acquire(sd_card_p); + + // Initialize the medium + int err = sd_init_medium(sd_card_p); + if (SD_BLOCK_DEVICE_ERROR_NONE != err) { + EMSG_PRINTF("Failed to initialize card\n"); + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + // No support for SDSC Card (CCS=0) with byte unit address + if (SDCARD_V2HC != sd_card_p->state.card_type) { + EMSG_PRINTF("SD Standard Capacity Memory Card unsupported\n"); + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + + DBG_PRINTF("SD card initialized\n"); + + // Set SCK for data transfer + sd_spi_go_high_frequency(sd_card_p); + + // Get the number of sectors on the card + sd_card_p->state.sectors = in_sd_spi_sectors(sd_card_p); + if (0 == sd_card_p->state.sectors) { + // CMD9 failed + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + // Get the CID of the card + if (SD_BLOCK_DEVICE_ERROR_NONE != sd_cmd(sd_card_p, CMD10_SEND_CID, 0x0, false, 0)) { + DBG_PRINTF("Didn't get a response from the disk\n"); + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + if (read_bytes(sd_card_p, (uint8_t *)&sd_card_p->state.CID, sizeof(CID_t)) != 0) { + DBG_PRINTF("Couldn't read CID response from disk\n"); + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + + // Set the block length to 512 (CMD16) + if (SD_BLOCK_DEVICE_ERROR_NONE != + sd_cmd(sd_card_p, CMD16_SET_BLOCKLEN, sd_block_size, false, 0)) { + DBG_PRINTF("Set %u-byte block timed out\n", sd_block_size); + sd_release(sd_card_p); + return sd_card_p->state.m_Status; + } + + // The card is now initialized + sd_card_p->state.m_Status &= ~STA_NOINIT; + + // Release the SD card + sd_release(sd_card_p); + + // Return the disk status + return sd_card_p->state.m_Status; +} + +/** + * @brief Deinitializes the SD card. + * + * This function deinitializes the SD card by setting the STA_NOINIT bit in the + * status field and setting the card type to SDCARD_NONE. It also deinitializes + * the chip select GPIO. + * + * @param sd_card_p Pointer to the sd_card_t structure to be deinitialized. + */ +static void sd_deinit(sd_card_t *sd_card_p) { + sd_card_p->state.m_Status |= STA_NOINIT; + sd_card_p->state.card_type = SDCARD_NONE; + + if ((uint)-1 != sd_card_p->spi_if_p->ss_gpio) { + gpio_deinit(sd_card_p->spi_if_p->ss_gpio); + gpio_set_dir(sd_card_p->spi_if_p->ss_gpio, GPIO_IN); + } +} + +/** + * @brief Initializes the sd_card_t structure. + * + * This function initializes the sd_card_t structure with pointers to the + * respective functions for reading and writing blocks, syncing the card, + * initializing the card, deinitializing the card, getting the number of + * sectors, and testing the card's communication. It also initializes the chip + * select GPIO pin. + * + * @param sd_card_p Pointer to the sd_card_t structure to be initialized. + */ +void sd_spi_ctor(sd_card_t *sd_card_p) { + sd_card_p->write_blocks = sd_write_blocks; + sd_card_p->read_blocks = sd_read_blocks; + sd_card_p->sync = sd_sync; + sd_card_p->init = sd_card_spi_init; + sd_card_p->deinit = sd_deinit; + sd_card_p->get_num_sectors = sd_spi_sectors; + sd_card_p->sd_test_com = sd_spi_test_com; + + // Chip select is active-low, so we'll initialise it to a + // driven-high state. + if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return; + gpio_init(sd_card_p->spi_if_p->ss_gpio); + gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); // Avoid any glitches when enabling output + gpio_set_dir(sd_card_p->spi_if_p->ss_gpio, GPIO_OUT); + gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); // In case set_dir does anything + if (sd_card_p->spi_if_p->set_drive_strength) { + gpio_set_drive_strength(sd_card_p->spi_if_p->ss_gpio, + sd_card_p->spi_if_p->ss_gpio_drive_strength); + } +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/SPI/sd_card_spi.h b/lib/sd_driver/SPI/sd_card_spi.h new file mode 100644 index 0000000..319279b --- /dev/null +++ b/lib/sd_driver/SPI/sd_card_spi.h @@ -0,0 +1,29 @@ +/* sd_card_spi.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#pragma once + +#include "sd_card.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void sd_spi_ctor(sd_card_t *sd_card_p); // Constructor for sd_card_t +uint32_t sd_go_idle_state(sd_card_t *sd_card_p); + +#ifdef __cplusplus +} +#endif +/* [] END OF FILE */ diff --git a/lib/sd_driver/SPI/sd_spi.c b/lib/sd_driver/SPI/sd_spi.c new file mode 100755 index 0000000..258b783 --- /dev/null +++ b/lib/sd_driver/SPI/sd_spi.c @@ -0,0 +1,65 @@ +/* sd_spi.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +/* Standard includes. */ +#include +#include +#include +// +#include "hardware/gpio.h" +// +#include "my_debug.h" +#include "delays.h" +#include "my_spi.h" +// +#if !defined(USE_DBG_PRINTF) || defined(NDEBUG) +# pragma GCC diagnostic ignored "-Wunused-variable" +#endif +// +#include "sd_spi.h" + +// #define TRACE_PRINTF(fmt, args...) +// #define TRACE_PRINTF printf + +void sd_spi_go_high_frequency(sd_card_t *sd_card_p) { + uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, sd_card_p->spi_if_p->spi->baud_rate); + DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual); +} +void sd_spi_go_low_frequency(sd_card_t *sd_card_p) { + uint actual = spi_set_baudrate(sd_card_p->spi_if_p->spi->hw_inst, 400 * 1000); // Actual frequency: 398089 + DBG_PRINTF("%s: Actual frequency: %lu\n", __FUNCTION__, (long)actual); +} + +/* +After power up, the host starts the clock and sends the initializing sequence on the CMD line. +This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec, +74 clocks or the supply-ramp-uptime; the additional 10 clocks +(over the 64 clocks after what the card should be ready for communication) is +provided to eliminate power-up synchronization problems. +*/ +void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p) { + if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return; + bool old_ss = gpio_get(sd_card_p->spi_if_p->ss_gpio); + // Set DI and CS high and apply 74 or more clock pulses to SCLK: + gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); + uint8_t ones[10]; + memset(ones, 0xFF, sizeof ones); + uint32_t start = millis(); + do { + spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones); + } while (millis() - start < 1); + gpio_put(sd_card_p->spi_if_p->ss_gpio, old_ss); +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/SPI/sd_spi.h b/lib/sd_driver/SPI/sd_spi.h new file mode 100755 index 0000000..88d86c2 --- /dev/null +++ b/lib/sd_driver/SPI/sd_spi.h @@ -0,0 +1,135 @@ +/* sd_spi.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#pragma once + +#include +// +#include "pico/stdlib.h" +// +#include "delays.h" +#include "my_debug.h" +#include "my_spi.h" +#include "sd_card.h" +#include "sd_timeouts.h" + +#ifdef NDEBUG +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +void sd_spi_go_low_frequency(sd_card_t *this); +void sd_spi_go_high_frequency(sd_card_t *this); + +/* +After power up, the host starts the clock and sends the initializing sequence on the CMD line. +This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of +1msec, 74 clocks or the supply-ramp-uptime; the additional 10 clocks (over the 64 clocks after +what the card should be ready for communication) is provided to eliminate power-up +synchronization problems. +*/ +void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p); + +//FIXME: sd_spi_read, sd_spi_write, and sd_spi_write_read should return an error code on timeout. + +static inline uint8_t sd_spi_read(sd_card_t *sd_card_p) { + uint8_t received = SPI_FILL_CHAR; + uint32_t start = millis(); + while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) && + millis() - start < sd_timeouts.sd_spi_read) + tight_loop_contents(); + myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst)); + int num = spi_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, SPI_FILL_CHAR, &received, 1); + myASSERT(1 == num); + return received; +} + +static inline void sd_spi_write(sd_card_t *sd_card_p, const uint8_t value) { + uint32_t start = millis(); + while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) && + millis() - start < sd_timeouts.sd_spi_write) + tight_loop_contents(); + myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst)); + int num = spi_write_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, 1); + myASSERT(1 == num); +} +static inline uint8_t sd_spi_write_read(sd_card_t *sd_card_p, const uint8_t value) { + uint8_t received = SPI_FILL_CHAR; + uint32_t start = millis(); + while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) && + millis() - start < sd_timeouts.sd_spi_write_read) + tight_loop_contents(); + myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst)); + int num = spi_write_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, &received, 1); + myASSERT(1 == num); + return received; +} + +// Would do nothing if sd_card_p->spi_if_p->ss_gpio were set to GPIO_FUNC_SPI. +static inline void sd_spi_select(sd_card_t *sd_card_p) { + if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return; + gpio_put(sd_card_p->spi_if_p->ss_gpio, 0); + // See http://elm-chan.org/docs/mmc/mmc_e.html#spibus + sd_spi_write(sd_card_p, SPI_FILL_CHAR); + LED_ON(); +} + +static inline void sd_spi_deselect(sd_card_t *sd_card_p) { + if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return; + gpio_put(sd_card_p->spi_if_p->ss_gpio, 1); + LED_OFF(); + /* + MMC/SDC enables/disables the DO output in synchronising to the SCLK. This + means there is a posibility of bus conflict with MMC/SDC and another SPI + slave that shares an SPI bus. Therefore to make MMC/SDC release the MISO + line, the master device needs to send a byte after the CS signal is + deasserted. + */ + sd_spi_write(sd_card_p, SPI_FILL_CHAR); +} + +static inline void sd_spi_lock(sd_card_t *sd_card_p) { spi_lock(sd_card_p->spi_if_p->spi); } +static inline void sd_spi_unlock(sd_card_t *sd_card_p) { spi_unlock(sd_card_p->spi_if_p->spi); } + +static inline void sd_spi_acquire(sd_card_t *sd_card_p) { + sd_spi_lock(sd_card_p); + sd_spi_select(sd_card_p); +} +static inline void sd_spi_release(sd_card_t *sd_card_p) { + sd_spi_deselect(sd_card_p); + sd_spi_unlock(sd_card_p); +} + +static inline void sd_spi_transfer_start(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx, + size_t length) { + return spi_transfer_start(sd_card_p->spi_if_p->spi, tx, rx, length); +} +static inline bool sd_spi_transfer_wait_complete(sd_card_t *sd_card_p, uint32_t timeout_ms) { + return spi_transfer_wait_complete(sd_card_p->spi_if_p->spi, timeout_ms); +} +/* Transfer tx to SPI while receiving SPI to rx. +tx or rx can be NULL if not important. */ +static inline bool sd_spi_transfer(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx, + size_t length) { + return spi_transfer(sd_card_p->spi_if_p->spi, tx, rx, length); +} + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/crash.c b/lib/sd_driver/crash.c new file mode 100644 index 0000000..18bbc5a --- /dev/null +++ b/lib/sd_driver/crash.c @@ -0,0 +1,312 @@ +/* crash.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#include +#include +// +#include "pico/stdlib.h" +#include "hardware/sync.h" +#if !PICO_RISCV +# if PICO_RP2040 +# include "RP2040.h" +# endif +# if PICO_RP2350 +# include "RP2350.h" +# endif +#else +# include "hardware/watchdog.h" +#endif +// +#include "crc.h" +#include "my_debug.h" +#include "my_rtc.h" +#include "util.h" +// +#include "crash.h" + +#if defined(NDEBUG) || !USE_DBG_PRINTF +# pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +static volatile crash_info_t crash_info_ram __attribute__((section(".uninitialized_data"))); + +static crash_info_t volatile *crash_info_ram_p = &crash_info_ram; + +static crash_info_t previous_crash_info; +static bool _previous_crash_info_valid = false; + +__attribute__((noreturn, always_inline)) +static inline void reset() { +// if (debugger_connected()) { + __breakpoint(); +// } else { +#if !PICO_RISCV + NVIC_SystemReset(); +#else + watchdog_reboot(0, 0, 0); + for (;;) { + __nop(); + } +#endif +// } + __builtin_unreachable(); +} + +void crash_handler_init() { + if (crash_info_ram.magic == crash_magic_hard_fault || + crash_info_ram.magic == crash_magic_stack_overflow || + crash_info_ram.magic == crash_magic_reboot_requested || + crash_info_ram.magic == crash_magic_assert || + crash_info_ram.magic == crash_magic_debug_mon) { + uint8_t xor_checksum = crc7((uint8_t *)crash_info_ram_p, + offsetof(crash_info_t, xor_checksum)); + if (xor_checksum == crash_info_ram.xor_checksum) { + // valid crash record + memcpy(&previous_crash_info, (void *)crash_info_ram_p, sizeof previous_crash_info); + _previous_crash_info_valid = true; + } + } + memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram); +} + +const crash_info_t *crash_handler_get_info() { + if (_previous_crash_info_valid) { + return &previous_crash_info; + } + return NULL; +} + + +__attribute__((noreturn)) +void system_reset_func(char const *const func) { + memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram); + crash_info_ram.magic = crash_magic_reboot_requested; + crash_info_ram.timestamp = epochtime; + snprintf((char *)crash_info_ram.calling_func, sizeof crash_info_ram.calling_func, "%s", + func); + crash_info_ram.xor_checksum = + crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum)); + __dsb(); + + reset(); + __builtin_unreachable(); +} + +__attribute__((noreturn)) +void capture_assert(const char *file, int line, const char *func, const char *pred) { + memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram); + crash_info_ram.magic = crash_magic_assert; + crash_info_ram.timestamp = epochtime; + + // If the filename is too long, take the end: + size_t full_len = strlen(file) + 1; + size_t offset = 0; + if (full_len > sizeof crash_info_ram.assert.file) { + offset = full_len - sizeof crash_info_ram.assert.file; + } + snprintf((char *)crash_info_ram.assert.file, sizeof crash_info_ram.assert.file, "%s", + file + offset); + snprintf((char *)crash_info_ram.assert.func, sizeof crash_info_ram.assert.func, "%s", func); + snprintf((char *)crash_info_ram.assert.pred, sizeof crash_info_ram.assert.pred, "%s", pred); + crash_info_ram.assert.line = line; + crash_info_ram.xor_checksum = + crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum)); + __dsb(); + + reset(); + __builtin_unreachable(); +} + +#if !PICO_RISCV + +__attribute__((used)) extern void DebugMon_HandlerC(uint32_t const *faultStackAddr) { + memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram); + crash_info_ram.magic = crash_magic_debug_mon; + crash_info_ram.timestamp = epochtime; + + /* Stores general registers */ + crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos]; + crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos]; + crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos]; + crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos]; + crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos]; + crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos]; + crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos]; + crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos]; + ///* Stores the Configurable Fault Status Register state with the fault cause */ + //crash_info_ram.cy_faultFrame.cfsr.cfsrReg = SCB->CFSR; + ///* Stores the Hard Fault Status Register */ + //crash_info_ram.cy_faultFrame.hfsr.hfsrReg = SCB->HFSR; + ///* Stores the System Handler Control and State Register */ + //crash_info_ram.cy_faultFrame.shcsr.shcsrReg = SCB->SHCSR; + ///* Store MemMange fault address */ + //crash_info_ram.cy_faultFrame.mmfar = SCB->MMFAR; + ///* Store Bus fault address */ + //crash_info_ram.cy_faultFrame.bfar = SCB->BFAR; + + volatile uint8_t __attribute__((unused)) watchpoint_number = 0; + // if (DWT->FUNCTION0 & DWT_FUNCTION_MATCHED_Msk) { + // watchpoint_number = 0; + //} else if (DWT->FUNCTION1 & DWT_FUNCTION_MATCHED_Msk) { + // watchpoint_number = 1; + //} else if (DWT->FUNCTION2 & DWT_FUNCTION_MATCHED_Msk) { + // watchpoint_number = 2; + //} + crash_info_ram.xor_checksum = + crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum)); + __dsb(); // make sure all data is really written into the memory before + // doing a reset + + reset(); +} + +extern void DebugMon_Handler(void); +__attribute__((naked)) void DebugMon_Handler(void) { + __asm volatile( + " movs r0,#4 \n" + " movs r1, lr \n" + " tst r0, r1 \n" + " beq _MSP2 \n" + " mrs r0, psp \n" + " b _HALT2 \n" + "_MSP2: \n" + " mrs r0, msp \n" + "_HALT2: \n" + " ldr r1,[r0,#20] \n" + " b DebugMon_HandlerC \n"); +} + +void Hardfault_HandlerC(uint32_t const *faultStackAddr) { + memset((void *)crash_info_ram_p, 0, sizeof crash_info_ram); + crash_info_ram.magic = crash_magic_hard_fault; + crash_info_ram.timestamp = epochtime; + + /* Stores general registers */ + crash_info_ram.cy_faultFrame.r0 = faultStackAddr[R0_Pos]; + crash_info_ram.cy_faultFrame.r1 = faultStackAddr[R1_Pos]; + crash_info_ram.cy_faultFrame.r2 = faultStackAddr[R2_Pos]; + crash_info_ram.cy_faultFrame.r3 = faultStackAddr[R3_Pos]; + crash_info_ram.cy_faultFrame.r12 = faultStackAddr[R12_Pos]; + crash_info_ram.cy_faultFrame.lr = faultStackAddr[LR_Pos]; + crash_info_ram.cy_faultFrame.pc = faultStackAddr[PC_Pos]; + crash_info_ram.cy_faultFrame.psr = faultStackAddr[PSR_Pos]; + + crash_info_ram.xor_checksum = + crc7((uint8_t *)&crash_info_ram, offsetof(crash_info_t, xor_checksum)); + __dsb(); // make sure all data is really written into the memory before + // doing a reset + + reset(); +} +__attribute__((naked)) void isr_hardfault(void) { + __asm volatile( + " movs r0,#4 \n" + " movs r1, lr \n" + " tst r0, r1 \n" + " beq _MSP3 \n" + " mrs r0, psp \n" + " b _HALT3 \n" + "_MSP3: \n" + " mrs r0, msp \n" + "_HALT3: \n" + " ldr r1,[r0,#20] \n" + " b Hardfault_HandlerC \n"); +} + +#endif // !PICO_RISCV + +enum { + crash_info_magic, + crash_info_hf_lr, + crash_info_hf_pc, + crash_info_reset_request_line, + crash_info_assert +}; + +int dump_crash_info(crash_info_t const *const crash_info_p, int next, char *const buf, + size_t const buf_sz) { + int nwrit = 0; + switch (next) { + case crash_info_magic: + nwrit = snprintf(buf, buf_sz, "Event: "); + switch (crash_info_p->magic) { + case crash_magic_none: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tNone."); + next = 0; + break; + case crash_magic_bootloader_entry: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tBootloader Entry."); + next = 0; + break; + case crash_magic_hard_fault: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tCM4 Hard Fault."); + next = crash_info_hf_lr; + break; + case crash_magic_debug_mon: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, + "\tDebug Monitor Watchpoint Triggered."); + next = crash_info_hf_lr; + break; + case crash_magic_reboot_requested: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tReboot Requested."); + next = crash_info_reset_request_line; + break; + case crash_magic_assert: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tAssertion Failed."); + next = crash_info_assert; + break; + default: + DBG_ASSERT_CASE_NOT(crash_info_p->magic); + next = 0; + } + { + struct tm tmbuf; + struct tm *ptm = localtime_r(&crash_info_p->timestamp, &tmbuf); + char tsbuf[32]; + size_t n = strftime(tsbuf, sizeof tsbuf, "\n\tTime: %Y-%m-%d %H:%M:%S\n", ptm); + myASSERT(n); + nwrit = snprintf(buf + nwrit, buf_sz - nwrit, "%s", tsbuf); + } + break; + case crash_info_hf_lr: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tLink Register (LR): %p\n", + (void *)crash_info_p->cy_faultFrame.lr); + next = crash_info_hf_pc; + break; + case crash_info_hf_pc: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, "\tProgram Counter (PC): %p\n", + (void *)crash_info_p->cy_faultFrame.pc); + next = 0; + break; + case crash_info_reset_request_line: + nwrit += + snprintf(buf + nwrit, buf_sz - nwrit, "\tReset request calling function: %s\n", + crash_info_p->calling_func); + next = 0; + break; + case crash_info_assert: + nwrit += snprintf(buf + nwrit, buf_sz - nwrit, + "\tAssertion \"%s\" failed: file \"%s\", line " + "%d, function: %s\n", + crash_info_p->assert.pred, crash_info_p->assert.file, + crash_info_p->assert.line, crash_info_p->assert.func); + next = 0; + break; + default: + ASSERT_CASE_NOT(crash_info_p->magic); + } + return next; +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/crash.h b/lib/sd_driver/crash.h new file mode 100644 index 0000000..394e33f --- /dev/null +++ b/lib/sd_driver/crash.h @@ -0,0 +1,114 @@ +/* crash.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +// Original from M0AGX (blog@m0agx.eu), "Preserving debugging breadcrumbs across +// reboots in Cortex-M," +// https://m0agx.eu/2018/08/18/preserving-debugging-breadcrumbs-across-reboots-in-cortex-m/ + +#pragma once + +#include +#include +#include + +// +#include "pico/stdlib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* The crash info section is at the beginning of the RAM, + * that is not initialized by the linker to preserve + * information across reboots. + */ + +/** + * These are the positions of the fault frame elements in the + * fault frame structure. + */ +enum { + R0_Pos = 0, /**< The position of the R0 content in a fault structure */ + R1_Pos, /**< The position of the R1 content in a fault structure */ + R2_Pos, /**< The position of the R2 content in a fault structure */ + R3_Pos, /**< The position of the R3 content in a fault structure */ + R12_Pos, /**< The position of the R12 content in a fault structure */ + LR_Pos, /**< The position of the LR content in a fault structure */ + PC_Pos, /**< The position of the PC content in a fault structure */ + PSR_Pos, /**< The position of the PSR content in a fault structure */ + NUM_REGS, /**< The number of registers in the fault frame */ +}; + +typedef struct { + uint32_t r0; /**< R0 register content */ + uint32_t r1; /**< R1 register content */ + uint32_t r2; /**< R2 register content */ + uint32_t r3; /**< R3 register content */ + uint32_t r12; /**< R12 register content */ + uint32_t lr; /**< LR register content */ + uint32_t pc; /**< PC register content */ + uint32_t psr; /**< PSR register content */ +} __attribute__((packed)) cy_stc_fault_frame_t; + +typedef enum { + crash_magic_none = 0, + crash_magic_bootloader_entry = 0xB000B000, + crash_magic_hard_fault = 0xCAFEBABE, + crash_magic_debug_mon = 0x01020304, + crash_magic_reboot_requested = 0x00ABCDEF, + crash_magic_stack_overflow = 0x0BADBEEF, + crash_magic_assert = 0xDEBDEBDE +} crash_magic_t; + +typedef struct { + char file[32]; + int line; + char func[32]; + char pred[32]; +} crash_assert_t; + +typedef struct { + uint32_t magic; + time_t timestamp; + union { + cy_stc_fault_frame_t cy_faultFrame; + crash_assert_t assert; + char calling_func[64]; + }; + uint8_t xor_checksum; // last to avoid including in calculation +} crash_info_t; + +// Trick to find struct size at compile time: +// char (*__kaboom)[sizeof(crash_info_flash_t)] = 1; +// warning: initialization of 'char (*)[132]' from 'int' makes ... + +void crash_handler_init(); +const crash_info_t *crash_handler_get_info(); +volatile const crash_info_t *crash_handler_get_info_flash(); + +#define SYSTEM_RESET() system_reset_func(__FUNCTION__) +void system_reset_func(char const *const func) __attribute__((noreturn)); + +void capture_assert(const char *file, int line, const char *func, const char *pred) + __attribute__((noreturn)); +void capture_assert_case_not(const char *file, int line, const char *func, int v) + __attribute__((noreturn)); + +int dump_crash_info(crash_info_t const *const pCrashInfo, int next, char *const buf, + size_t const buf_sz); + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/crc.c b/lib/sd_driver/crc.c new file mode 100755 index 0000000..7f3c642 --- /dev/null +++ b/lib/sd_driver/crc.c @@ -0,0 +1,379 @@ +/* crc.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +/* Partially derived from: + * crcany (https://github.com/madler/crcany) + * License + * This code is under the zlib license, permitting free commercial use. + */ + +#include "crc.h" + +const char m_Crc7Table[] = {0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, + 0x3F, 0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, 0x19, 0x10, 0x0B, + 0x02, 0x3D, 0x34, 0x2F, 0x26, 0x51, 0x58, 0x43, 0x4A, 0x75, 0x7C, 0x67, + 0x6E, 0x32, 0x3B, 0x20, 0x29, 0x16, 0x1F, 0x04, 0x0D, 0x7A, 0x73, 0x68, + 0x61, 0x5E, 0x57, 0x4C, 0x45, 0x2B, 0x22, 0x39, 0x30, 0x0F, 0x06, 0x1D, + 0x14, 0x63, 0x6A, 0x71, 0x78, 0x47, 0x4E, 0x55, 0x5C, 0x64, 0x6D, 0x76, + 0x7F, 0x40, 0x49, 0x52, 0x5B, 0x2C, 0x25, 0x3E, 0x37, 0x08, 0x01, 0x1A, + 0x13, 0x7D, 0x74, 0x6F, 0x66, 0x59, 0x50, 0x4B, 0x42, 0x35, 0x3C, 0x27, + 0x2E, 0x11, 0x18, 0x03, 0x0A, 0x56, 0x5F, 0x44, 0x4D, 0x72, 0x7B, 0x60, + 0x69, 0x1E, 0x17, 0x0C, 0x05, 0x3A, 0x33, 0x28, 0x21, 0x4F, 0x46, 0x5D, + 0x54, 0x6B, 0x62, 0x79, 0x70, 0x07, 0x0E, 0x15, 0x1C, 0x23, 0x2A, 0x31, + 0x38, 0x41, 0x48, 0x53, 0x5A, 0x65, 0x6C, 0x77, 0x7E, 0x09, 0x00, 0x1B, + 0x12, 0x2D, 0x24, 0x3F, 0x36, 0x58, 0x51, 0x4A, 0x43, 0x7C, 0x75, 0x6E, + 0x67, 0x10, 0x19, 0x02, 0x0B, 0x34, 0x3D, 0x26, 0x2F, 0x73, 0x7A, 0x61, + 0x68, 0x57, 0x5E, 0x45, 0x4C, 0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0x0D, + 0x04, 0x6A, 0x63, 0x78, 0x71, 0x4E, 0x47, 0x5C, 0x55, 0x22, 0x2B, 0x30, + 0x39, 0x06, 0x0F, 0x14, 0x1D, 0x25, 0x2C, 0x37, 0x3E, 0x01, 0x08, 0x13, + 0x1A, 0x6D, 0x64, 0x7F, 0x76, 0x49, 0x40, 0x5B, 0x52, 0x3C, 0x35, 0x2E, + 0x27, 0x18, 0x11, 0x0A, 0x03, 0x74, 0x7D, 0x66, 0x6F, 0x50, 0x59, 0x42, + 0x4B, 0x17, 0x1E, 0x05, 0x0C, 0x33, 0x3A, 0x21, 0x28, 0x5F, 0x56, 0x4D, + 0x44, 0x7B, 0x72, 0x69, 0x60, 0x0E, 0x07, 0x1C, 0x15, 0x2A, 0x23, 0x38, + 0x31, 0x46, 0x4F, 0x54, 0x5D, 0x62, 0x6B, 0x70, 0x79}; + +/* The CRC check sum is a 16-bit value and is computed as follows: + Generator polynomial G(x) = x^16 +x^12 +x^5 +1 + M(x) = (first bit) * x^n + (second bit)* x^(n-1) +...+ (last bit) * x^0 + CRC[15...0] = Remainder [(M(x) * x^16)/G(x)] + The first bit is the first data bit of the corresponding block. The degree n of the polynomial denotes the + number of bits of the data block decreased by one (e.g. n = 4095 for a block length of 512 bytes). The + generator polynomial G(x) is a standard CCITT polynomial. The code has a minimal distance d=4 and is + used for a payload length of up to 2048 Bytes (n <= 16383). + + A CRC is called an n-bit CRC when its check value is n-bits. + For a given n, multiple CRCs are possible, each with a different polynomial. + Such a polynomial has highest degree n, and hence n + 1 terms (the polynomial has a length of n + 1). + The remainder has length n. + The CRC has a name of the form CRC-n-XXX. + + Omission of the high-order bit of the divisor polynomial: + Since the high-order bit is always 1, and since an n-bit CRC must be defined by + an (n + 1)-bit divisor which overflows an n-bit register, + some writers assume that it is unnecessary to mention the divisor's high-order bit. + Omission of the low-order bit of the divisor polynomial: + Since the low-order bit is always 1, authors such as Philip Koopman represent polynomials + with their high-order bit intact, but without the low-order bit (the x^0 or 1 term). + This convention encodes the polynomial complete with its degree in one integer. + + https://reveng.sourceforge.io/crc-catalogue/all.htm#crc.cat.crc-16-ibm-3740 + https://github.com/madler/crcany + + 16-bit CRC-CCITT specification, is: (CRC-16/IBM-3740? An algorithm commonly misidentified as CRC-CCITT.) + Width = 16 bits + Truncated polynomial = 0x1021 + Initial value = 0xFFFF (This doesn't seem to be how it works for SD cards!) + Input data is NOT reflected + Output CRC is NOT reflected + No XOR is performed on the output CRC + + Polynomial representations + Normal Reversed Reciprocal Reversed reciprocal + 0x1021 0x8408 0x811 0x8810 + + 1 + 2109876543210 + 0x1021 = 0b1000000100001 = 4129 + */ + +static uint16_t const table_byte[] = { + 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 +}; + +static uint16_t const table_word[][256] = { + {0x0000, 0x2110, 0x4220, 0x6330, 0x8440, 0xa550, 0xc660, 0xe770, 0x0881, 0x2991, + 0x4aa1, 0x6bb1, 0x8cc1, 0xadd1, 0xcee1, 0xeff1, 0x3112, 0x1002, 0x7332, 0x5222, + 0xb552, 0x9442, 0xf772, 0xd662, 0x3993, 0x1883, 0x7bb3, 0x5aa3, 0xbdd3, 0x9cc3, + 0xfff3, 0xdee3, 0x6224, 0x4334, 0x2004, 0x0114, 0xe664, 0xc774, 0xa444, 0x8554, + 0x6aa5, 0x4bb5, 0x2885, 0x0995, 0xeee5, 0xcff5, 0xacc5, 0x8dd5, 0x5336, 0x7226, + 0x1116, 0x3006, 0xd776, 0xf666, 0x9556, 0xb446, 0x5bb7, 0x7aa7, 0x1997, 0x3887, + 0xdff7, 0xfee7, 0x9dd7, 0xbcc7, 0xc448, 0xe558, 0x8668, 0xa778, 0x4008, 0x6118, + 0x0228, 0x2338, 0xccc9, 0xedd9, 0x8ee9, 0xaff9, 0x4889, 0x6999, 0x0aa9, 0x2bb9, + 0xf55a, 0xd44a, 0xb77a, 0x966a, 0x711a, 0x500a, 0x333a, 0x122a, 0xfddb, 0xdccb, + 0xbffb, 0x9eeb, 0x799b, 0x588b, 0x3bbb, 0x1aab, 0xa66c, 0x877c, 0xe44c, 0xc55c, + 0x222c, 0x033c, 0x600c, 0x411c, 0xaeed, 0x8ffd, 0xeccd, 0xcddd, 0x2aad, 0x0bbd, + 0x688d, 0x499d, 0x977e, 0xb66e, 0xd55e, 0xf44e, 0x133e, 0x322e, 0x511e, 0x700e, + 0x9fff, 0xbeef, 0xdddf, 0xfccf, 0x1bbf, 0x3aaf, 0x599f, 0x788f, 0x8891, 0xa981, + 0xcab1, 0xeba1, 0x0cd1, 0x2dc1, 0x4ef1, 0x6fe1, 0x8010, 0xa100, 0xc230, 0xe320, + 0x0450, 0x2540, 0x4670, 0x6760, 0xb983, 0x9893, 0xfba3, 0xdab3, 0x3dc3, 0x1cd3, + 0x7fe3, 0x5ef3, 0xb102, 0x9012, 0xf322, 0xd232, 0x3542, 0x1452, 0x7762, 0x5672, + 0xeab5, 0xcba5, 0xa895, 0x8985, 0x6ef5, 0x4fe5, 0x2cd5, 0x0dc5, 0xe234, 0xc324, + 0xa014, 0x8104, 0x6674, 0x4764, 0x2454, 0x0544, 0xdba7, 0xfab7, 0x9987, 0xb897, + 0x5fe7, 0x7ef7, 0x1dc7, 0x3cd7, 0xd326, 0xf236, 0x9106, 0xb016, 0x5766, 0x7676, + 0x1546, 0x3456, 0x4cd9, 0x6dc9, 0x0ef9, 0x2fe9, 0xc899, 0xe989, 0x8ab9, 0xaba9, + 0x4458, 0x6548, 0x0678, 0x2768, 0xc018, 0xe108, 0x8238, 0xa328, 0x7dcb, 0x5cdb, + 0x3feb, 0x1efb, 0xf98b, 0xd89b, 0xbbab, 0x9abb, 0x754a, 0x545a, 0x376a, 0x167a, + 0xf10a, 0xd01a, 0xb32a, 0x923a, 0x2efd, 0x0fed, 0x6cdd, 0x4dcd, 0xaabd, 0x8bad, + 0xe89d, 0xc98d, 0x267c, 0x076c, 0x645c, 0x454c, 0xa23c, 0x832c, 0xe01c, 0xc10c, + 0x1fef, 0x3eff, 0x5dcf, 0x7cdf, 0x9baf, 0xbabf, 0xd98f, 0xf89f, 0x176e, 0x367e, + 0x554e, 0x745e, 0x932e, 0xb23e, 0xd10e, 0xf01e}, + {0x0000, 0x3133, 0x6266, 0x5355, 0xc4cc, 0xf5ff, 0xa6aa, 0x9799, 0xa989, 0x98ba, + 0xcbef, 0xfadc, 0x6d45, 0x5c76, 0x0f23, 0x3e10, 0x7303, 0x4230, 0x1165, 0x2056, + 0xb7cf, 0x86fc, 0xd5a9, 0xe49a, 0xda8a, 0xebb9, 0xb8ec, 0x89df, 0x1e46, 0x2f75, + 0x7c20, 0x4d13, 0xe606, 0xd735, 0x8460, 0xb553, 0x22ca, 0x13f9, 0x40ac, 0x719f, + 0x4f8f, 0x7ebc, 0x2de9, 0x1cda, 0x8b43, 0xba70, 0xe925, 0xd816, 0x9505, 0xa436, + 0xf763, 0xc650, 0x51c9, 0x60fa, 0x33af, 0x029c, 0x3c8c, 0x0dbf, 0x5eea, 0x6fd9, + 0xf840, 0xc973, 0x9a26, 0xab15, 0xcc0d, 0xfd3e, 0xae6b, 0x9f58, 0x08c1, 0x39f2, + 0x6aa7, 0x5b94, 0x6584, 0x54b7, 0x07e2, 0x36d1, 0xa148, 0x907b, 0xc32e, 0xf21d, + 0xbf0e, 0x8e3d, 0xdd68, 0xec5b, 0x7bc2, 0x4af1, 0x19a4, 0x2897, 0x1687, 0x27b4, + 0x74e1, 0x45d2, 0xd24b, 0xe378, 0xb02d, 0x811e, 0x2a0b, 0x1b38, 0x486d, 0x795e, + 0xeec7, 0xdff4, 0x8ca1, 0xbd92, 0x8382, 0xb2b1, 0xe1e4, 0xd0d7, 0x474e, 0x767d, + 0x2528, 0x141b, 0x5908, 0x683b, 0x3b6e, 0x0a5d, 0x9dc4, 0xacf7, 0xffa2, 0xce91, + 0xf081, 0xc1b2, 0x92e7, 0xa3d4, 0x344d, 0x057e, 0x562b, 0x6718, 0x981b, 0xa928, + 0xfa7d, 0xcb4e, 0x5cd7, 0x6de4, 0x3eb1, 0x0f82, 0x3192, 0x00a1, 0x53f4, 0x62c7, + 0xf55e, 0xc46d, 0x9738, 0xa60b, 0xeb18, 0xda2b, 0x897e, 0xb84d, 0x2fd4, 0x1ee7, + 0x4db2, 0x7c81, 0x4291, 0x73a2, 0x20f7, 0x11c4, 0x865d, 0xb76e, 0xe43b, 0xd508, + 0x7e1d, 0x4f2e, 0x1c7b, 0x2d48, 0xbad1, 0x8be2, 0xd8b7, 0xe984, 0xd794, 0xe6a7, + 0xb5f2, 0x84c1, 0x1358, 0x226b, 0x713e, 0x400d, 0x0d1e, 0x3c2d, 0x6f78, 0x5e4b, + 0xc9d2, 0xf8e1, 0xabb4, 0x9a87, 0xa497, 0x95a4, 0xc6f1, 0xf7c2, 0x605b, 0x5168, + 0x023d, 0x330e, 0x5416, 0x6525, 0x3670, 0x0743, 0x90da, 0xa1e9, 0xf2bc, 0xc38f, + 0xfd9f, 0xccac, 0x9ff9, 0xaeca, 0x3953, 0x0860, 0x5b35, 0x6a06, 0x2715, 0x1626, + 0x4573, 0x7440, 0xe3d9, 0xd2ea, 0x81bf, 0xb08c, 0x8e9c, 0xbfaf, 0xecfa, 0xddc9, + 0x4a50, 0x7b63, 0x2836, 0x1905, 0xb210, 0x8323, 0xd076, 0xe145, 0x76dc, 0x47ef, + 0x14ba, 0x2589, 0x1b99, 0x2aaa, 0x79ff, 0x48cc, 0xdf55, 0xee66, 0xbd33, 0x8c00, + 0xc113, 0xf020, 0xa375, 0x9246, 0x05df, 0x34ec, 0x67b9, 0x568a, 0x689a, 0x59a9, + 0x0afc, 0x3bcf, 0xac56, 0x9d65, 0xce30, 0xff03}, + {0x0000, 0x3037, 0x606e, 0x5059, 0xc0dc, 0xf0eb, 0xa0b2, 0x9085, 0xa1a9, 0x919e, + 0xc1c7, 0xf1f0, 0x6175, 0x5142, 0x011b, 0x312c, 0x6343, 0x5374, 0x032d, 0x331a, + 0xa39f, 0x93a8, 0xc3f1, 0xf3c6, 0xc2ea, 0xf2dd, 0xa284, 0x92b3, 0x0236, 0x3201, + 0x6258, 0x526f, 0xc686, 0xf6b1, 0xa6e8, 0x96df, 0x065a, 0x366d, 0x6634, 0x5603, + 0x672f, 0x5718, 0x0741, 0x3776, 0xa7f3, 0x97c4, 0xc79d, 0xf7aa, 0xa5c5, 0x95f2, + 0xc5ab, 0xf59c, 0x6519, 0x552e, 0x0577, 0x3540, 0x046c, 0x345b, 0x6402, 0x5435, + 0xc4b0, 0xf487, 0xa4de, 0x94e9, 0xad1d, 0x9d2a, 0xcd73, 0xfd44, 0x6dc1, 0x5df6, + 0x0daf, 0x3d98, 0x0cb4, 0x3c83, 0x6cda, 0x5ced, 0xcc68, 0xfc5f, 0xac06, 0x9c31, + 0xce5e, 0xfe69, 0xae30, 0x9e07, 0x0e82, 0x3eb5, 0x6eec, 0x5edb, 0x6ff7, 0x5fc0, + 0x0f99, 0x3fae, 0xaf2b, 0x9f1c, 0xcf45, 0xff72, 0x6b9b, 0x5bac, 0x0bf5, 0x3bc2, + 0xab47, 0x9b70, 0xcb29, 0xfb1e, 0xca32, 0xfa05, 0xaa5c, 0x9a6b, 0x0aee, 0x3ad9, + 0x6a80, 0x5ab7, 0x08d8, 0x38ef, 0x68b6, 0x5881, 0xc804, 0xf833, 0xa86a, 0x985d, + 0xa971, 0x9946, 0xc91f, 0xf928, 0x69ad, 0x599a, 0x09c3, 0x39f4, 0x5a3b, 0x6a0c, + 0x3a55, 0x0a62, 0x9ae7, 0xaad0, 0xfa89, 0xcabe, 0xfb92, 0xcba5, 0x9bfc, 0xabcb, + 0x3b4e, 0x0b79, 0x5b20, 0x6b17, 0x3978, 0x094f, 0x5916, 0x6921, 0xf9a4, 0xc993, + 0x99ca, 0xa9fd, 0x98d1, 0xa8e6, 0xf8bf, 0xc888, 0x580d, 0x683a, 0x3863, 0x0854, + 0x9cbd, 0xac8a, 0xfcd3, 0xcce4, 0x5c61, 0x6c56, 0x3c0f, 0x0c38, 0x3d14, 0x0d23, + 0x5d7a, 0x6d4d, 0xfdc8, 0xcdff, 0x9da6, 0xad91, 0xfffe, 0xcfc9, 0x9f90, 0xafa7, + 0x3f22, 0x0f15, 0x5f4c, 0x6f7b, 0x5e57, 0x6e60, 0x3e39, 0x0e0e, 0x9e8b, 0xaebc, + 0xfee5, 0xced2, 0xf726, 0xc711, 0x9748, 0xa77f, 0x37fa, 0x07cd, 0x5794, 0x67a3, + 0x568f, 0x66b8, 0x36e1, 0x06d6, 0x9653, 0xa664, 0xf63d, 0xc60a, 0x9465, 0xa452, + 0xf40b, 0xc43c, 0x54b9, 0x648e, 0x34d7, 0x04e0, 0x35cc, 0x05fb, 0x55a2, 0x6595, + 0xf510, 0xc527, 0x957e, 0xa549, 0x31a0, 0x0197, 0x51ce, 0x61f9, 0xf17c, 0xc14b, + 0x9112, 0xa125, 0x9009, 0xa03e, 0xf067, 0xc050, 0x50d5, 0x60e2, 0x30bb, 0x008c, + 0x52e3, 0x62d4, 0x328d, 0x02ba, 0x923f, 0xa208, 0xf251, 0xc266, 0xf34a, 0xc37d, + 0x9324, 0xa313, 0x3396, 0x03a1, 0x53f8, 0x63cf}, + {0x0000, 0xb476, 0x68ed, 0xdc9b, 0xf1ca, 0x45bc, 0x9927, 0x2d51, 0xc385, 0x77f3, + 0xab68, 0x1f1e, 0x324f, 0x8639, 0x5aa2, 0xeed4, 0xa71b, 0x136d, 0xcff6, 0x7b80, + 0x56d1, 0xe2a7, 0x3e3c, 0x8a4a, 0x649e, 0xd0e8, 0x0c73, 0xb805, 0x9554, 0x2122, + 0xfdb9, 0x49cf, 0x4e37, 0xfa41, 0x26da, 0x92ac, 0xbffd, 0x0b8b, 0xd710, 0x6366, + 0x8db2, 0x39c4, 0xe55f, 0x5129, 0x7c78, 0xc80e, 0x1495, 0xa0e3, 0xe92c, 0x5d5a, + 0x81c1, 0x35b7, 0x18e6, 0xac90, 0x700b, 0xc47d, 0x2aa9, 0x9edf, 0x4244, 0xf632, + 0xdb63, 0x6f15, 0xb38e, 0x07f8, 0x9c6e, 0x2818, 0xf483, 0x40f5, 0x6da4, 0xd9d2, + 0x0549, 0xb13f, 0x5feb, 0xeb9d, 0x3706, 0x8370, 0xae21, 0x1a57, 0xc6cc, 0x72ba, + 0x3b75, 0x8f03, 0x5398, 0xe7ee, 0xcabf, 0x7ec9, 0xa252, 0x1624, 0xf8f0, 0x4c86, + 0x901d, 0x246b, 0x093a, 0xbd4c, 0x61d7, 0xd5a1, 0xd259, 0x662f, 0xbab4, 0x0ec2, + 0x2393, 0x97e5, 0x4b7e, 0xff08, 0x11dc, 0xa5aa, 0x7931, 0xcd47, 0xe016, 0x5460, + 0x88fb, 0x3c8d, 0x7542, 0xc134, 0x1daf, 0xa9d9, 0x8488, 0x30fe, 0xec65, 0x5813, + 0xb6c7, 0x02b1, 0xde2a, 0x6a5c, 0x470d, 0xf37b, 0x2fe0, 0x9b96, 0x38dd, 0x8cab, + 0x5030, 0xe446, 0xc917, 0x7d61, 0xa1fa, 0x158c, 0xfb58, 0x4f2e, 0x93b5, 0x27c3, + 0x0a92, 0xbee4, 0x627f, 0xd609, 0x9fc6, 0x2bb0, 0xf72b, 0x435d, 0x6e0c, 0xda7a, + 0x06e1, 0xb297, 0x5c43, 0xe835, 0x34ae, 0x80d8, 0xad89, 0x19ff, 0xc564, 0x7112, + 0x76ea, 0xc29c, 0x1e07, 0xaa71, 0x8720, 0x3356, 0xefcd, 0x5bbb, 0xb56f, 0x0119, + 0xdd82, 0x69f4, 0x44a5, 0xf0d3, 0x2c48, 0x983e, 0xd1f1, 0x6587, 0xb91c, 0x0d6a, + 0x203b, 0x944d, 0x48d6, 0xfca0, 0x1274, 0xa602, 0x7a99, 0xceef, 0xe3be, 0x57c8, + 0x8b53, 0x3f25, 0xa4b3, 0x10c5, 0xcc5e, 0x7828, 0x5579, 0xe10f, 0x3d94, 0x89e2, + 0x6736, 0xd340, 0x0fdb, 0xbbad, 0x96fc, 0x228a, 0xfe11, 0x4a67, 0x03a8, 0xb7de, + 0x6b45, 0xdf33, 0xf262, 0x4614, 0x9a8f, 0x2ef9, 0xc02d, 0x745b, 0xa8c0, 0x1cb6, + 0x31e7, 0x8591, 0x590a, 0xed7c, 0xea84, 0x5ef2, 0x8269, 0x361f, 0x1b4e, 0xaf38, + 0x73a3, 0xc7d5, 0x2901, 0x9d77, 0x41ec, 0xf59a, 0xd8cb, 0x6cbd, 0xb026, 0x0450, + 0x4d9f, 0xf9e9, 0x2572, 0x9104, 0xbc55, 0x0823, 0xd4b8, 0x60ce, 0x8e1a, 0x3a6c, + 0xe6f7, 0x5281, 0x7fd0, 0xcba6, 0x173d, 0xa34b}, + {0x0000, 0x51aa, 0x8344, 0xd2ee, 0x0689, 0x5723, 0x85cd, 0xd467, 0x2d02, 0x7ca8, + 0xae46, 0xffec, 0x2b8b, 0x7a21, 0xa8cf, 0xf965, 0x5a04, 0x0bae, 0xd940, 0x88ea, + 0x5c8d, 0x0d27, 0xdfc9, 0x8e63, 0x7706, 0x26ac, 0xf442, 0xa5e8, 0x718f, 0x2025, + 0xf2cb, 0xa361, 0xb408, 0xe5a2, 0x374c, 0x66e6, 0xb281, 0xe32b, 0x31c5, 0x606f, + 0x990a, 0xc8a0, 0x1a4e, 0x4be4, 0x9f83, 0xce29, 0x1cc7, 0x4d6d, 0xee0c, 0xbfa6, + 0x6d48, 0x3ce2, 0xe885, 0xb92f, 0x6bc1, 0x3a6b, 0xc30e, 0x92a4, 0x404a, 0x11e0, + 0xc587, 0x942d, 0x46c3, 0x1769, 0x6811, 0x39bb, 0xeb55, 0xbaff, 0x6e98, 0x3f32, + 0xeddc, 0xbc76, 0x4513, 0x14b9, 0xc657, 0x97fd, 0x439a, 0x1230, 0xc0de, 0x9174, + 0x3215, 0x63bf, 0xb151, 0xe0fb, 0x349c, 0x6536, 0xb7d8, 0xe672, 0x1f17, 0x4ebd, + 0x9c53, 0xcdf9, 0x199e, 0x4834, 0x9ada, 0xcb70, 0xdc19, 0x8db3, 0x5f5d, 0x0ef7, + 0xda90, 0x8b3a, 0x59d4, 0x087e, 0xf11b, 0xa0b1, 0x725f, 0x23f5, 0xf792, 0xa638, + 0x74d6, 0x257c, 0x861d, 0xd7b7, 0x0559, 0x54f3, 0x8094, 0xd13e, 0x03d0, 0x527a, + 0xab1f, 0xfab5, 0x285b, 0x79f1, 0xad96, 0xfc3c, 0x2ed2, 0x7f78, 0xd022, 0x8188, + 0x5366, 0x02cc, 0xd6ab, 0x8701, 0x55ef, 0x0445, 0xfd20, 0xac8a, 0x7e64, 0x2fce, + 0xfba9, 0xaa03, 0x78ed, 0x2947, 0x8a26, 0xdb8c, 0x0962, 0x58c8, 0x8caf, 0xdd05, + 0x0feb, 0x5e41, 0xa724, 0xf68e, 0x2460, 0x75ca, 0xa1ad, 0xf007, 0x22e9, 0x7343, + 0x642a, 0x3580, 0xe76e, 0xb6c4, 0x62a3, 0x3309, 0xe1e7, 0xb04d, 0x4928, 0x1882, + 0xca6c, 0x9bc6, 0x4fa1, 0x1e0b, 0xcce5, 0x9d4f, 0x3e2e, 0x6f84, 0xbd6a, 0xecc0, + 0x38a7, 0x690d, 0xbbe3, 0xea49, 0x132c, 0x4286, 0x9068, 0xc1c2, 0x15a5, 0x440f, + 0x96e1, 0xc74b, 0xb833, 0xe999, 0x3b77, 0x6add, 0xbeba, 0xef10, 0x3dfe, 0x6c54, + 0x9531, 0xc49b, 0x1675, 0x47df, 0x93b8, 0xc212, 0x10fc, 0x4156, 0xe237, 0xb39d, + 0x6173, 0x30d9, 0xe4be, 0xb514, 0x67fa, 0x3650, 0xcf35, 0x9e9f, 0x4c71, 0x1ddb, + 0xc9bc, 0x9816, 0x4af8, 0x1b52, 0x0c3b, 0x5d91, 0x8f7f, 0xded5, 0x0ab2, 0x5b18, + 0x89f6, 0xd85c, 0x2139, 0x7093, 0xa27d, 0xf3d7, 0x27b0, 0x761a, 0xa4f4, 0xf55e, + 0x563f, 0x0795, 0xd57b, 0x84d1, 0x50b6, 0x011c, 0xd3f2, 0x8258, 0x7b3d, 0x2a97, + 0xf879, 0xa9d3, 0x7db4, 0x2c1e, 0xfef0, 0xaf5a}, + {0x0000, 0xa045, 0x408b, 0xe0ce, 0xa106, 0x0143, 0xe18d, 0x41c8, 0x420d, 0xe248, + 0x0286, 0xa2c3, 0xe30b, 0x434e, 0xa380, 0x03c5, 0x841a, 0x245f, 0xc491, 0x64d4, + 0x251c, 0x8559, 0x6597, 0xc5d2, 0xc617, 0x6652, 0x869c, 0x26d9, 0x6711, 0xc754, + 0x279a, 0x87df, 0x0835, 0xa870, 0x48be, 0xe8fb, 0xa933, 0x0976, 0xe9b8, 0x49fd, + 0x4a38, 0xea7d, 0x0ab3, 0xaaf6, 0xeb3e, 0x4b7b, 0xabb5, 0x0bf0, 0x8c2f, 0x2c6a, + 0xcca4, 0x6ce1, 0x2d29, 0x8d6c, 0x6da2, 0xcde7, 0xce22, 0x6e67, 0x8ea9, 0x2eec, + 0x6f24, 0xcf61, 0x2faf, 0x8fea, 0x106a, 0xb02f, 0x50e1, 0xf0a4, 0xb16c, 0x1129, + 0xf1e7, 0x51a2, 0x5267, 0xf222, 0x12ec, 0xb2a9, 0xf361, 0x5324, 0xb3ea, 0x13af, + 0x9470, 0x3435, 0xd4fb, 0x74be, 0x3576, 0x9533, 0x75fd, 0xd5b8, 0xd67d, 0x7638, + 0x96f6, 0x36b3, 0x777b, 0xd73e, 0x37f0, 0x97b5, 0x185f, 0xb81a, 0x58d4, 0xf891, + 0xb959, 0x191c, 0xf9d2, 0x5997, 0x5a52, 0xfa17, 0x1ad9, 0xba9c, 0xfb54, 0x5b11, + 0xbbdf, 0x1b9a, 0x9c45, 0x3c00, 0xdcce, 0x7c8b, 0x3d43, 0x9d06, 0x7dc8, 0xdd8d, + 0xde48, 0x7e0d, 0x9ec3, 0x3e86, 0x7f4e, 0xdf0b, 0x3fc5, 0x9f80, 0x20d4, 0x8091, + 0x605f, 0xc01a, 0x81d2, 0x2197, 0xc159, 0x611c, 0x62d9, 0xc29c, 0x2252, 0x8217, + 0xc3df, 0x639a, 0x8354, 0x2311, 0xa4ce, 0x048b, 0xe445, 0x4400, 0x05c8, 0xa58d, + 0x4543, 0xe506, 0xe6c3, 0x4686, 0xa648, 0x060d, 0x47c5, 0xe780, 0x074e, 0xa70b, + 0x28e1, 0x88a4, 0x686a, 0xc82f, 0x89e7, 0x29a2, 0xc96c, 0x6929, 0x6aec, 0xcaa9, + 0x2a67, 0x8a22, 0xcbea, 0x6baf, 0x8b61, 0x2b24, 0xacfb, 0x0cbe, 0xec70, 0x4c35, + 0x0dfd, 0xadb8, 0x4d76, 0xed33, 0xeef6, 0x4eb3, 0xae7d, 0x0e38, 0x4ff0, 0xefb5, + 0x0f7b, 0xaf3e, 0x30be, 0x90fb, 0x7035, 0xd070, 0x91b8, 0x31fd, 0xd133, 0x7176, + 0x72b3, 0xd2f6, 0x3238, 0x927d, 0xd3b5, 0x73f0, 0x933e, 0x337b, 0xb4a4, 0x14e1, + 0xf42f, 0x546a, 0x15a2, 0xb5e7, 0x5529, 0xf56c, 0xf6a9, 0x56ec, 0xb622, 0x1667, + 0x57af, 0xf7ea, 0x1724, 0xb761, 0x388b, 0x98ce, 0x7800, 0xd845, 0x998d, 0x39c8, + 0xd906, 0x7943, 0x7a86, 0xdac3, 0x3a0d, 0x9a48, 0xdb80, 0x7bc5, 0x9b0b, 0x3b4e, + 0xbc91, 0x1cd4, 0xfc1a, 0x5c5f, 0x1d97, 0xbdd2, 0x5d1c, 0xfd59, 0xfe9c, 0x5ed9, + 0xbe17, 0x1e52, 0x5f9a, 0xffdf, 0x1f11, 0xbf54}, + {0x0000, 0x61b8, 0xe360, 0x82d8, 0xc6c1, 0xa779, 0x25a1, 0x4419, 0xad93, 0xcc2b, + 0x4ef3, 0x2f4b, 0x6b52, 0x0aea, 0x8832, 0xe98a, 0x7b37, 0x1a8f, 0x9857, 0xf9ef, + 0xbdf6, 0xdc4e, 0x5e96, 0x3f2e, 0xd6a4, 0xb71c, 0x35c4, 0x547c, 0x1065, 0x71dd, + 0xf305, 0x92bd, 0xf66e, 0x97d6, 0x150e, 0x74b6, 0x30af, 0x5117, 0xd3cf, 0xb277, + 0x5bfd, 0x3a45, 0xb89d, 0xd925, 0x9d3c, 0xfc84, 0x7e5c, 0x1fe4, 0x8d59, 0xece1, + 0x6e39, 0x0f81, 0x4b98, 0x2a20, 0xa8f8, 0xc940, 0x20ca, 0x4172, 0xc3aa, 0xa212, + 0xe60b, 0x87b3, 0x056b, 0x64d3, 0xecdd, 0x8d65, 0x0fbd, 0x6e05, 0x2a1c, 0x4ba4, + 0xc97c, 0xa8c4, 0x414e, 0x20f6, 0xa22e, 0xc396, 0x878f, 0xe637, 0x64ef, 0x0557, + 0x97ea, 0xf652, 0x748a, 0x1532, 0x512b, 0x3093, 0xb24b, 0xd3f3, 0x3a79, 0x5bc1, + 0xd919, 0xb8a1, 0xfcb8, 0x9d00, 0x1fd8, 0x7e60, 0x1ab3, 0x7b0b, 0xf9d3, 0x986b, + 0xdc72, 0xbdca, 0x3f12, 0x5eaa, 0xb720, 0xd698, 0x5440, 0x35f8, 0x71e1, 0x1059, + 0x9281, 0xf339, 0x6184, 0x003c, 0x82e4, 0xe35c, 0xa745, 0xc6fd, 0x4425, 0x259d, + 0xcc17, 0xadaf, 0x2f77, 0x4ecf, 0x0ad6, 0x6b6e, 0xe9b6, 0x880e, 0xf9ab, 0x9813, + 0x1acb, 0x7b73, 0x3f6a, 0x5ed2, 0xdc0a, 0xbdb2, 0x5438, 0x3580, 0xb758, 0xd6e0, + 0x92f9, 0xf341, 0x7199, 0x1021, 0x829c, 0xe324, 0x61fc, 0x0044, 0x445d, 0x25e5, + 0xa73d, 0xc685, 0x2f0f, 0x4eb7, 0xcc6f, 0xadd7, 0xe9ce, 0x8876, 0x0aae, 0x6b16, + 0x0fc5, 0x6e7d, 0xeca5, 0x8d1d, 0xc904, 0xa8bc, 0x2a64, 0x4bdc, 0xa256, 0xc3ee, + 0x4136, 0x208e, 0x6497, 0x052f, 0x87f7, 0xe64f, 0x74f2, 0x154a, 0x9792, 0xf62a, + 0xb233, 0xd38b, 0x5153, 0x30eb, 0xd961, 0xb8d9, 0x3a01, 0x5bb9, 0x1fa0, 0x7e18, + 0xfcc0, 0x9d78, 0x1576, 0x74ce, 0xf616, 0x97ae, 0xd3b7, 0xb20f, 0x30d7, 0x516f, + 0xb8e5, 0xd95d, 0x5b85, 0x3a3d, 0x7e24, 0x1f9c, 0x9d44, 0xfcfc, 0x6e41, 0x0ff9, + 0x8d21, 0xec99, 0xa880, 0xc938, 0x4be0, 0x2a58, 0xc3d2, 0xa26a, 0x20b2, 0x410a, + 0x0513, 0x64ab, 0xe673, 0x87cb, 0xe318, 0x82a0, 0x0078, 0x61c0, 0x25d9, 0x4461, + 0xc6b9, 0xa701, 0x4e8b, 0x2f33, 0xadeb, 0xcc53, 0x884a, 0xe9f2, 0x6b2a, 0x0a92, + 0x982f, 0xf997, 0x7b4f, 0x1af7, 0x5eee, 0x3f56, 0xbd8e, 0xdc36, 0x35bc, 0x5404, + 0xd6dc, 0xb764, 0xf37d, 0x92c5, 0x101d, 0x71a5}, + {0x0000, 0xd347, 0xa68f, 0x75c8, 0x6d0f, 0xbe48, 0xcb80, 0x18c7, 0xda1e, 0x0959, + 0x7c91, 0xafd6, 0xb711, 0x6456, 0x119e, 0xc2d9, 0xb43d, 0x677a, 0x12b2, 0xc1f5, + 0xd932, 0x0a75, 0x7fbd, 0xacfa, 0x6e23, 0xbd64, 0xc8ac, 0x1beb, 0x032c, 0xd06b, + 0xa5a3, 0x76e4, 0x687b, 0xbb3c, 0xcef4, 0x1db3, 0x0574, 0xd633, 0xa3fb, 0x70bc, + 0xb265, 0x6122, 0x14ea, 0xc7ad, 0xdf6a, 0x0c2d, 0x79e5, 0xaaa2, 0xdc46, 0x0f01, + 0x7ac9, 0xa98e, 0xb149, 0x620e, 0x17c6, 0xc481, 0x0658, 0xd51f, 0xa0d7, 0x7390, + 0x6b57, 0xb810, 0xcdd8, 0x1e9f, 0xd0f6, 0x03b1, 0x7679, 0xa53e, 0xbdf9, 0x6ebe, + 0x1b76, 0xc831, 0x0ae8, 0xd9af, 0xac67, 0x7f20, 0x67e7, 0xb4a0, 0xc168, 0x122f, + 0x64cb, 0xb78c, 0xc244, 0x1103, 0x09c4, 0xda83, 0xaf4b, 0x7c0c, 0xbed5, 0x6d92, + 0x185a, 0xcb1d, 0xd3da, 0x009d, 0x7555, 0xa612, 0xb88d, 0x6bca, 0x1e02, 0xcd45, + 0xd582, 0x06c5, 0x730d, 0xa04a, 0x6293, 0xb1d4, 0xc41c, 0x175b, 0x0f9c, 0xdcdb, + 0xa913, 0x7a54, 0x0cb0, 0xdff7, 0xaa3f, 0x7978, 0x61bf, 0xb2f8, 0xc730, 0x1477, + 0xd6ae, 0x05e9, 0x7021, 0xa366, 0xbba1, 0x68e6, 0x1d2e, 0xce69, 0x81fd, 0x52ba, + 0x2772, 0xf435, 0xecf2, 0x3fb5, 0x4a7d, 0x993a, 0x5be3, 0x88a4, 0xfd6c, 0x2e2b, + 0x36ec, 0xe5ab, 0x9063, 0x4324, 0x35c0, 0xe687, 0x934f, 0x4008, 0x58cf, 0x8b88, + 0xfe40, 0x2d07, 0xefde, 0x3c99, 0x4951, 0x9a16, 0x82d1, 0x5196, 0x245e, 0xf719, + 0xe986, 0x3ac1, 0x4f09, 0x9c4e, 0x8489, 0x57ce, 0x2206, 0xf141, 0x3398, 0xe0df, + 0x9517, 0x4650, 0x5e97, 0x8dd0, 0xf818, 0x2b5f, 0x5dbb, 0x8efc, 0xfb34, 0x2873, + 0x30b4, 0xe3f3, 0x963b, 0x457c, 0x87a5, 0x54e2, 0x212a, 0xf26d, 0xeaaa, 0x39ed, + 0x4c25, 0x9f62, 0x510b, 0x824c, 0xf784, 0x24c3, 0x3c04, 0xef43, 0x9a8b, 0x49cc, + 0x8b15, 0x5852, 0x2d9a, 0xfedd, 0xe61a, 0x355d, 0x4095, 0x93d2, 0xe536, 0x3671, + 0x43b9, 0x90fe, 0x8839, 0x5b7e, 0x2eb6, 0xfdf1, 0x3f28, 0xec6f, 0x99a7, 0x4ae0, + 0x5227, 0x8160, 0xf4a8, 0x27ef, 0x3970, 0xea37, 0x9fff, 0x4cb8, 0x547f, 0x8738, + 0xf2f0, 0x21b7, 0xe36e, 0x3029, 0x45e1, 0x96a6, 0x8e61, 0x5d26, 0x28ee, 0xfba9, + 0x8d4d, 0x5e0a, 0x2bc2, 0xf885, 0xe042, 0x3305, 0x46cd, 0x958a, 0x5753, 0x8414, + 0xf1dc, 0x229b, 0x3a5c, 0xe91b, 0x9cd3, 0x4f94} +}; + +static inline uint16_t swaplow(uint16_t crc) { + return + ((crc & 0xff) << 8) + + ((crc & 0xff00) >> 8); +} + +// This code assumes that integers are stored little-endian. + +__attribute__((optimize("Ofast"))) +static uint16_t crc16ibm_3740_word(uint16_t crc, void const *mem, size_t len) { + unsigned char const *data = mem; + if (data == NULL) + return 0xffff; + while (len && ((ptrdiff_t)data & 0x7)) { + len--; + crc = (crc << 8) ^ + table_byte[((crc >> 8) ^ *data++) & 0xff]; + } + crc = swaplow(crc); + size_t n = len >> 3; + for (size_t i = 0; i < n; i++) { + uint64_t word = crc ^ ((uint64_t const *)data)[i]; + crc = table_word[7][word & 0xff] ^ + table_word[6][(word >> 8) & 0xff] ^ + table_word[5][(word >> 16) & 0xff] ^ + table_word[4][(word >> 24) & 0xff] ^ + table_word[3][(word >> 32) & 0xff] ^ + table_word[2][(word >> 40) & 0xff] ^ + table_word[1][(word >> 48) & 0xff] ^ + table_word[0][word >> 56]; + } + data += n << 3; + len &= 7; + crc = swaplow(crc); + while (len) { + len--; + crc = (crc << 8) ^ + table_byte[((crc >> 8) ^ *data++) & 0xff]; + } + return crc; +} + +uint16_t crc16(uint8_t const *data, int const length) +{ + //Calculate the CRC16 checksum for the specified data block + unsigned short crc = 0; + //Return the calculated checksum + return crc16ibm_3740_word(crc, data, length); +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/crc.h b/lib/sd_driver/crc.h new file mode 100755 index 0000000..e3437bb --- /dev/null +++ b/lib/sd_driver/crc.h @@ -0,0 +1,73 @@ +/* crc.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +/* Derived from: + * SD/MMC File System Library + * Copyright (c) 2016 Neil Thiessen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SD_CRC_H +#define SD_CRC_H + +#include +#include + +/** + * @brief Calculate the CRC7 checksum for the specified data block. + * + * This function calculates the CRC7 checksum for the specified data block + * using the lookup table defined in the m_Crc7Table array. + * + * @param data The data block to be checked. + * @param length The length of the data block in bytes. + * @return The calculated checksum. + */ +__attribute__((optimize("Ofast"))) +static inline char crc7(uint8_t const *data, int const length) { +extern const char m_Crc7Table[]; + char crc = 0; + for (int i = 0; i < length; i++) { + crc = m_Crc7Table[(crc << 1) ^ data[i]]; + } + + //Return the calculated checksum + return crc; +} + +/** + * @brief Calculate the CRC16 checksum for the specified data block. + * + * This function calculates the CRC16 checksum for the specified data block + * using the lookup table defined in the m_Crc7Table array. + * + * @param data The data block to be checked. + * @param length The length of the data block in bytes. + * @return The calculated checksum. + */ +uint16_t crc16(uint8_t const *data, int const length); + +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/delays.h b/lib/sd_driver/delays.h new file mode 100644 index 0000000..0538d99 --- /dev/null +++ b/lib/sd_driver/delays.h @@ -0,0 +1,79 @@ +/* + * delays.h + * + * Created on: Apr 25, 2022 + * Author: carlk + */ +/* Using millis() or micros() for timeouts + +For example, + + uint32_t start = millis(); + do { + // ... + } + } while (millis() - start < TIMEOUT); + +There is no problem if the millis() counter wraps, +due to the properties of unsigned integer modulo arithmetic. + +"A computation involving unsigned operands can never overflow, +because a result that cannot be represented by the resulting +unsigned integer type is reduced modulo the number that is +one greater than the largest value that can be represented +by the resulting type." + -- ISO/IEC 9899:1999 (E) §6.2.5/9 + +In other words, a uint32_t will wrap at 0 and UINT_MAX. + +So, for example, + 0x00000000 - 0xFFFFFFFF = 0x00000001 + +Remember that an unsigned integer will never be negative! + +Be careful with comparisons. In the example above, +if 0x00000000 is the result of the counter wrapping, +and 0xFFFFFFFF is the start timestamp, a comparison like + + millis() - start < TIMEOUT + +is OK, but the following code is problematic if, say, the first call +to millis() returns 0xFFFFFFF0 and the second +call to millis() returns 0xFFFFFFFF: + + uint32_t end = millis() + 100; // end = 0x00000054 + while (millis() < end) // while (0xFFFFFFFF < 0x00000054) + +*/ + +#pragma once + +#include +// +#include "pico.h" +#include "pico/stdlib.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static inline uint32_t millis() { + __compiler_memory_barrier(); + return time_us_64() / 1000; + __compiler_memory_barrier(); +} + +static inline void delay_ms(uint32_t ulTime_ms) { + sleep_ms(ulTime_ms); +} + +static inline uint64_t micros() { + __compiler_memory_barrier(); + return to_us_since_boot(get_absolute_time()); + __compiler_memory_barrier(); +} + +#ifdef __cplusplus +} +#endif +/* [] END OF FILE */ diff --git a/lib/sd_driver/dma_interrupts.c b/lib/sd_driver/dma_interrupts.c new file mode 100644 index 0000000..8ef8d9d --- /dev/null +++ b/lib/sd_driver/dma_interrupts.c @@ -0,0 +1,85 @@ + +#include "hw_config.h" +#include "sd_card.h" +// +#include "dma_interrupts.h" + +static void dma_irq_handler(const uint DMA_IRQ_num, io_rw_32 *dma_hw_ints_p) { + // Iterate through all of the SD cards + for (size_t i = 0; i < sd_get_num(); ++i) { + sd_card_t *sd_card_p = sd_get_by_num(i); + if (!sd_card_p) + continue; + uint irq_num = 0, channel = 0; + if (SD_IF_SDIO == sd_card_p->type) { + irq_num = sd_card_p->sdio_if_p->DMA_IRQ_num; + channel = sd_card_p->sdio_if_p->state.SDIO_DMA_CHB; + } + // Is this channel requesting interrupt? + if (irq_num == DMA_IRQ_num && (*dma_hw_ints_p & (1 << channel))) { + *dma_hw_ints_p = 1 << channel; // Clear it. + if (SD_IF_SDIO == sd_card_p->type) { + sdio_irq_handler(sd_card_p); + } + } + } +} +static void __not_in_flash_func(dma_irq_handler_0)() { + dma_irq_handler(DMA_IRQ_0, &dma_hw->ints0); +} +static void __not_in_flash_func(dma_irq_handler_1)() { + dma_irq_handler(DMA_IRQ_1, &dma_hw->ints1); +} + +/* Adding the interrupt request handler + Only add it once. + Otherwise, space is wasted in irq_add_shared_handler's table. + Also, each core maintains its own table of interrupt vectors, + and we don't want both cores to call the IRQ handler. +*/ +typedef struct ih_added_rec_t { + uint num; + bool added; +} ih_added_rec_t; +static ih_added_rec_t ih_added_recs[] = { + {DMA_IRQ_0, false}, + {DMA_IRQ_1, false}}; +static bool is_handler_added(const uint num) { + for (size_t i = 0; i < count_of(ih_added_recs); ++i) + if (num == ih_added_recs[i].num) + return ih_added_recs[i].added; + return false; +} +static void mark_handler_added(const uint num) { + size_t i; + for (i = 0; i < count_of(ih_added_recs); ++i) + if (num == ih_added_recs[i].num) { + ih_added_recs[i].added = true; + break; + } + myASSERT(i < count_of(ih_added_recs)); +} +void dma_irq_add_handler(const uint num, bool exclusive) { + if (!is_handler_added(num)) { + static void (*irq_handler)(); + switch (num) { + case DMA_IRQ_0: + irq_handler = dma_irq_handler_0; + break; + case DMA_IRQ_1: + irq_handler = dma_irq_handler_1; + break; + default: + myASSERT(false); + } + if (exclusive) { + irq_set_exclusive_handler(num, *irq_handler); + } else { + irq_add_shared_handler( + num, *irq_handler, + PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); + } + irq_set_enabled(num, true); // Enable IRQ in NVIC + mark_handler_added(num); + } +} diff --git a/lib/sd_driver/dma_interrupts.h b/lib/sd_driver/dma_interrupts.h new file mode 100644 index 0000000..8e47d01 --- /dev/null +++ b/lib/sd_driver/dma_interrupts.h @@ -0,0 +1,14 @@ + +#pragma once + +#include "pico.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void dma_irq_add_handler(const uint num, bool exclusive); + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/f_util.c b/lib/sd_driver/f_util.c new file mode 100644 index 0000000..6e97868 --- /dev/null +++ b/lib/sd_driver/f_util.c @@ -0,0 +1,155 @@ +/* f_util.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#include +#include +// +#include "ff.h" + +const char *FRESULT_str(FRESULT i) { + switch (i) { + case FR_OK: + return "Succeeded"; + case FR_DISK_ERR: + return "A hard error occurred in the low level disk I/O layer"; + case FR_INT_ERR: + return "Assertion failed"; + case FR_NOT_READY: + return "The physical drive cannot work"; + case FR_NO_FILE: + return "Could not find the file"; + case FR_NO_PATH: + return "Could not find the path"; + case FR_INVALID_NAME: + return "The path name format is invalid"; + case FR_DENIED: + return "Access denied due to prohibited access or directory full"; + case FR_EXIST: + return "Access denied due to prohibited access (exists)"; + case FR_INVALID_OBJECT: + return "The file/directory object is invalid"; + case FR_WRITE_PROTECTED: + return "The physical drive is write protected"; + case FR_INVALID_DRIVE: + return "The logical drive number is invalid"; + case FR_NOT_ENABLED: + return "The volume has no work area (mount)"; + case FR_NO_FILESYSTEM: + return "There is no valid FAT volume"; + case FR_MKFS_ABORTED: + return "The f_mkfs() aborted due to any problem"; + case FR_TIMEOUT: + return "Could not get a grant to access the volume within defined " + "period"; + case FR_LOCKED: + return "The operation is rejected according to the file sharing " + "policy"; + case FR_NOT_ENOUGH_CORE: + return "LFN working buffer could not be allocated"; + case FR_TOO_MANY_OPEN_FILES: + return "Number of open files > FF_FS_LOCK"; + case FR_INVALID_PARAMETER: + return "Given parameter is invalid"; + default: + return "Unknown"; + } +} + +FRESULT delete_node ( + TCHAR* path, /* Path name buffer with the sub-directory to delete */ + UINT sz_buff, /* Size of path name buffer (items) */ + FILINFO* fno /* Name read buffer */ +) +{ + UINT i, j; + FRESULT fr; + DIR dir; + + + fr = f_opendir(&dir, path); /* Open the sub-directory to make it empty */ + if (fr != FR_OK) return fr; + + for (i = 0; path[i]; i++) ; /* Get current path length */ + path[i++] = '/'; + + for (;;) { + fr = f_readdir(&dir, fno); /* Get a directory item */ + if (fr != FR_OK || !fno->fname[0]) break; /* End of directory? */ + j = 0; + do { /* Make a path name */ + if (i + j >= sz_buff) { /* Buffer over flow? */ + fr = 100; break; /* Fails with 100 when buffer overflow */ + } + path[i + j] = fno->fname[j]; + } while (fno->fname[j++]); + if (fno->fattrib & AM_DIR) { /* Item is a sub-directory */ + fr = delete_node(path, sz_buff, fno); + } else { /* Item is a file */ + fr = f_unlink(path); + } + if (fr != FR_OK) break; + } + + path[--i] = 0; /* Restore the path name */ + f_closedir(&dir); + + if (fr == FR_OK) fr = f_unlink(path); /* Delete the empty sub-directory */ + return fr; +} + +void ls(const char *dir) { + char cwdbuf[FF_LFN_BUF] = {0}; + FRESULT fr; /* Return value */ + char const *p_dir; + if (dir[0]) { + p_dir = dir; + } else { + fr = f_getcwd(cwdbuf, sizeof cwdbuf); + if (FR_OK != fr) { + printf("f_getcwd error: %s (%d)\n", FRESULT_str(fr), fr); + return; + } + p_dir = cwdbuf; + } + printf("Directory Listing: %s\n", p_dir); + DIR dj = {}; /* Directory object */ + FILINFO fno = {}; /* File information */ + assert(p_dir); + fr = f_findfirst(&dj, &fno, p_dir, "*"); + if (FR_OK != fr) { + printf("f_findfirst error: %s (%d)\n", FRESULT_str(fr), fr); + return; + } + while (fr == FR_OK && fno.fname[0]) { /* Repeat while an item is found */ + /* Create a string that includes the file name, the file size and the + attributes string. */ + const char *pcWritableFile = "writable file", + *pcReadOnlyFile = "read only file", + *pcDirectory = "directory"; + const char *pcAttrib; + /* Point pcAttrib to a string that describes the file. */ + if (fno.fattrib & AM_DIR) { + pcAttrib = pcDirectory; + } else if (fno.fattrib & AM_RDO) { + pcAttrib = pcReadOnlyFile; + } else { + pcAttrib = pcWritableFile; + } + /* Create a string that includes the file name, the file size and the + attributes string. */ + printf("%s [%s] [size=%llu]\n", fno.fname, pcAttrib, fno.fsize); + + fr = f_findnext(&dj, &fno); /* Search for next item */ + } + f_closedir(&dj); +} diff --git a/lib/sd_driver/f_util.h b/lib/sd_driver/f_util.h new file mode 100755 index 0000000..97c33e1 --- /dev/null +++ b/lib/sd_driver/f_util.h @@ -0,0 +1,32 @@ +/* f_util.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#pragma once +#include "ff.h" + +#ifdef __cplusplus +extern "C" { +#endif + + const char *FRESULT_str(FRESULT i); + FRESULT delete_node ( + TCHAR* path, /* Path name buffer with the sub-directory to delete */ + UINT sz_buff, /* Size of path name buffer (items) */ + FILINFO* fno /* Name read buffer */ + ); + + void ls(const char *dir); + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/hw_config.h b/lib/sd_driver/hw_config.h new file mode 100644 index 0000000..3c12569 --- /dev/null +++ b/lib/sd_driver/hw_config.h @@ -0,0 +1,44 @@ +/* hw_config.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#pragma once + +#include + +#include "sd_card.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* FatFS supports up to 10 logical drives. By default, each logical +drive is associated with the physical drive in same drive number. */ + +/* Return the number of physical drives (SD card sockets) in the configuration */ +size_t sd_get_num(); + +/* Return a pointer to the SD card "object" at the given physical drive number. +(See http://elm-chan.org/fsw/ff/doc/filename.html#vol.) +Parameter `num` must be less than sd_get_num(). */ +sd_card_t* sd_get_by_num(size_t num); + +/* See http://elm-chan.org/fsw/ff/doc/config.html#str_volume_id */ +#if FF_STR_VOLUME_ID +extern const char* VolumeStr[FF_VOLUMES]; /* User defined volume ID */ +#endif + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/my_debug.c b/lib/sd_driver/my_debug.c new file mode 100644 index 0000000..e6cbe30 --- /dev/null +++ b/lib/sd_driver/my_debug.c @@ -0,0 +1,223 @@ +/* my_debug.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#include +#include +#include +#include +#include +#include +// +#include "pico/stdlib.h" +#include "hardware/sync.h" +// +#include "crash.h" +// +#include "my_debug.h" + +/* Function Attribute ((weak)) +The weak attribute causes a declaration of an external symbol to be emitted as a weak symbol +rather than a global. This is primarily useful in defining library functions that can be +overridden in user code, though it can also be used with non-function declarations. The +overriding symbol must have the same type as the weak symbol. +https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html + +You can override these functions in your application to redirect "stdout"-type messages. +*/ + +/* Single string output callbacks */ + +void __attribute__((weak)) put_out_error_message(const char *s) { (void)s; } +void __attribute__((weak)) put_out_info_message(const char *s) { (void)s; } +void __attribute__((weak)) put_out_debug_message(const char *s) { (void)s; } + +/* "printf"-style output callbacks */ + +#if defined(USE_PRINTF) && USE_PRINTF + +int __attribute__((weak)) +error_message_printf(const char *func, int line, + const char *fmt, ...) +{ + printf("%s:%d: ", func, line); + va_list args; + va_start(args, fmt); + int cw = vprintf(fmt, args); + va_end(args); + stdio_flush(); + return cw; +} +int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int cw = vprintf(fmt, args); + va_end(args); + stdio_flush(); + return cw; +} +int __attribute__((weak)) info_message_printf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + int cw = vprintf(fmt, args); + va_end(args); + return cw; +} +int __attribute__((weak)) +debug_message_printf(const char *func, int line, + const char *fmt, ...) +{ + (void) func; + (void) line; + va_list args; + va_start(args, fmt); + int cw = vprintf(fmt, args); + va_end(args); + stdio_flush(); + return cw; +} + +#else + +/* These will truncate at 256 bytes. You can tell by checking the return code. */ + +int __attribute__((weak)) +error_message_printf(const char *func, int line, + const char *fmt, ...) +{ + (void) func; + (void) line; + char buf[256] = {0}; + va_list args; + va_start(args, fmt); + int cw = vsnprintf(buf, sizeof(buf), fmt, args); + put_out_error_message(buf); + va_end(args); + return cw; +} +int __attribute__((weak)) error_message_printf_plain(const char *fmt, ...) { + char buf[256] = {0}; + va_list args; + va_start(args, fmt); + int cw = vsnprintf(buf, sizeof(buf), fmt, args); + put_out_info_message(buf); + va_end(args); + return cw; +} +int __attribute__((weak)) info_message_printf(const char *fmt, ...) { + char buf[256] = {0}; + va_list args; + va_start(args, fmt); + int cw = vsnprintf(buf, sizeof(buf), fmt, args); + put_out_info_message(buf); + va_end(args); + return cw; +} +int __attribute__((weak)) +debug_message_printf(const char *func, int line, + const char *fmt, ...) +{ + (void) func; + (void) line; + char buf[256] = {0}; + va_list args; + va_start(args, fmt); + int cw = vsnprintf(buf, sizeof(buf), fmt, args); + put_out_debug_message(buf); + va_end(args); + return cw; +} + +#endif + +void __attribute__((weak)) my_assert_func(const char *file, int line, const char *func, + const char *pred) { + error_message_printf_plain("assertion \"%s\" failed: file \"%s\", line %d, function: %s\n", + pred, file, line, func); + (void)save_and_disable_interrupts(); /* Disable global interrupts. */ + capture_assert(file, line, func, pred); +} + +void assert_case_not_func(const char *file, int line, const char *func, int v) { + char pred[128]; + snprintf(pred, sizeof pred, "case not %d", v); + my_assert_func(file, line, func, pred); +} + +void assert_case_is(const char *file, int line, const char *func, int v, int expected) { + char pred[128]; + snprintf(pred, sizeof pred, "%d is %d", v, expected); + my_assert_func(file, line, func, pred); +} + +void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes) { + int n = 0; + for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) { + for (size_t col = 0; col < 32 && byte_ix < nbytes; ++col, ++byte_ix) { + n += snprintf(buf + n, buf_sz - n, "%02hhx ", pbytes[byte_ix]); + myASSERT(0 < n && n < (int)buf_sz); + } + n += snprintf(buf + n, buf_sz - n, "\n"); + myASSERT(0 < n && n < (int)buf_sz); + } +} +void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes) { + IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pbytes, nbytes); + stdio_flush(); + size_t col = 0; + for (size_t byte_ix = 0; byte_ix < nbytes; ++byte_ix) { + IMSG_PRINTF("%02hhx ", pbytes[byte_ix]); + if (++col > 31) { + IMSG_PRINTF("\n"); + col = 0; + } + stdio_flush(); + } +} +// nwords is size in WORDS! +void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords) { + IMSG_PRINTF("\n%s(%s, 0x%p, %zu)\n", __FUNCTION__, s, pwords, nwords); + stdio_flush(); + size_t col = 0; + for (size_t word_ix = 0; word_ix < nwords; ++word_ix) { + IMSG_PRINTF("%08lx ", pwords[word_ix]); + if (++col > 7) { + IMSG_PRINTF("\n"); + col = 0; + } + stdio_flush(); + } +} +// nwords is size in bytes +bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1, + const uint8_t *pbytes1, const size_t nbytes) { + /* Verify the data. */ + if (0 != memcmp(pbytes0, pbytes1, nbytes)) { + hexdump_8(s0, pbytes0, nbytes); + hexdump_8(s1, pbytes1, nbytes); + return false; + } + return true; +} +// nwords is size in WORDS! +bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1, + const uint32_t *pwords1, const size_t nwords) { + /* Verify the data. */ + if (0 != memcmp(pwords0, pwords1, nwords * sizeof(uint32_t))) { + hexdump_32(s0, pwords0, nwords); + hexdump_32(s1, pwords1, nwords); + return false; + } + return true; +} +/* [] END OF FILE */ diff --git a/lib/sd_driver/my_debug.h b/lib/sd_driver/my_debug.h new file mode 100755 index 0000000..4a2d0ad --- /dev/null +++ b/lib/sd_driver/my_debug.h @@ -0,0 +1,140 @@ +/* my_debug.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#pragma once + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* USE_PRINTF +If this is defined and not zero, +these message output functions will use the Pico SDK's stdout. +*/ + +/* USE_DBG_PRINTF +If this is not defined or is zero +DBG_PRINTF statements will be effectively stripped from the code. +*/ + +/* Single string output callbacks: send message output somewhere. +To use these, do not define the USE_PRINTF compile definition, +and override these "weak" functions by strongly implementing them in user code. +The weak implementations do nothing. + */ +void put_out_error_message(const char *s); +void put_out_info_message(const char *s); +void put_out_debug_message(const char *s); + +// https://gcc.gnu.org/onlinedocs/gcc-3.2.3/cpp/Variadic-Macros.html + +int error_message_printf(const char *func, int line, const char *fmt, ...) + __attribute__((format(__printf__, 3, 4))); +#ifndef EMSG_PRINTF +#define EMSG_PRINTF(fmt, ...) error_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__) +#endif + +int error_message_printf_plain(const char *fmt, ...) __attribute__((format(__printf__, 1, 2))); + +int debug_message_printf(const char *func, int line, const char *fmt, ...) + __attribute__((format(__printf__, 3, 4))); +#ifndef DBG_PRINTF +# if defined(USE_DBG_PRINTF) && USE_DBG_PRINTF // && !defined(NDEBUG) +# define DBG_PRINTF(fmt, ...) debug_message_printf(__func__, __LINE__, fmt, ##__VA_ARGS__) +# else +# define DBG_PRINTF(fmt, ...) (void)0 +# endif +#endif + +int info_message_printf(const char *fmt, ...) __attribute__((format(__printf__, 1, 2))); +#ifndef IMSG_PRINTF +#define IMSG_PRINTF(fmt, ...) info_message_printf(fmt, ##__VA_ARGS__) +#endif + +void lock_printf(); +void unlock_printf(); + +void my_assert_func(const char *file, int line, const char *func, const char *pred) + __attribute__((noreturn)); +#ifdef NDEBUG /* required by ANSI standard */ +# define myASSERT(__e) ((void)0) +#else +# define myASSERT(__e) ((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e)) +#endif + +void assert_always_func(const char *file, int line, const char *func, const char *pred) + __attribute__((noreturn)); +#define ASSERT_ALWAYS(__e) \ + ((__e) ? (void)0 : my_assert_func(__FILE__, __LINE__, __func__, #__e)) + +void assert_case_is(const char *file, int line, const char *func, int v, int expected) + __attribute__((noreturn)); +#define ASSERT_CASE_IS(__v, __e) \ + ((__v == __e) ? (void)0 : assert_case_is(__FILE__, __LINE__, __func__, __v, __e)) + +void assert_case_not_func(const char *file, int line, const char *func, int v) + __attribute__((noreturn)); +#define ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v)) + +#ifdef NDEBUG /* required by ANSI standard */ +#define DBG_ASSERT_CASE_NOT(__e) ((void)0) +#else +#define DBG_ASSERT_CASE_NOT(__v) (assert_case_not_func(__FILE__, __LINE__, __func__, __v)) +#endif +static inline void dump_bytes(size_t num, uint8_t bytes[]) { + (void)num; + (void)bytes; + DBG_PRINTF(" "); + for (size_t j = 0; j < 16; ++j) { + DBG_PRINTF("%02hhx", j); + if (j < 15) + DBG_PRINTF(" "); + else { + DBG_PRINTF("\n"); + } + } + for (size_t i = 0; i < num; i += 16) { + DBG_PRINTF("%04x ", i); + for (size_t j = 0; j < 16 && i + j < num; ++j) { + DBG_PRINTF("%02hhx", bytes[i + j]); + if (j < 15) + DBG_PRINTF(" "); + else { + DBG_PRINTF("\n"); + } + } + } + DBG_PRINTF("\n"); +} + +void dump8buf(char *buf, size_t buf_sz, uint8_t *pbytes, size_t nbytes); +void hexdump_8(const char *s, const uint8_t *pbytes, size_t nbytes); +bool compare_buffers_8(const char *s0, const uint8_t *pbytes0, const char *s1, + const uint8_t *pbytes1, const size_t nbytes); + +// sz is size in BYTES! +void hexdump_32(const char *s, const uint32_t *pwords, size_t nwords); +// sz is size in BYTES! +bool compare_buffers_32(const char *s0, const uint32_t *pwords0, const char *s1, + const uint32_t *pwords1, const size_t nwords); + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/my_rtc.c b/lib/sd_driver/my_rtc.c new file mode 100644 index 0000000..5d4e2c6 --- /dev/null +++ b/lib/sd_driver/my_rtc.c @@ -0,0 +1,159 @@ +/* my_rtc.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#include +#include +#include +// +#include "pico/aon_timer.h" +#include "pico/stdio.h" +#include "pico/stdlib.h" +#include "pico/util/datetime.h" +#if HAS_RP2040_RTC +# include "hardware/rtc.h" +#endif +// +#include "crc.h" +#include "ff.h" +// +#include "my_rtc.h" + +time_t epochtime; + +// Make an attempt to save a recent time stamp across reset: +typedef struct rtc_save { + uint32_t signature; + struct timespec ts; + char checksum; // last, not included in checksum +} rtc_save_t; +static rtc_save_t rtc_save __attribute__((section(".uninitialized_data"))); + +static bool get_time(struct timespec *ts) { + if (!aon_timer_is_running()) return false; + aon_timer_get_time(ts); + return true; +} + +/** + * @brief Update the epochtime variable from the always-on timer + * + * If the always-on timer is running, copy its current value to the epochtime variable. + * Also, copy the current always-on timer value to the rtc_save structure and + * calculate a checksum of the structure. + */ +static void update_epochtime() { + bool ok = get_time(&rtc_save.ts); + if (!ok) return; + // Set the signature to the magic number + rtc_save.signature = 0xBABEBABE; + // Calculate the checksum of the structure + rtc_save.checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum)); + // Copy the seconds part of the always-on timer to the epochtime variable + epochtime = rtc_save.ts.tv_sec; +} + +/** + * @brief Get the current time in seconds since the Epoch. + * + * @param[in] pxTime If not NULL, the current time is copied here. + * @return The current time in seconds since the Epoch. + */ +time_t time(time_t *pxTime) { + update_epochtime(); + if (pxTime) { + *pxTime = epochtime; + } + return epochtime; +} + +/** + * @brief Initialize the always-on timer and save its value to the rtc_save structure + * + * If the always-on timer is already running, this function does nothing. + * Otherwise, it initializes the RTC if it is available and checks if the saved + * time is valid. If the saved time is valid, it sets the always-on timer to the saved + * value. + */ +void time_init() { + // If the always-on timer is already running, return immediately + if (aon_timer_is_running()) return; + + // Initialize the RTC if it is available +#if HAS_RP2040_RTC + rtc_init(); +#endif + + // Check if the saved time is valid + char xor_checksum = crc7((uint8_t *)&rtc_save, offsetof(rtc_save_t, checksum)); + bool ok = rtc_save.signature == 0xBABEBABE && rtc_save.checksum == xor_checksum; + + // If the saved time is valid, set the always-on timer + if (ok) aon_timer_set_time(&rtc_save.ts); +} + +/** + * @brief Get the current time in the FAT time format + * + * The FAT file system uses a specific date and time format that is different + * from the one used by the standard C library. This function converts the + * current time to the FAT time format. + * + * @return The current time in the FAT time format (DWORD) + */ +DWORD get_fattime(void) { + struct timespec ts; + bool ok = get_time(&ts); + if (!ok) return 0; + + struct tm t; + localtime_r(&ts.tv_sec, &t); + + DWORD fattime = 0; + // bit31:25 + // Year origin from the 1980 (0..127, e.g. 37 for 2017) + // tm_year int years since 1900 + int yr = t.tm_year + 1900 - 1980; + assert(yr >= 0 && yr <= 127); + fattime |= (0b01111111 & yr) << 25; + // bit24:21 + // Month (1..12) + // tm_mon int months since January 0-11 + uint8_t mo = t.tm_mon + 1; + assert(mo >= 1 && mo <= 12); + fattime |= (0b00001111 & mo) << 21; + // bit20:16 + // Day of the month (1..31) + // tm_mday int day of the month 1-31 + uint8_t da = t.tm_mday; + assert(da >= 1 && da <= 31); + fattime |= (0b00011111 & da) << 16; + // bit15:11 + // Hour (0..23) + // tm_hour int hours since midnight 0-23 + uint8_t hr = t.tm_hour; + assert(hr <= 23); + fattime |= (0b00011111 & hr) << 11; + // bit10:5 + // Minute (0..59) + // tm_min int minutes after the hour 0-59 + uint8_t mi = t.tm_min; + assert(mi <= 59); + fattime |= (0b00111111 & mi) << 5; + // bit4:0 + // Second / 2 (0..29, e.g. 25 for 50) + // tm_sec int seconds after the minute 0-61* + uint8_t sd = t.tm_sec / 2; + assert(sd <= 30); // The extra range is to accommodate for leap seconds + fattime |= (0b00011111 & sd); + return fattime; +} diff --git a/lib/sd_driver/my_rtc.h b/lib/sd_driver/my_rtc.h new file mode 100755 index 0000000..d0c5e24 --- /dev/null +++ b/lib/sd_driver/my_rtc.h @@ -0,0 +1,27 @@ +/* my_rtc.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +extern time_t epochtime; +void time_init(); + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/rp2040_sdio.pio.h b/lib/sd_driver/rp2040_sdio.pio.h new file mode 100644 index 0000000..4285b84 --- /dev/null +++ b/lib/sd_driver/rp2040_sdio.pio.h @@ -0,0 +1,124 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +#define CLKDIV 4 +#define SDIO_CLK_PIN_D0_OFFSET 30 + +// ------------ // +// sdio_cmd_clk // +// ------------ // + +#define sdio_cmd_clk_wrap_target 0 +#define sdio_cmd_clk_wrap 17 + +static const uint16_t sdio_cmd_clk_program_instructions[] = { + // .wrap_target + 0xb1e3, // 0: mov osr, null side 1 [1] + 0xa14d, // 1: mov y, !status side 0 [1] + 0x1161, // 2: jmp !y, 1 side 1 [1] + 0x6160, // 3: out null, 32 side 0 [1] + 0x7128, // 4: out x, 8 side 1 [1] + 0xe101, // 5: set pins, 1 side 0 [1] + 0xf181, // 6: set pindirs, 1 side 1 [1] + 0x6101, // 7: out pins, 1 side 0 [1] + 0x1147, // 8: jmp x--, 7 side 1 [1] + 0xe180, // 9: set pindirs, 0 side 0 [1] + 0x7128, // 10: out x, 8 side 1 [1] + 0xa142, // 11: nop side 0 [1] + 0x1131, // 12: jmp !x, 17 side 1 [1] + 0xa142, // 13: nop side 0 [1] + 0x11cd, // 14: jmp pin, 13 side 1 [1] + 0x4101, // 15: in pins, 1 side 0 [1] + 0x114f, // 16: jmp x--, 15 side 1 [1] + 0x8120, // 17: push block side 0 [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program sdio_cmd_clk_program = { + .instructions = sdio_cmd_clk_program_instructions, + .length = 18, + .origin = -1, +}; + +static inline pio_sm_config sdio_cmd_clk_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + sdio_cmd_clk_wrap_target, offset + sdio_cmd_clk_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} +#endif + +// ------------ // +// sdio_data_rx // +// ------------ // + +#define sdio_data_rx_wrap_target 0 +#define sdio_data_rx_wrap 4 + +static const uint16_t sdio_data_rx_program_instructions[] = { + // .wrap_target + 0xa022, // 0: mov x, y + 0x2020, // 1: wait 0 pin, 0 + 0x23be, // 2: wait 1 pin, 30 [3] + 0x4204, // 3: in pins, 4 [2] + 0x0043, // 4: jmp x--, 3 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program sdio_data_rx_program = { + .instructions = sdio_data_rx_program_instructions, + .length = 5, + .origin = -1, +}; + +static inline pio_sm_config sdio_data_rx_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + sdio_data_rx_wrap_target, offset + sdio_data_rx_wrap); + return c; +} +#endif + +// ------------ // +// sdio_data_tx // +// ------------ // + +#define sdio_data_tx_wrap_target 5 +#define sdio_data_tx_wrap 8 + +static const uint16_t sdio_data_tx_program_instructions[] = { + 0x203e, // 0: wait 0 pin, 30 + 0x24be, // 1: wait 1 pin, 30 [4] + 0x6104, // 2: out pins, 4 [1] + 0x0142, // 3: jmp x--, 2 [1] + 0xe180, // 4: set pindirs, 0 [1] + // .wrap_target + 0x4101, // 5: in pins, 1 [1] + 0x0185, // 6: jmp y--, 5 [1] + 0x21a0, // 7: wait 1 pin, 0 [1] + 0x8120, // 8: push block [1] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program sdio_data_tx_program = { + .instructions = sdio_data_tx_program_instructions, + .length = 9, + .origin = -1, +}; + +static inline pio_sm_config sdio_data_tx_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + sdio_data_tx_wrap_target, offset + sdio_data_tx_wrap); + return c; +} +#endif + diff --git a/lib/sd_driver/sd_card.c b/lib/sd_driver/sd_card.c new file mode 100755 index 0000000..b205746 --- /dev/null +++ b/lib/sd_driver/sd_card.c @@ -0,0 +1,386 @@ +/* sd_card.c +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +/* Standard includes. */ +#include +#include +#include +#include +#include +// +#include "pico/mutex.h" +// +#include "SDIO/SdioCard.h" +#include "SPI/sd_card_spi.h" +#include "hw_config.h" // Hardware Configuration of the SPI and SD Card "objects" +#include "my_debug.h" +#include "sd_card_constants.h" +#include "sd_regs.h" +#include "sd_timeouts.h" +#include "util.h" +// +#include "diskio.h" /* Declarations of disk functions */ // Needed for STA_NOINIT, ... +// +#include "sd_card.h" + +#define TRACE_PRINTF(fmt, args...) +// #define TRACE_PRINTF printf + +#ifdef NDEBUG +# pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +static bool driver_initialized; + +// An SD card can only do one thing at a time. +void sd_lock(sd_card_t *sd_card_p) { + myASSERT(mutex_is_initialized(&sd_card_p->state.mutex)); + mutex_enter_blocking(&sd_card_p->state.mutex); +} +void sd_unlock(sd_card_t *sd_card_p) { + myASSERT(mutex_is_initialized(&sd_card_p->state.mutex)); + mutex_exit(&sd_card_p->state.mutex); +} +bool sd_is_locked(sd_card_t *sd_card_p) { + myASSERT(mutex_is_initialized(&sd_card_p->state.mutex)); + uint32_t owner_out; + return !mutex_try_enter(&sd_card_p->state.mutex, &owner_out); +} + +sd_card_t *sd_get_by_drive_prefix(const char *const drive_prefix) { + // Numeric drive number is always valid + if (2 == strlen(drive_prefix) && isdigit((unsigned char)drive_prefix[0]) && + ':' == drive_prefix[1]) + return sd_get_by_num(atoi(drive_prefix)); +#if FF_STR_VOLUME_ID + for (size_t i = 0; i < sd_get_num(); ++i) { + // Ignore '/', trailing ':' + if (strstr(drive_prefix, VolumeStr[i])) return sd_get_by_num(i); + } + EMSG_PRINTF("%s: unknown drive prefix %s\n", __func__, drive_prefix); +#endif + return NULL; +} + +/* Return non-zero if the SD-card is present. */ +bool sd_card_detect(sd_card_t *sd_card_p) { + TRACE_PRINTF("> %s\r\n", __FUNCTION__); + if (!sd_card_p->use_card_detect) { + sd_card_p->state.m_Status &= ~STA_NODISK; + return true; + } + /*!< Check GPIO to detect SD */ + if (gpio_get(sd_card_p->card_detect_gpio) == sd_card_p->card_detected_true) { + // The socket is now occupied + sd_card_p->state.m_Status &= ~STA_NODISK; + TRACE_PRINTF("SD card detected!\r\n"); + return true; + } else { + // The socket is now empty + sd_card_p->state.m_Status |= (STA_NODISK | STA_NOINIT); + sd_card_p->state.card_type = SDCARD_NONE; + EMSG_PRINTF("No SD card detected!\r\n"); + return false; + } +} + +void sd_set_drive_prefix(sd_card_t *sd_card_p, size_t phy_drv_num) { +#if FF_STR_VOLUME_ID == 0 + int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix, + "%d:", phy_drv_num); +#elif FF_STR_VOLUME_ID == 1 /* Arbitrary string is enabled */ + // Add ':' + int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix, + "%s:", VolumeStr[phy_drv_num]); +#elif FF_STR_VOLUME_ID == 2 /* Unix style drive prefix */ + // Add '/' + int rc = snprintf(sd_card_p->state.drive_prefix, sizeof sd_card_p->state.drive_prefix, + "/%s", VolumeStr[phy_drv_num]); +#else +#error "Unknown FF_STR_VOLUME_ID" +#endif + // Notice that only when this returned value is non-negative and less than n, + // the string has been completely written. + myASSERT(0 <= rc && (size_t)rc < sizeof sd_card_p->state.drive_prefix); +} + +char const *sd_get_drive_prefix(sd_card_t *sd_card_p) { + myASSERT(driver_initialized); + myASSERT(sd_card_p); + if (!sd_card_p) return ""; + return sd_card_p->state.drive_prefix; +} + +bool sd_init_driver() { + auto_init_mutex(initialized_mutex); + mutex_enter_blocking(&initialized_mutex); + bool ok = true; + if (!driver_initialized) { + myASSERT(sd_get_num()); + for (size_t i = 0; i < sd_get_num(); ++i) { + sd_card_t *sd_card_p = sd_get_by_num(i); + if (!sd_card_p) continue; + + myASSERT(sd_card_p->type); + + if (!mutex_is_initialized(&sd_card_p->state.mutex)) + mutex_init(&sd_card_p->state.mutex); + sd_lock(sd_card_p); + + sd_card_p->state.m_Status = STA_NOINIT; + + sd_set_drive_prefix(sd_card_p, i); + + // Set up Card Detect + if (sd_card_p->use_card_detect) { + if (sd_card_p->card_detect_use_pull) { + if (sd_card_p->card_detect_pull_hi) { + gpio_pull_up(sd_card_p->card_detect_gpio); + } else { + gpio_pull_down(sd_card_p->card_detect_gpio); + } + } + gpio_init(sd_card_p->card_detect_gpio); + } + + switch (sd_card_p->type) { + case SD_IF_NONE: + myASSERT(false); + break; + case SD_IF_SPI: + myASSERT(sd_card_p->spi_if_p); // Must have an interface object + myASSERT(sd_card_p->spi_if_p->spi); + sd_spi_ctor(sd_card_p); + if (!my_spi_init(sd_card_p->spi_if_p->spi)) { + ok = false; + } + /* At power up the SD card CD/DAT3 / CS line has a 50KOhm pull up enabled + * in the card. This resistor serves two functions Card detection and Mode + * Selection. For Mode Selection, the host can drive the line high or let it + * be pulled high to select SD mode. If the host wants to select SPI mode it + * should drive the line low. + * + * There is an important thing needs to be considered that the MMC/SDC is + * initially NOT the SPI device. Some bus activity to access another SPI + * device can cause a bus conflict due to an accidental response of the + * MMC/SDC. Therefore the MMC/SDC should be initialized to put it into the + * SPI mode prior to access any other device attached to the same SPI bus. + */ + sd_go_idle_state(sd_card_p); + break; + case SD_IF_SDIO: + myASSERT(sd_card_p->sdio_if_p); + sd_sdio_ctor(sd_card_p); + break; + default: + myASSERT(false); + } // switch (sd_card_p->type) + + sd_unlock(sd_card_p); + } // for + driver_initialized = true; + } + mutex_exit(&initialized_mutex); + return ok; +} + +void cidDmp(sd_card_t *sd_card_p, printer_t printer) { + // +-----------------------+-------+-------+-----------+ + // | Name | Field | Width | CID-slice | + // +-----------------------+-------+-------+-----------+ + // | Manufacturer ID | MID | 8 | [127:120] | 15 + (*printer)( + "\nManufacturer ID: " + "0x%x\n", + ext_bits16(sd_card_p->state.CID, 127, 120)); + // | OEM/Application ID | OID | 16 | [119:104] | 14 + (*printer)("OEM ID: "); + { + char buf[3]; + ext_str(16, sd_card_p->state.CID, 119, 104, sizeof buf, buf); + (*printer)("%s", buf); + } + // | Product name | PNM | 40 | [103:64] | 12 + (*printer)("Product: "); + { + char buf[6]; + ext_str(16, sd_card_p->state.CID, 103, 64, sizeof buf, buf); + (*printer)("%s", buf); + } + // | Product revision | PRV | 8 | [63:56] | 7 + (*printer)( + "\nRevision: " + "%d.%d\n", + ext_bits16(sd_card_p->state.CID, 63, 60), ext_bits16(sd_card_p->state.CID, 59, 56)); + // | Product serial number | PSN | 32 | [55:24] | 6 + // (*printer)("0x%lx\n", __builtin_bswap32(ext_bits16(sd_card_p->state.CID, 55, 24)) + (*printer)( + "Serial number: " + "0x%lx\n", + ext_bits16(sd_card_p->state.CID, 55, 24)); + // | reserved | -- | 4 | [23:20] | 2 + // | Manufacturing date | MDT | 12 | [19:8] | + // The "m" field [11:8] is the month code. 1 = January. + // The "y" field [19:12] is the year code. 0 = 2000. + (*printer)( + "Manufacturing date: " + "%d/%d\n", + ext_bits16(sd_card_p->state.CID, 11, 8), + ext_bits16(sd_card_p->state.CID, 19, 12) + 2000); + (*printer)("\n"); + // | CRC7 checksum | CRC | 7 | [7:1] | 0 + // | not used, always 1- | 1 | [0:0] | | + // +-----------------------+-------+-------+-----------+ +} +void csdDmp(sd_card_t *sd_card_p, printer_t printer) { + uint32_t c_size, c_size_mult, read_bl_len; + uint32_t block_len, mult, blocknr; + uint32_t hc_c_size; + uint64_t blocks = 0, capacity = 0; + bool erase_single_block_enable = 0; + uint8_t erase_sector_size = 0; + + // csd_structure : CSD[127:126] + int csd_structure = ext_bits16(sd_card_p->state.CSD, 127, 126); + switch (csd_structure) { + case 0: + c_size = ext_bits16(sd_card_p->state.CSD, 73, 62); // c_size : CSD[73:62] + c_size_mult = + ext_bits16(sd_card_p->state.CSD, 49, 47); // c_size_mult : CSD[49:47] + read_bl_len = + ext_bits16(sd_card_p->state.CSD, 83, 80); // read_bl_len : CSD[83:80] - the + // *maximum* read block length + block_len = 1 << read_bl_len; // BLOCK_LEN = 2^READ_BL_LEN + mult = 1 << (c_size_mult + 2); // MULT = 2^C_SIZE_MULT+2 (C_SIZE_MULT < 8) + blocknr = (c_size + 1) * mult; // BLOCKNR = (C_SIZE+1) * MULT + capacity = (uint64_t)blocknr * block_len; // memory capacity = BLOCKNR * BLOCK_LEN + blocks = capacity / sd_block_size; + + (*printer)("Standard Capacity: c_size: %" PRIu32 "\r\n", c_size); + (*printer)("Sectors: 0x%llx : %llu\r\n", blocks, blocks); + (*printer)("Capacity: 0x%llx : %llu MiB\r\n", capacity, + (capacity / (1024U * 1024U))); + break; + + case 1: + hc_c_size = + ext_bits16(sd_card_p->state.CSD, 69, 48); // device size : C_SIZE : [69:48] + blocks = (hc_c_size + 1) << 10; // block count = C_SIZE+1) * 1K + // byte (512B is block size) + + /* ERASE_BLK_EN + The ERASE_BLK_EN defines the granularity of the unit size of the data to be erased. + The erase operation can erase either one or multiple units of 512 bytes or one or + multiple units (or sectors) of SECTOR_SIZE. If ERASE_BLK_EN=0, the host can erase + one or multiple units of SECTOR_SIZE. If ERASE_BLK_EN=1 the host can erase one or + multiple units of 512 bytes. + */ + erase_single_block_enable = ext_bits16(sd_card_p->state.CSD, 46, 46); + + /* SECTOR_SIZE + The size of an erasable sector. The content of this register is a 7-bit binary coded + value, defining the number of write blocks. The actual size is computed by + increasing this number by one. A value of zero means one write block, 127 means 128 + write blocks. + */ + erase_sector_size = ext_bits16(sd_card_p->state.CSD, 45, 39) + 1; + + (*printer)("SDHC/SDXC Card: hc_c_size: %" PRIu32 "\r\n", hc_c_size); + (*printer)("Sectors: %llu\r\n", blocks); + (*printer)("Capacity: %llu MiB (%llu MB)\r\n", blocks / 2048, + blocks * sd_block_size / 1000000); + (*printer)("ERASE_BLK_EN: %s\r\n", erase_single_block_enable + ? "units of 512 bytes" + : "units of SECTOR_SIZE"); + (*printer)("SECTOR_SIZE (size of an erasable sector): %d (%lu bytes)\r\n", + erase_sector_size, + (uint32_t)(erase_sector_size ? 512 : 1) * erase_sector_size); + break; + + default: + (*printer)("CSD struct unsupported\r\n"); + }; +} + +#define KB 1024 +#define MB (1024 * 1024) + +/* AU (Allocation Unit): +is a physical boundary of the card and consists of one or more blocks and its +size depends on each card. */ +bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p) { + if (SD_IF_SPI == sd_card_p->type) return false; // SPI can't do full SD Status + + uint8_t status[64] = {0}; + bool ok = rp2040_sdio_get_sd_status(sd_card_p, status); + if (!ok) return false; + // 431:428 AU_SIZE + uint8_t au_size = ext_bits(64, status, 431, 428); + switch (au_size) { + // AU_SIZE Value Definition + case 0x0: + *au_size_bytes_p = 0; + break; // Not Defined + case 0x1: + *au_size_bytes_p = 16 * KB; + break; + case 0x2: + *au_size_bytes_p = 32 * KB; + break; + case 0x3: + *au_size_bytes_p = 64 * KB; + break; + case 0x4: + *au_size_bytes_p = 128 * KB; + break; + case 0x5: + *au_size_bytes_p = 256 * KB; + break; + case 0x6: + *au_size_bytes_p = 512 * KB; + break; + case 0x7: + *au_size_bytes_p = 1 * MB; + break; + case 0x8: + *au_size_bytes_p = 2 * MB; + break; + case 0x9: + *au_size_bytes_p = 4 * MB; + break; + case 0xA: + *au_size_bytes_p = 8 * MB; + break; + case 0xB: + *au_size_bytes_p = 12 * MB; + break; + case 0xC: + *au_size_bytes_p = 16 * MB; + break; + case 0xD: + *au_size_bytes_p = 24 * MB; + break; + case 0xE: + *au_size_bytes_p = 32 * MB; + break; + case 0xF: + *au_size_bytes_p = 64 * MB; + break; + default: + myASSERT(false); + } + return true; +} + +/* [] END OF FILE */ diff --git a/lib/sd_driver/sd_card.h b/lib/sd_driver/sd_card.h new file mode 100644 index 0000000..6e6ee8b --- /dev/null +++ b/lib/sd_driver/sd_card.h @@ -0,0 +1,163 @@ +/* sd_card.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +// Note: The model used here is one FatFS per SD card. +// Multiple partitions on a card are not supported. + +#pragma once + +#include +#include +#include +// +#include + +#include "hardware/gpio.h" +#include "pico/mutex.h" +// +#include "ff.h" +// +#include "SDIO/rp2040_sdio.h" +#include "SPI/my_spi.h" +#include "SPI/sd_card_spi.h" +#include "diskio.h" +#include "sd_card_constants.h" +#include "sd_regs.h" +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { SD_IF_NONE, SD_IF_SPI, SD_IF_SDIO } sd_if_t; + +typedef struct sd_spi_if_state_t { + bool ongoing_mlt_blk_wrt; + uint32_t cont_sector_wrt; + uint32_t n_wrt_blks_reqd; +} sd_spi_if_state_t; + +typedef struct sd_spi_if_t { + spi_t *spi; + // Slave select is here instead of in spi_t because multiple SDs can share an SPI. + uint ss_gpio; // Slave select for this SD card + // Drive strength levels for GPIO outputs: + // GPIO_DRIVE_STRENGTH_2MA + // GPIO_DRIVE_STRENGTH_4MA + // GPIO_DRIVE_STRENGTH_8MA + // GPIO_DRIVE_STRENGTH_12MA + bool set_drive_strength; + enum gpio_drive_strength ss_gpio_drive_strength; + sd_spi_if_state_t state; +} sd_spi_if_t; + +typedef struct sd_sdio_if_t { + // See sd_driver\SDIO\rp2040_sdio.pio for SDIO_CLK_PIN_D0_OFFSET + uint CLK_gpio; // Must be (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32 + uint CMD_gpio; + uint D0_gpio; // D0 + uint D1_gpio; // Must be D0 + 1 + uint D2_gpio; // Must be D0 + 2 + uint D3_gpio; // Must be D0 + 3 + PIO SDIO_PIO; // either pio0 or pio1 + uint DMA_IRQ_num; // DMA_IRQ_0 or DMA_IRQ_1 + bool use_exclusive_DMA_IRQ_handler; + uint baud_rate; + // Drive strength levels for GPIO outputs: + // GPIO_DRIVE_STRENGTH_2MA + // GPIO_DRIVE_STRENGTH_4MA + // GPIO_DRIVE_STRENGTH_8MA + // GPIO_DRIVE_STRENGTH_12MA + bool set_drive_strength; + enum gpio_drive_strength CLK_gpio_drive_strength; + enum gpio_drive_strength CMD_gpio_drive_strength; + enum gpio_drive_strength D0_gpio_drive_strength; + enum gpio_drive_strength D1_gpio_drive_strength; + enum gpio_drive_strength D2_gpio_drive_strength; + enum gpio_drive_strength D3_gpio_drive_strength; + + /* The following fields are not part of the configuration. + They are state variables, and are dynamically assigned. */ + sd_sdio_if_state_t state; +} sd_sdio_if_t; + +typedef struct sd_card_state_t { + DSTATUS m_Status; // Card status + card_type_t card_type; // Assigned dynamically + CSD_t CSD; // Card-Specific Data register. + CID_t CID; // Card IDentification register + uint32_t sectors; // Assigned dynamically + + mutex_t mutex; + FATFS fatfs; + bool mounted; +#if FF_STR_VOLUME_ID + char drive_prefix[32]; +#else + char drive_prefix[4]; +#endif +} sd_card_state_t; + +typedef struct sd_card_t sd_card_t; + +// "Class" representing SD Cards +struct sd_card_t { + sd_if_t type; // Interface type + union { + sd_spi_if_t *spi_if_p; + sd_sdio_if_t *sdio_if_p; + }; + bool use_card_detect; + uint card_detect_gpio; // Card detect; ignored if !use_card_detect + uint card_detected_true; // Varies with card socket; ignored if !use_card_detect + bool card_detect_use_pull; + bool card_detect_pull_hi; + + /* The following fields are state variables and not part of the configuration. + They are dynamically assigned. */ + sd_card_state_t state; + + DSTATUS (*init)(sd_card_t *sd_card_p); + void (*deinit)(sd_card_t *sd_card_p); + block_dev_err_t (*write_blocks)(sd_card_t *sd_card_p, const uint8_t *buffer, + uint32_t ulSectorNumber, uint32_t blockCnt); + block_dev_err_t (*read_blocks)(sd_card_t *sd_card_p, uint8_t *buffer, + uint32_t ulSectorNumber, uint32_t ulSectorCount); + block_dev_err_t (*sync)(sd_card_t *sd_card_p); + uint32_t (*get_num_sectors)(sd_card_t *sd_card_p); + + // Useful when use_card_detect is false - call periodically to check for presence of SD card + // Returns true if and only if SD card was sensed on the bus + bool (*sd_test_com)(sd_card_t *sd_card_p); +}; + +void sd_lock(sd_card_t *sd_card_p); +void sd_unlock(sd_card_t *sd_card_p); +bool sd_is_locked(sd_card_t *sd_card_p); + +bool sd_init_driver(); +bool sd_card_detect(sd_card_t *sd_card_p); +void cidDmp(sd_card_t *sd_card_p, printer_t printer); +void csdDmp(sd_card_t *sd_card_p, printer_t printer); +bool sd_allocation_unit(sd_card_t *sd_card_p, size_t *au_size_bytes_p); +sd_card_t *sd_get_by_drive_prefix(const char *const name); + +// sd_init_driver() must be called before this: +char const *sd_get_drive_prefix(sd_card_t *sd_card_p); + +#ifdef __cplusplus +} +#endif + +/* [] END OF FILE */ diff --git a/lib/sd_driver/sd_card_constants.h b/lib/sd_driver/sd_card_constants.h new file mode 100644 index 0000000..e99bca7 --- /dev/null +++ b/lib/sd_driver/sd_card_constants.h @@ -0,0 +1,103 @@ +/* sd_card_constants.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/*!< Block size supported for SD card is 512 bytes */ +// Only HC block size is supported. +static const size_t sd_block_size = 512; + +typedef enum { + SD_BLOCK_DEVICE_ERROR_NONE = 0, + SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK = 1 << 0, /*!< operation would block */ + SD_BLOCK_DEVICE_ERROR_UNSUPPORTED = 1 << 1, /*!< unsupported operation */ + SD_BLOCK_DEVICE_ERROR_PARAMETER = 1 << 2, /*!< invalid parameter */ + SD_BLOCK_DEVICE_ERROR_NO_INIT = 1 << 3, /*!< uninitialized */ + SD_BLOCK_DEVICE_ERROR_NO_DEVICE = 1 << 4, /*!< device is missing or not connected */ + SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED = 1 << 5, /*!< write protected */ + SD_BLOCK_DEVICE_ERROR_UNUSABLE = 1 << 6, /*!< unusable card */ + SD_BLOCK_DEVICE_ERROR_NO_RESPONSE = 1 << 7, /*!< No response from device */ + SD_BLOCK_DEVICE_ERROR_CRC = 1 << 8, /*!< CRC error */ + SD_BLOCK_DEVICE_ERROR_ERASE = 1 << 9, /*!< Erase error: reset/sequence */ + SD_BLOCK_DEVICE_ERROR_WRITE = 1 << 10 /*!< Write error: !SPI_DATA_ACCEPTED */ +} block_dev_err_t; + +/** Represents the different SD/MMC card types */ +typedef enum { + SDCARD_NONE = 0, /**< No card is present */ + SDCARD_V1 = 1, /**< v1.x Standard Capacity */ + SDCARD_V2 = 2, /**< v2.x Standard capacity SD card */ + SDCARD_V2HC = 3, /**< v2.x High capacity SD card */ + CARD_UNKNOWN = 4 /**< Unknown or unsupported card */ +} card_type_t; + +/* On the wire, convert to hex and add 0x40 for transmitter bit. +e.g.: CMD17_READ_SINGLE_BLOCK: 17 = 0x11; 0x11 | 0x40 = 0x51 */ +// Supported SD Card Commands +typedef enum { /* Number on wire in parens */ + CMD_NOT_SUPPORTED = -1, /* Command not supported error */ + CMD0_GO_IDLE_STATE = 0, /* Resets the SD Memory Card */ + CMD1_SEND_OP_COND = 1, /* Sends host capacity support */ + CMD2_ALL_SEND_CID = 2, /* Asks any card to send the CID. */ + CMD3_SEND_RELATIVE_ADDR = 3, /* Ask the card to publish a new RCA. */ + CMD6_SWITCH_FUNC = 6, /* Check and Switches card function */ + CMD7_SELECT_CARD = 7, /* SELECT/DESELECT_CARD - toggles between the stand-by and transfer states. */ + CMD8_SEND_IF_COND = 8, /* Supply voltage info */ + CMD9_SEND_CSD = 9, /* Provides Card Specific data */ + CMD10_SEND_CID = 10, /* Provides Card Identification */ + CMD12_STOP_TRANSMISSION = 12, /* Forces the card to stop transmission */ + CMD13_SEND_STATUS = 13, /* (0x4D) Card responds with status */ + CMD16_SET_BLOCKLEN = 16, /* Length for SC card is set */ + CMD17_READ_SINGLE_BLOCK = 17, /* (0x51) Read single block of data */ + CMD18_READ_MULTIPLE_BLOCK = 18, /* (0x52) Continuously Card transfers data blocks to host + until interrupted by a STOP_TRANSMISSION command */ + CMD24_WRITE_BLOCK = 24, /* (0x58) Write single block of data */ + CMD25_WRITE_MULTIPLE_BLOCK = 25, /* (0x59) Continuously writes blocks of data + until 'Stop Tran' token is sent */ + CMD27_PROGRAM_CSD = 27, /* Programming bits of CSD */ + CMD32_ERASE_WR_BLK_START_ADDR = 32, /* Sets the address of the first write + block to be erased. */ + CMD33_ERASE_WR_BLK_END_ADDR = 33, /* Sets the address of the last write + block of the continuous range to be erased.*/ + CMD38_ERASE = 38, /* Erases all previously selected write blocks */ + CMD55_APP_CMD = 55, /* Extend to Applications specific commands */ + CMD56_GEN_CMD = 56, /* General Purpose Command */ + CMD58_READ_OCR = 58, /* Read OCR register of card */ + CMD59_CRC_ON_OFF = 59, /* Turns the CRC option on or off*/ + // App Commands + ACMD6_SET_BUS_WIDTH = 6, + ACMD13_SD_STATUS = 13, + ACMD22_SEND_NUM_WR_BLOCKS = 22, + ACMD23_SET_WR_BLK_ERASE_COUNT = 23, + ACMD41_SD_SEND_OP_COND = 41, + ACMD42_SET_CLR_CARD_DETECT = 42, + ACMD51_SEND_SCR = 51, +} cmdSupported; +//------------------------------------------------------------------------------ + +///* Disk Status Bits (DSTATUS) */ +// See diskio.h. +// enum { +// STA_NOINIT = 0x01, /* Drive not initialized */ +// STA_NODISK = 0x02, /* No medium in the drive */ +// STA_PROTECT = 0x04 /* Write protected */ +//}; + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/sd_regs.h b/lib/sd_driver/sd_regs.h new file mode 100644 index 0000000..db2207f --- /dev/null +++ b/lib/sd_driver/sd_regs.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include "util.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* ++-----------------------+-------+-------+-----------+ +| Name | Field | Width | CID-slice | ++-----------------------+-------+-------+-----------+ +| Manufacturer ID | MID | 8 | [127:120] | +| OEM/Application ID | OID | 16 | [119:104] | +| Product name | PNM | 40 | [103:64] | +| Product revision | PRV | 8 | [63:56] | +| Product serial number | PSN | 32 | [55:24] | +| reserved | -- | 4 | [23:20] | +| Manufacturing date | MDT | 12 | [19:8] | +| CRC7 checksum | CRC | 7 | [7:1] | +| not used, always 1- | 1 | [0:0] | | ++-----------------------+-------+-------+-----------+ +*/ +// Table 5-2: The CID Fields +typedef uint8_t CID_t[16]; + +/* ++---------------+-----------------------+-------------------------------------+ +| CSD_STRUCTURE | CSD structure version | Card Capacity | ++---------------+-----------------------+-------------------------------------+ +| 0 | CSD Version 1.0 | Standard Capacity | +| 1 | CSD Version 2.0 | High Capacity and Extended Capacity | +| 2-3 | reserved | | ++---------------+-----------------------+-------------------------------------+ +*/ +// Table 5-3: CSD Register Structure + +/* ++--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+ +| Name | Field | Width | Value | Cell Type | CSD-slice | ++--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+ +| CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] | +| reserved | - | 6 | 00 0000b | R | [125:120] | +| data read access-time-1 | TAAC | 8 | xxh | R | [119:112] | +| data read access-time-2 in CLK cycles (NSAC*100) | NSAC | 8 | xxh | R | [111:104] | +| max. data transfer rate | TRAN_SPEED | 8 | 32h or 5Ah | R | [103:96] | +| card command classes | CCC | 12 | 01x110110101b | R | [95:84] | +| max. read data block length | READ_BL_LEN | 4 | xh | R | [83:80] | +| partial blocks for read allowed | READ_BL_PARTIAL | 1 | 1b | R | [79:79] | +| write block misalignment | WRITE_BLK_MISALIGN | 1 | xb | R | [78:78] | +| read block misalignment | READ_BLK_MISALIGN | 1 | xb | R | [77:77] | +| DSR implemented | DSR_IMP | 1 | xb | R | [76:76] | +| reserved | - | 2 | 00b | R | [75:74] | +| device size | C_SIZE | 12 | xxxh | R | [73:62] | +| max. read current @VDD min | VDD_R_CURR_MIN | 3 | xxxb | R | [61:59] | +| max. read current @VDD max | VDD_R_CURR_MAX | 3 | xxxb | R | [58:56] | +| max. write current @VDD min | VDD_W_CURR_MIN | 3 | xxxb | R | [55:53] | +| max. write current @VDD max | VDD_W_CURR_MAX | 3 | xxxb | R | [52:50] | +| device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] | +| erase single block enable | ERASE_BLK_EN | 1 | xb | R | [46:46] | +| erase sector size | SECTOR_SIZE | 7 | xxxxxxxb | R | [45:39] | +| write protect group size | WP_GRP_SIZE | 7 | xxxxxxxb | R | [38:32] | +| write protect group enable | WP_GRP_ENABLE | 1 | xb | R | [31:31] | +| reserved | (Do not use) | 2 | 00b | R | [30:29] | +| write speed factor | R2W_FACTOR | 3 | xxxb | R | [28:26] | +| max. write data block length | WRITE_BL_LEN | 4 | xxxxb | R | [25:22] | +| partial blocks for write allowed | WRITE_BL_PARTIAL | 1 | xb | R | [21:21] | +| reserved | - | 5 | 00000b | R | [20:16] | +| File format group | FILE_FORMAT_GRP | 1 | xb | R/W(1) | [15:15] | +| copy flag | COPY | 1 | xb | R/W(1) | [14:14] | +| permanent write protection | PERM_WRITE_PROTECT | 1 | xb | R/W(1) | [13:13] | +| temporary write protection | TMP_WRITE_PROTECT | 1 | xb | R/W | [12:12] | +| File format | FILE_FORMAT | 2 | xxb | R/W(1) | [11:10] | +| reserved | | 2 | 00b | R/W | [9:8] | +| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] | +| not used, always'1' | - | 1 | 1b | - | [0:0] | ++--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+ +*/ +// Table 5-4: The CSD Register Fields (CSD Version 1.0) + +/* ++------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+ +| Name | Field | Width | Value | Cell Type | CSD-slice | ++------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+ +| CSD structure | CSD_STRUCTURE | 2 | 01b | R | [127:126] | +| reserved | - | 6 | 00 0000b | R | [125:120] | +| data read access-time | (TAAC) | 8 | 0Eh | R | [119:112] | +| data read access-time in CLK cycles (NSAC*100) | (NSAC) | 8 | 00h | R | [111:104] | +| max. data transfer rate | (TRAN_SPEED) | 8 | 32h, 5Ah, 0Bh or 2Bh | R | [103:96] | +| card command classes | CCC | 12 | 01x110110101b | R | [95:84] | +| max. read data block length | (READ_BL_LEN) | 4 | 9 | R | [83:80] | +| partial blocks for read allowed | (READ_BL_PARTIAL) | 1 | 0 | R | [79:79] | +| write block misalignment | (WRITE_BLK_MISALIGN) | 1 | 0 | R | [78:78] | +| read block misalignment | (READ_BLK_MISALIGN) | 1 | 0 | R | [77:77] | +| DSR implemented | DSR_IMP | 1 | x | R | [76:76] | +| reserved | - | 6 | 00 0000b | R | [75:70] | +| device size | C_SIZE | 22 | xxxxxxh | R | [69:48] | +| reserved | - | 1 | 0 | R | [47:47] | +| erase single block enable | (ERASE_BLK_EN) | 1 | 1 | R | [46:46] | +| erase sector size | (SECTOR_SIZE) | 7 | 7Fh | R | [45:39] | +| write protect group size | (WP_GRP_SIZE) | 7 | 0000000b | R | [38:32] | +| write protect group enable | (WP_GRP_ENABLE) | 1 | 0 | R | [31:31] | +| reserved | | 2 | 00b | R | [30:29] | +| write speed factor | (R2W_FACTOR) | 3 | 010b | R | [28:26] | +| max. write data block length | (WRITE_BL_LEN) | 4 | 9 | R | [25:22] | +| partial blocks for write allowed | (WRITE_BL_PARTIAL) | 1 | 0 | R | [21:21] | +| reserved | - | 5 | 00000b | R | [20:16] | +| File format group | (FILE_FORMAT_GRP) | 1 | 0 | R | [15:15] | +| copy flag | COPY | 1 | x | R/W(1) | [14:14] | +| permanent write protection | PERM_WRITE_PROTECT | 1 | x | R/W(1) | [13:13] | +| temporary write protection | TMP_WRITE_PROTECT | 1 | x | R/W | [12:12] | +| File format | (FILE_FORMAT) | 2 | 00b | R | [11:10] | +| reserved | - | 2 | 00b | R | [9:8] | +| CRC | CRC | 7 | xxxxxxxb | R/W | [7:1] | +| not used, always'1' | - | 1 | 1 | - | [0:0] | ++------------------------------------------------+----------------------+-------+----------------------+-----------+-----------+ +*/ +// Table 5-16: The CSD Register Fields (CSD Version 2.0) +typedef uint8_t CSD_t[16]; + +/* return Capacity in sectors */ +static inline uint32_t CSD_sectors(CSD_t csd) /* const */ { + uint32_t c_size; + // +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+ + // | Name | Field | Width | Value | Cell Type | CSD-slice | + // +--------------------------------------------------+--------------------+-------+---------------+-----------+-----------+ + // | CSD structure | CSD_STRUCTURE | 2 | 00b | R | [127:126] | + uint8_t ver = ext_bits16(csd, 127, 126); + if (ver == 0) { + // | device size | C_SIZE | 12 | xxxh | R | [73:62] | + c_size = ext_bits16(csd, 73, 62); + // | device size multiplier | C_SIZE_MULT | 3 | xxxb | R | [49:47] | + uint8_t c_size_mult = ext_bits16(csd, 49, 47); + // MULT = 2^(C_SIZE_MULT+2) + uint32_t mult = 1UL << (c_size_mult + 2); + // BLOCKNR = (C_SIZE+1) * MULT + return (c_size + 1) * mult; + } else if (ver == 1) { + // | device size | C_SIZE | 22 | xxxxxxh | R | [69:48] | + c_size = ext_bits16(csd, 69, 48); + /* The user data area capacity is calculated from C_SIZE as follows: + memory capacity = (C_SIZE+1) * 512K byte */ + return (c_size + 1) * 1024; // sectors + } else { + return 0; + } +} + +#ifdef __cplusplus +} +#endif diff --git a/lib/sd_driver/sd_timeouts.c b/lib/sd_driver/sd_timeouts.c new file mode 100644 index 0000000..0317f2a --- /dev/null +++ b/lib/sd_driver/sd_timeouts.c @@ -0,0 +1,19 @@ + +#include "sd_timeouts.h" + +sd_timeouts_t sd_timeouts __attribute__((weak)) = { + .sd_command = 2000, // Timeout in ms for response + .sd_command_retries = 3, // Times SPI cmd is retried when there is no response + .sd_lock = 8000, // Timeout in ms for response + .sd_spi_read = 1000, // Timeout in ms for response + .sd_spi_write = 1000, // Timeout in ms for response + .sd_spi_write_read = 1000, // Timeout in ms for response + .spi_lock = 4000, // Timeout in ms for response + .rp2040_sdio_command_R1 = 10, // Timeout in ms for response + .rp2040_sdio_command_R2 = 2, // Timeout in ms for response + .rp2040_sdio_command_R3 = 2, // Timeout in ms for response + .rp2040_sdio_rx_poll = 1000, // Timeout in ms for response + .rp2040_sdio_tx_poll = 5000, // Timeout in ms for response + .sd_sdio_begin = 1000, // Timeout in ms for response + .sd_sdio_stopTransmission = 200, // Timeout in ms for response +}; diff --git a/lib/sd_driver/sd_timeouts.h b/lib/sd_driver/sd_timeouts.h new file mode 100644 index 0000000..48d8db2 --- /dev/null +++ b/lib/sd_driver/sd_timeouts.h @@ -0,0 +1,23 @@ + +#pragma once + +#include + +typedef struct { + uint32_t sd_command; + unsigned sd_command_retries; + unsigned sd_lock; + unsigned sd_spi_read; + unsigned sd_spi_write; + unsigned sd_spi_write_read; + unsigned spi_lock; + unsigned rp2040_sdio_command_R1; + unsigned rp2040_sdio_command_R2; + unsigned rp2040_sdio_command_R3; + unsigned rp2040_sdio_rx_poll; + unsigned rp2040_sdio_tx_poll; + unsigned sd_sdio_begin; + unsigned sd_sdio_stopTransmission; +} sd_timeouts_t; + +extern sd_timeouts_t sd_timeouts; diff --git a/lib/sd_driver/util.h b/lib/sd_driver/util.h new file mode 100755 index 0000000..5562aa3 --- /dev/null +++ b/lib/sd_driver/util.h @@ -0,0 +1,91 @@ +/* util.h +Copyright 2021 Carl John Kugler III + +Licensed under the Apache License, Version 2.0 (the License); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +*/ + +#pragma once + +#include +#include +#include +// +#include "my_debug.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +// Greatest Common Divisor: Euclidian Algorithm +// https://www.freecodecamp.org/news/euclidian-gcd-algorithm-greatest-common-divisor/ +int gcd(int a,int b); + +typedef int (*printer_t)(const char* format, ...); + +// works with negative index +static inline int wrap_ix(int index, int n) +{ + return ((index % n) + n) % n; +} +// Calculate arr indices with wrap around (+ and -) +static inline int mod_floor(int a, int n) { + return ((a % n) + n) % n; +} + +__attribute__((always_inline)) static inline uint32_t calculate_checksum(uint32_t const *p, size_t const size){ + uint32_t checksum = 0; + for (uint32_t i = 0; i < (size/sizeof(uint32_t))-1; i++){ + checksum ^= *p; + p++; + } + return checksum; +} + +static inline void ext_str(size_t const data_sz, + uint8_t const data[], + size_t const msb, + size_t const lsb, + size_t const buf_sz, + char buf[]) { + memset(buf, 0, buf_sz); + size_t size = (1 + msb - lsb) / 8; // bytes + size_t byte = (data_sz - 1) - (msb / 8); + for (uint32_t i = 0; i < size; i++) { + myASSERT(i < buf_sz); + myASSERT(byte < data_sz); + buf[i] = data[byte++]; + } +} + +static inline uint32_t ext_bits(size_t n_src_bytes, unsigned char const *data, int msb, int lsb) { + uint32_t bits = 0; + uint32_t size = 1 + msb - lsb; + for (uint32_t i = 0; i < size; i++) { + uint32_t position = lsb + i; + uint32_t byte = (n_src_bytes - 1) - (position >> 3); + uint32_t bit = position & 0x7; + uint32_t value = (data[byte] >> bit) & 1; + bits |= value << i; + } + return bits; +} +static inline uint32_t ext_bits16(unsigned char const *data, int msb, int lsb) { + return ext_bits(16, data, msb, lsb); +} + +char const* uint8_binary_str(uint8_t number); +char const* uint_binary_str(unsigned int number); + +#ifdef __cplusplus +} +#endif +/* [] END OF FILE */ diff --git a/main.c b/main.c index a3b80e0..e7a374f 100644 --- a/main.c +++ b/main.c @@ -28,7 +28,10 @@ #include #include "bsp/board_api.h" +#include "lib/sd_driver/sd_card.h" #include "tusb.h" +#include "ff.h" +#include "f_util.h" //--------------------------------------------------------------------+ // MACRO CONSTANT TYPEDEF PROTYPES @@ -43,6 +46,59 @@ extern uint8_t tuh_max3421_reg_read(uint8_t rhport, uint8_t reg, bool in_isr); extern bool tuh_max3421_reg_write(uint8_t rhport, uint8_t reg, uint8_t data, bool in_isr); #endif +//--------------------------------------------------------------------+ +// SD CARD +//--------------------------------------------------------------------+ +void log_to_sd(const char * message); +FIL fil_sdcard; // File to log on the SD Card + +/* SDIO Interface */ +static sd_sdio_if_t sdio_if = { + /* + Pins CLK_gpio, D1_gpio, D2_gpio, and D3_gpio are at offsets from pin D0_gpio. + The offsets are determined by sd_driver\SDIO\rp2040_sdio.pio. + CLK_gpio = (D0_gpio + SDIO_CLK_PIN_D0_OFFSET) % 32; + As of this writing, SDIO_CLK_PIN_D0_OFFSET is 30, + which is -2 in mod32 arithmetic, so: + CLK_gpio = D0_gpio -2. + D1_gpio = D0_gpio + 1; + D2_gpio = D0_gpio + 2; + D3_gpio = D0_gpio + 3; + */ + .CMD_gpio = 3, + .D0_gpio = 4, + .baud_rate = 125 * 1000 * 1000 / 6 // 20833333 Hz +}; + +/* Hardware Configuration of the SD Card socket "object" */ +static sd_card_t sd_card = {.type = SD_IF_SDIO, .sdio_if_p = &sdio_if}; + +/** + * @brief Get the number of SD cards. + * + * @return The number of SD cards, which is 1 in this case. + */ +size_t sd_get_num() { return 1; } + +/** + * @brief Get a pointer to an SD card object by its number. + * + * @param[in] num The number of the SD card to get. + * + * @return A pointer to the SD card object, or @c NULL if the number is invalid. + */ +sd_card_t* sd_get_by_num(size_t num) { + if (0 == num) { + // The number 0 is a valid SD card number. + // Return a pointer to the sd_card object. + return &sd_card; + } else { + // The number is invalid. Return @c NULL. + return NULL; + } +} + + /*------------- MAIN -------------*/ int main(void) { board_init(); @@ -62,6 +118,27 @@ int main(void) { tuh_max3421_reg_write(BOARD_TUH_RHPORT, IOPINS1_ADDR, 0x01, false); #endif + // Init SD Card for log + FATFS fs; + char drive_path[3] = "0:"; + drive_path[0] += DEV_SDIO_MIN; + FRESULT fr = f_mount(&fs, drive_path, 1); + if (FR_OK != fr) { + panic("f_mount error: %s (%d)\n", FRESULT_str(fr), fr); + return -1; + }else{ + printf("SD Card mounted on %s\n", drive_path); + } + + char* const filepath = "0:/log.txt"; + filepath[0] = drive_path[0]; + fr = f_open(&fil_sdcard, filepath, FA_OPEN_ALWAYS | FA_WRITE); + if (FR_OK != fr && FR_EXIST != fr) { + panic("f_open(%s) error: %s (%d)\n", filepath, FRESULT_str(fr), fr); + return -1; + } + + while (1) { // tinyusb host task tuh_task(); @@ -78,12 +155,18 @@ int main(void) { void tuh_mount_cb(uint8_t dev_addr) { // application set-up + char message[100]; printf("A device with address %d is mounted\r\n", dev_addr); + sprintf(message, "A device with address %d is mounted\r\n", dev_addr); + log_to_sd(message); } void tuh_umount_cb(uint8_t dev_addr) { // application tear-down + char message[100]; printf("A device with address %d is unmounted \r\n", dev_addr); + sprintf(message, "A device with address %d is unmounted\r\n", dev_addr); + log_to_sd(message); } @@ -103,3 +186,21 @@ void led_blinking_task(void) { board_led_write(led_state); led_state = 1 - led_state; // toggle } + + +void log_to_sd(const char * chaine){ + static int i=0; + unsigned int nb_char_written, strlen_chaine; + absolute_time_t current_time, start_time; + FRESULT error; + strlen_chaine = strlen(chaine); + start_time = get_absolute_time(); + error = f_write(&fil_sdcard, chaine, strlen_chaine, &nb_char_written); + if (error != FR_OK) { + printf("f_write failed, error: %d\n", error); + } + f_sync(&fil_sdcard); + current_time = get_absolute_time(); + printf("Ecrit: %d octets en %llu us\n", nb_char_written, current_time - start_time); + printf(">vitesse(Mo/s):%d:%.1f\n", i++, (float)(nb_char_written) / (float)(current_time - start_time)); +} \ No newline at end of file diff --git a/msc_app.c b/msc_app.c index 9dc57f8..75a4a12 100644 --- a/msc_app.c +++ b/msc_app.c @@ -70,7 +70,7 @@ bool inquiry_complete_cb(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_da // Monter le système de fichier avec FatFs uint8_t const drive_num = dev_addr-1; char drive_path[3] = "0:"; - drive_path[0] += drive_num; + drive_path[0] += drive_num + DEV_USB_MIN; printf("dev_addr: %d\ndrive_num: %d\n", dev_addr, drive_num); error = f_mount(&fatfs[drive_num], drive_path, 1); if ( error != FR_OK )