Compare commits

..

No commits in common. "ea82637e9901df83deab2d07c15d08d18078e080" and "2f051541cc9c869068f3d0761cb6d44979014d2a" have entirely different histories.

132 changed files with 238 additions and 8065 deletions

View File

@ -6,8 +6,7 @@
"${workspaceFolder}/build/generated/pico_base",
"${env:PICO_SDK_PATH}/src/**/include",
"${env:PICO_SDK_PATH}/lib/**/src",
"${workspaceFolder}/lib/FatFs/source",
"${workspaceFolder}/lib/sd_driver"
"${workspaceFolder}/lib/source"
],
"myCompilerPath": "/usr/bin/arm-none-eabi-gcc"

12
.vscode/settings.json vendored
View File

@ -4,16 +4,6 @@
"tusb.h": "c",
"inttypes.h": "c",
"stdlib.h": "c",
"cdefs.h": "c",
"tusb_fifo.h": "c",
"tusb_common.h": "c",
"diskio.h": "c",
"ff.h": "c",
"time.h": "c",
"hw_config.h": "c",
"sd_card.h": "c",
"rp2040_sdio.h": "c",
"util.h": "c",
"stddef.h": "c"
"cdefs.h": "c"
}
}

View File

@ -12,61 +12,24 @@ 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
main.c
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
lib/source/ff.c
lib/source/ffsystem.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}/lib/sd_driver/)
if(PICO_RISCV)
set(HWDEP_LIBS hardware_watchdog)
else()
set(HWDEP_LIBS cmsis_core)
endif()
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/lib/source/)
# 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
hardware_dma
hardware_pio
hardware_spi
hardware_sync
pico_aon_timer
pico_stdlib
${HWDEP_LIBS}
)
target_link_libraries(host_cdc_msc_hid PUBLIC pico_stdlib tinyusb_host tinyusb_board)
pico_add_extra_outputs(host_cdc_msc_hid)

View File

@ -1,6 +0,0 @@
USB Hôte sur le RP2040 - Ajout FsFAT
======================
Code pour écrire un fichier sur une clé USB formatée en exFAT ou sur une carte SD, les fonctions cohabitent !
Compatible avec le PICO SDK v2.1.1.

View File

@ -1,173 +0,0 @@
/* 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;
}
}

View File

@ -1,123 +0,0 @@
#include "ff.h" /* Obtains integer types */
#include "tusb.h"
#include "diskio.h"
static volatile bool _disk_busy[CFG_TUH_DEVICE_MAX];
//--------------------------------------------------------------------+
// DiskIO
//--------------------------------------------------------------------+
static void wait_for_disk_io(BYTE pdrv)
{
while(_disk_busy[pdrv])
{
tuh_task();
}
}
static bool disk_io_complete(uint8_t dev_addr, tuh_msc_complete_data_t const * cb_data)
{
(void) dev_addr; (void) cb_data;
_disk_busy[dev_addr-1] = false;
return true;
}
DSTATUS USB_disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
uint8_t dev_addr = pdrv + 1;
return tuh_msc_mounted(dev_addr) ? 0 : STA_NODISK;
}
DSTATUS USB_disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
(void) pdrv;
return 0; // nothing to do
}
DRESULT USB_disk_read (
BYTE pdrv, /* Physical drive nmuber 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 */
)
{
uint8_t const dev_addr = pdrv + 1;
uint8_t const lun = 0;
_disk_busy[pdrv] = true;
tuh_msc_read10(dev_addr, lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
wait_for_disk_io(pdrv);
return RES_OK;
}
#if FF_FS_READONLY == 0
DRESULT USB_disk_write (
BYTE pdrv, /* Physical drive nmuber 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 */
)
{
uint8_t const dev_addr = pdrv + 1;
uint8_t const lun = 0;
_disk_busy[pdrv] = true;
tuh_msc_write10(dev_addr, lun, buff, sector, (uint16_t) count, disk_io_complete, 0);
wait_for_disk_io(pdrv);
return RES_OK;
}
#endif
DRESULT USB_disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
uint8_t const dev_addr = pdrv + 1;
uint8_t const lun = 0;
switch ( cmd )
{
case CTRL_SYNC:
// nothing to do since we do blocking
return RES_OK;
case GET_SECTOR_COUNT:
*((DWORD*) buff) = (WORD) tuh_msc_get_block_count(dev_addr, lun);
return RES_OK;
case GET_SECTOR_SIZE:
*((WORD*) buff) = (WORD) tuh_msc_get_block_size(dev_addr, lun);
return RES_OK;
case GET_BLOCK_SIZE:
*((DWORD*) buff) = 1; // erase block size in units of sector size
return RES_OK;
default:
return RES_PARERR;
}
return RES_OK;
}
/*
DWORD get_fattime (void){
return
(2025-1970) << 25 | // Année
6 << 21 | // Mois
7 << 16 | // jour du mois
17 << 11 | // Heures
29 << 5 | // Minutes
30 << 0 // Secondes
;
}*/

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,214 +0,0 @@
/**
* 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

View File

@ -1,181 +0,0 @@
// 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

View File

@ -1,860 +0,0 @@
// 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;
}

View File

@ -1,120 +0,0 @@
// 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

View File

@ -1,157 +0,0 @@
; 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

View File

@ -1,664 +0,0 @@
// 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;
}

View File

@ -1,371 +0,0 @@
/* 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 */

View File

@ -1,124 +0,0 @@
/* 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 */

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
/* 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 */

View File

@ -1,65 +0,0 @@
/* 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 1s. 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 */

View File

@ -1,135 +0,0 @@
/* 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 1s. 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 */

View File

@ -1,312 +0,0 @@
/* 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 */

Some files were not shown because too many files have changed in this diff Show More