Compare commits
No commits in common. "ea82637e9901df83deab2d07c15d08d18078e080" and "2f051541cc9c869068f3d0761cb6d44979014d2a" have entirely different histories.
ea82637e99
...
2f051541cc
3
.vscode/c_cpp_properties.json
vendored
@ -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
@ -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"
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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.
|
173
diskio_SDIO.c
@ -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;
|
||||
}
|
||||
}
|
123
diskio_USB.c
@ -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
|
||||
;
|
||||
}*/
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
@ -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
|
@ -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
|
@ -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;
|
||||
}
|
@ -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 */
|
@ -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 */
|
@ -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 */
|
@ -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 ‘1’s. The sequence length is the maximum of 1msec,
|
||||
74 clocks or the supply-ramp-uptime; the additional 10 clocks
|
||||
(over the 64 clocks after what the card should be ready for communication) is
|
||||
provided to eliminate power-up synchronization problems.
|
||||
*/
|
||||
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p) {
|
||||
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
|
||||
bool old_ss = gpio_get(sd_card_p->spi_if_p->ss_gpio);
|
||||
// Set DI and CS high and apply 74 or more clock pulses to SCLK:
|
||||
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
|
||||
uint8_t ones[10];
|
||||
memset(ones, 0xFF, sizeof ones);
|
||||
uint32_t start = millis();
|
||||
do {
|
||||
spi_transfer(sd_card_p->spi_if_p->spi, ones, NULL, sizeof ones);
|
||||
} while (millis() - start < 1);
|
||||
gpio_put(sd_card_p->spi_if_p->ss_gpio, old_ss);
|
||||
}
|
||||
|
||||
/* [] END OF FILE */
|
@ -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 ‘1’s. The sequence length is the maximum of
|
||||
1msec, 74 clocks or the supply-ramp-uptime; the additional 10 clocks (over the 64 clocks after
|
||||
what the card should be ready for communication) is provided to eliminate power-up
|
||||
synchronization problems.
|
||||
*/
|
||||
void sd_spi_send_initializing_sequence(sd_card_t *sd_card_p);
|
||||
|
||||
//FIXME: sd_spi_read, sd_spi_write, and sd_spi_write_read should return an error code on timeout.
|
||||
|
||||
static inline uint8_t sd_spi_read(sd_card_t *sd_card_p) {
|
||||
uint8_t received = SPI_FILL_CHAR;
|
||||
uint32_t start = millis();
|
||||
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
|
||||
millis() - start < sd_timeouts.sd_spi_read)
|
||||
tight_loop_contents();
|
||||
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
|
||||
int num = spi_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, SPI_FILL_CHAR, &received, 1);
|
||||
myASSERT(1 == num);
|
||||
return received;
|
||||
}
|
||||
|
||||
static inline void sd_spi_write(sd_card_t *sd_card_p, const uint8_t value) {
|
||||
uint32_t start = millis();
|
||||
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
|
||||
millis() - start < sd_timeouts.sd_spi_write)
|
||||
tight_loop_contents();
|
||||
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
|
||||
int num = spi_write_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, 1);
|
||||
myASSERT(1 == num);
|
||||
}
|
||||
static inline uint8_t sd_spi_write_read(sd_card_t *sd_card_p, const uint8_t value) {
|
||||
uint8_t received = SPI_FILL_CHAR;
|
||||
uint32_t start = millis();
|
||||
while (!spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst) &&
|
||||
millis() - start < sd_timeouts.sd_spi_write_read)
|
||||
tight_loop_contents();
|
||||
myASSERT(spi_is_writable(sd_card_p->spi_if_p->spi->hw_inst));
|
||||
int num = spi_write_read_blocking(sd_card_p->spi_if_p->spi->hw_inst, &value, &received, 1);
|
||||
myASSERT(1 == num);
|
||||
return received;
|
||||
}
|
||||
|
||||
// Would do nothing if sd_card_p->spi_if_p->ss_gpio were set to GPIO_FUNC_SPI.
|
||||
static inline void sd_spi_select(sd_card_t *sd_card_p) {
|
||||
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
|
||||
gpio_put(sd_card_p->spi_if_p->ss_gpio, 0);
|
||||
// See http://elm-chan.org/docs/mmc/mmc_e.html#spibus
|
||||
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
|
||||
LED_ON();
|
||||
}
|
||||
|
||||
static inline void sd_spi_deselect(sd_card_t *sd_card_p) {
|
||||
if ((uint)-1 == sd_card_p->spi_if_p->ss_gpio) return;
|
||||
gpio_put(sd_card_p->spi_if_p->ss_gpio, 1);
|
||||
LED_OFF();
|
||||
/*
|
||||
MMC/SDC enables/disables the DO output in synchronising to the SCLK. This
|
||||
means there is a posibility of bus conflict with MMC/SDC and another SPI
|
||||
slave that shares an SPI bus. Therefore to make MMC/SDC release the MISO
|
||||
line, the master device needs to send a byte after the CS signal is
|
||||
deasserted.
|
||||
*/
|
||||
sd_spi_write(sd_card_p, SPI_FILL_CHAR);
|
||||
}
|
||||
|
||||
static inline void sd_spi_lock(sd_card_t *sd_card_p) { spi_lock(sd_card_p->spi_if_p->spi); }
|
||||
static inline void sd_spi_unlock(sd_card_t *sd_card_p) { spi_unlock(sd_card_p->spi_if_p->spi); }
|
||||
|
||||
static inline void sd_spi_acquire(sd_card_t *sd_card_p) {
|
||||
sd_spi_lock(sd_card_p);
|
||||
sd_spi_select(sd_card_p);
|
||||
}
|
||||
static inline void sd_spi_release(sd_card_t *sd_card_p) {
|
||||
sd_spi_deselect(sd_card_p);
|
||||
sd_spi_unlock(sd_card_p);
|
||||
}
|
||||
|
||||
static inline void sd_spi_transfer_start(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
|
||||
size_t length) {
|
||||
return spi_transfer_start(sd_card_p->spi_if_p->spi, tx, rx, length);
|
||||
}
|
||||
static inline bool sd_spi_transfer_wait_complete(sd_card_t *sd_card_p, uint32_t timeout_ms) {
|
||||
return spi_transfer_wait_complete(sd_card_p->spi_if_p->spi, timeout_ms);
|
||||
}
|
||||
/* Transfer tx to SPI while receiving SPI to rx.
|
||||
tx or rx can be NULL if not important. */
|
||||
static inline bool sd_spi_transfer(sd_card_t *sd_card_p, const uint8_t *tx, uint8_t *rx,
|
||||
size_t length) {
|
||||
return spi_transfer(sd_card_p->spi_if_p->spi, tx, rx, length);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/* [] END OF FILE */
|
@ -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 */
|