Code fonctionnel, on écrit sur la carte SD et sur la clé USB
This commit is contained in:
parent
c877fd05f6
commit
e2d49bac4f
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@ -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"
|
||||
|
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@ -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"
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
173
diskio_SDIO.c
Normal file
173
diskio_SDIO.c
Normal file
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
;
|
||||
}
|
||||
}*/
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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) */
|
||||
|
||||
|
214
lib/sd_driver/SDIO/SdioCard.h
Executable file
214
lib/sd_driver/SDIO/SdioCard.h
Executable file
@ -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
|
181
lib/sd_driver/SDIO/ZuluSCSI_platform.h
Executable file
181
lib/sd_driver/SDIO/ZuluSCSI_platform.h
Executable file
@ -0,0 +1,181 @@
|
||||
// Platform-specific definitions for ZuluSCSI RP2040 hardware.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
// #include <Arduino.h>
|
||||
// #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
|
860
lib/sd_driver/SDIO/rp2040_sdio.c
Executable file
860
lib/sd_driver/SDIO/rp2040_sdio.c
Executable file
@ -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 <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
//
|
||||
#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;
|
||||
}
|
120
lib/sd_driver/SDIO/rp2040_sdio.h
Executable file
120
lib/sd_driver/SDIO/rp2040_sdio.h
Executable file
@ -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 <stdint.h>
|
||||
|
||||
#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
|
157
lib/sd_driver/SDIO/rp2040_sdio.pio
Executable file
157
lib/sd_driver/SDIO/rp2040_sdio.pio
Executable file
@ -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
|
664
lib/sd_driver/SDIO/sd_card_sdio.c
Normal file
664
lib/sd_driver/SDIO/sd_card_sdio.c
Normal file
@ -0,0 +1,664 @@
|
||||
// Driver for accessing SD card in SDIO mode on RP2040.
|
||||
|
||||
#include "ZuluSCSI_platform.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
//
|
||||
// Hardware
|
||||
//
|
||||
#include <hardware/dma.h>
|
||||
#include <hardware/gpio.h>
|
||||
#include <hardware/clocks.h>
|
||||
#include <hardware/pio.h>
|
||||
|
||||
//
|
||||
// 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;
|
||||
}
|
371
lib/sd_driver/SPI/my_spi.c
Executable file
371
lib/sd_driver/SPI/my_spi.c
Executable file
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
//
|
||||
#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 */
|
124
lib/sd_driver/SPI/my_spi.h
Executable file
124
lib/sd_driver/SPI/my_spi.h
Executable file
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
//
|
||||
// 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 */
|
1731
lib/sd_driver/SPI/sd_card_spi.c
Normal file
1731
lib/sd_driver/SPI/sd_card_spi.c
Normal file
File diff suppressed because it is too large
Load Diff
29
lib/sd_driver/SPI/sd_card_spi.h
Normal file
29
lib/sd_driver/SPI/sd_card_spi.h
Normal file
@ -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 */
|
65
lib/sd_driver/SPI/sd_spi.c
Executable file
65
lib/sd_driver/SPI/sd_spi.c
Executable file
@ -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 <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
//
|
||||
#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 */
|
135
lib/sd_driver/SPI/sd_spi.h
Executable file
135
lib/sd_driver/SPI/sd_spi.h
Executable file
@ -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 <stdint.h>
|
||||
//
|
||||
#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 */
|
312
lib/sd_driver/crash.c
Normal file
312
lib/sd_driver/crash.c
Normal file
@ -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 <string.h>
|
||||
#include <time.h>
|
||||
//
|
||||
#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 */
|
114
lib/sd_driver/crash.h
Normal file
114
lib/sd_driver/crash.h
Normal file
@ -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 <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
//
|
||||
#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 */
|
379
lib/sd_driver/crc.c
Executable file
379
lib/sd_driver/crc.c
Executable file
@ -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 */
|
73
lib/sd_driver/crc.h
Executable file
73
lib/sd_driver/crc.h
Executable file
@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @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 */
|
79
lib/sd_driver/delays.h
Normal file
79
lib/sd_driver/delays.h
Normal file
@ -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 <stdint.h>
|
||||
//
|
||||
#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 */
|
85
lib/sd_driver/dma_interrupts.c
Normal file
85
lib/sd_driver/dma_interrupts.c
Normal file
@ -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);
|
||||
}
|
||||
}
|
14
lib/sd_driver/dma_interrupts.h
Normal file
14
lib/sd_driver/dma_interrupts.h
Normal file
@ -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
|
155
lib/sd_driver/f_util.c
Normal file
155
lib/sd_driver/f_util.c
Normal file
@ -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 <assert.h>
|
||||
#include <stdio.h>
|
||||
//
|
||||
#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);
|
||||
}
|
32
lib/sd_driver/f_util.h
Executable file
32
lib/sd_driver/f_util.h
Executable file
@ -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
|
44
lib/sd_driver/hw_config.h
Normal file
44
lib/sd_driver/hw_config.h
Normal file
@ -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 <stddef.h>
|
||||
|
||||
#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 */
|
223
lib/sd_driver/my_debug.c
Normal file
223
lib/sd_driver/my_debug.c
Normal file
@ -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 <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
//
|
||||
#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 */
|
140
lib/sd_driver/my_debug.h
Executable file
140
lib/sd_driver/my_debug.h
Executable file
@ -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 <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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 */
|
159
lib/sd_driver/my_rtc.c
Normal file
159
lib/sd_driver/my_rtc.c
Normal file
@ -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 <assert.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
//
|
||||
#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;
|
||||
}
|
27
lib/sd_driver/my_rtc.h
Executable file
27
lib/sd_driver/my_rtc.h
Executable file
@ -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 <time.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern time_t epochtime;
|
||||
void time_init();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
124
lib/sd_driver/rp2040_sdio.pio.h
Normal file
124
lib/sd_driver/rp2040_sdio.pio.h
Normal file
@ -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
|
||||
|
386
lib/sd_driver/sd_card.c
Executable file
386
lib/sd_driver/sd_card.c
Executable file
@ -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 <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
//
|
||||
#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 */
|
163
lib/sd_driver/sd_card.h
Normal file
163
lib/sd_driver/sd_card.h
Normal file
@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
//
|
||||
#include <hardware/pio.h>
|
||||
|
||||
#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 */
|
103
lib/sd_driver/sd_card_constants.h
Normal file
103
lib/sd_driver/sd_card_constants.h
Normal file
@ -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
|
152
lib/sd_driver/sd_regs.h
Normal file
152
lib/sd_driver/sd_regs.h
Normal file
@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#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
|
19
lib/sd_driver/sd_timeouts.c
Normal file
19
lib/sd_driver/sd_timeouts.c
Normal file
@ -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
|
||||
};
|
23
lib/sd_driver/sd_timeouts.h
Normal file
23
lib/sd_driver/sd_timeouts.h
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
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;
|
91
lib/sd_driver/util.h
Executable file
91
lib/sd_driver/util.h
Executable file
@ -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 <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
//
|
||||
#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 */
|
101
main.c
101
main.c
@ -28,7 +28,10 @@
|
||||
#include <string.h>
|
||||
|
||||
#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));
|
||||
}
|
@ -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 )
|
||||
|
Loading…
Reference in New Issue
Block a user