Ditching default target for `make` in favour of `make all` (#14099)
[betaflight.git] / src / main / drivers / sdcard_sdio_baremetal.c
blob3342e3bc14a5868744f618d1673e79af8927b76f
1 /*
2 * This file is part of Cleanflight and Betaflight.
4 * Cleanflight and Betaflight are free software. You can redistribute
5 * this software and/or modify this software under the terms of the
6 * GNU General Public License as published by the Free Software
7 * Foundation, either version 3 of the License, or (at your option)
8 * any later version.
10 * Cleanflight and Betaflight are distributed in the hope that they
11 * will be useful, but WITHOUT ANY WARRANTY; without even the implied
12 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 * See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this software.
18 * If not, see <http://www.gnu.org/licenses/>.
21 /* Adaptation of original driver to SDIO: Chris Hockuba (https://github.com/conkerkh) */
23 #include <stdbool.h>
24 #include <stdint.h>
25 #include <string.h>
27 #include "platform.h"
29 #ifdef USE_SDCARD_SDIO
31 #include "drivers/nvic.h"
32 #include "drivers/io.h"
33 #include "drivers/dma.h"
34 #include "drivers/dma_reqmap.h"
36 #include "drivers/time.h"
38 #include "pg/bus_spi.h" // For spiPinConfig_t, which is unused but should be defined
39 #include "pg/sdio.h"
41 #include "drivers/sdcard.h"
42 #include "drivers/sdcard_impl.h"
43 #include "drivers/sdcard_standard.h"
44 #include "drivers/sdmmc_sdio.h"
46 // Use this to speed up writing to SDCARD... asyncfatfs has limited support for multiblock write
47 #define FATFS_BLOCK_CACHE_SIZE 16
48 uint8_t writeCache[512 * FATFS_BLOCK_CACHE_SIZE] __attribute__ ((aligned (4)));
49 uint32_t cacheCount = 0;
51 void cache_write(uint8_t *buffer)
53 if (cacheCount == sizeof(writeCache)) {
54 // Prevents overflow
55 return;
57 memcpy(&writeCache[cacheCount], buffer, 512);
58 cacheCount += 512;
61 uint16_t cache_getCount(void)
63 return (cacheCount / 512);
66 void cache_reset(void)
68 cacheCount = 0;
71 /**
72 * Returns true if the card has already been, or is currently, initializing and hasn't encountered enough errors to
73 * trip our error threshold and be disabled (i.e. our card is in and working!)
75 static bool sdcardSdio_isFunctional(void)
77 return sdcard.state != SDCARD_STATE_NOT_PRESENT;
80 /**
81 * Handle a failure of an SD card operation by resetting the card back to its initialization phase.
83 * Increments the failure counter, and when the failure threshold is reached, disables the card until
84 * the next call to sdcard_init().
86 static void sdcard_reset(void)
88 if (SD_Init() != 0) {
89 sdcard.failureCount++;
90 if (sdcard.failureCount >= SDCARD_MAX_CONSECUTIVE_FAILURES || !sdcard_isInserted()) {
91 sdcard.state = SDCARD_STATE_NOT_PRESENT;
92 } else {
93 sdcard.operationStartTime = millis();
94 sdcard.state = SDCARD_STATE_RESET;
99 typedef enum {
100 SDCARD_RECEIVE_SUCCESS,
101 SDCARD_RECEIVE_BLOCK_IN_PROGRESS,
102 SDCARD_RECEIVE_ERROR
103 } sdcardReceiveBlockStatus_e;
106 * Attempt to receive a data block from the SD card.
108 * Return true on success, otherwise the card has not responded yet and you should retry later.
110 static sdcardReceiveBlockStatus_e sdcard_receiveDataBlock(uint8_t *buffer, int count)
112 UNUSED(buffer);
113 UNUSED(count);
114 SD_Error_t ret = SD_CheckRead();
116 if (ret == SD_BUSY) {
117 return SDCARD_RECEIVE_BLOCK_IN_PROGRESS;
120 if (SD_GetState() != true) {
121 return SDCARD_RECEIVE_ERROR;
124 return SDCARD_RECEIVE_SUCCESS;
127 static bool sdcard_receiveCID(void)
129 SD_CardInfo_t *sdinfo = &SD_CardInfo;
130 SD_Error_t error = SD_GetCardInfo();
131 if (error) {
132 return false;
135 sdcard.metadata.manufacturerID = sdinfo->SD_cid.ManufacturerID;
136 sdcard.metadata.oemID = sdinfo->SD_cid.OEM_AppliID;
137 sdcard.metadata.productName[0] = (sdinfo->SD_cid.ProdName1 & 0xFF000000) >> 24;
138 sdcard.metadata.productName[1] = (sdinfo->SD_cid.ProdName1 & 0x00FF0000) >> 16;
139 sdcard.metadata.productName[2] = (sdinfo->SD_cid.ProdName1 & 0x0000FF00) >> 8;
140 sdcard.metadata.productName[3] = (sdinfo->SD_cid.ProdName1 & 0x000000FF) >> 0;
141 sdcard.metadata.productName[4] = sdinfo->SD_cid.ProdName2;
142 sdcard.metadata.productRevisionMajor = sdinfo->SD_cid.ProdRev >> 4;
143 sdcard.metadata.productRevisionMinor = sdinfo->SD_cid.ProdRev & 0x0F;
144 sdcard.metadata.productSerial = sdinfo->SD_cid.ProdSN;
145 sdcard.metadata.productionYear = (((sdinfo->SD_cid.ManufactDate & 0x0F00) >> 8) | ((sdinfo->SD_cid.ManufactDate & 0xFF) >> 4)) + 2000;
146 sdcard.metadata.productionMonth = sdinfo->SD_cid.ManufactDate & 0x000F;
148 return true;
151 static bool sdcard_fetchCSD(void)
153 /* The CSD command's data block should always arrive within 8 idle clock cycles (SD card spec). This is because
154 * the information about card latency is stored in the CSD register itself, so we can't use that yet!
156 SD_CardInfo_t *sdinfo = &SD_CardInfo;
157 SD_Error_t error;
158 error = SD_GetCardInfo();
159 if (error) {
160 return false;
163 sdcard.metadata.numBlocks = sdinfo->CardCapacity;
164 return true;
168 * Check if the SD Card has completed its startup sequence. Must be called with sdcard.state == SDCARD_STATE_INITIALIZATION.
170 * Returns true if the card has finished its init process.
172 static bool sdcard_checkInitDone(void)
174 if (SD_GetState()) {
175 SD_CardType_t *sdtype = &SD_CardType;
176 SD_Error_t errorState = SD_GetCardInfo();
177 if (errorState != SD_OK) {
178 return false;
181 sdcard.version = (*sdtype) ? 2 : 1;
182 sdcard.highCapacity = (*sdtype == 2) ? 1 : 0;
183 return true;
186 // When card init is complete, the idle bit in the response becomes zero.
187 return false;
191 * Begin the initialization process for the SD card. This must be called first before any other sdcard_ routine.
193 static void sdcardSdio_init(const sdcardConfig_t *config, const spiPinConfig_t *spiConfig)
195 UNUSED(spiConfig);
197 sdcard.enabled = config->mode;
198 if (!sdcard.enabled) {
199 sdcard.state = SDCARD_STATE_NOT_PRESENT;
200 return;
203 #ifdef USE_DMA_SPEC
204 #if !defined(STM32H7) // H7 uses IDMA
205 const dmaChannelSpec_t *dmaChannelSpec = dmaGetChannelSpecByPeripheral(DMA_PERIPH_SDIO, 0, sdioConfig()->dmaopt);
207 if (!dmaChannelSpec) {
208 sdcard.state = SDCARD_STATE_NOT_PRESENT;
209 return;
212 sdcard.dmaIdentifier = dmaGetIdentifier(dmaChannelSpec->ref);
214 if (sdcard.dmaIdentifier == 0) {
215 sdcard.state = SDCARD_STATE_NOT_PRESENT;
216 return;
218 #endif
219 #endif
220 if (sdioConfig()->useCache) {
221 sdcard.useCache = 1;
222 } else {
223 sdcard.useCache = 0;
225 #ifdef USE_DMA_SPEC
226 #if defined(STM32H7) // H7 uses IDMA
227 SD_Initialize_LL(0);
228 #else
229 SD_Initialize_LL((DMA_ARCH_TYPE *)dmaChannelSpec->ref);
230 #endif
231 #else
232 SD_Initialize_LL(SDCARD_SDIO_DMA_OPT);
233 #endif
235 if (sdcard_isInserted()) {
236 if (SD_Init() != 0) {
237 sdcard.state = SDCARD_STATE_NOT_PRESENT;
238 sdcard.failureCount++;
239 return;
241 } else {
242 sdcard.state = SDCARD_STATE_NOT_PRESENT;
243 sdcard.failureCount++;
244 return;
247 sdcard.operationStartTime = millis();
248 sdcard.state = SDCARD_STATE_RESET;
249 sdcard.failureCount = 0;
253 * Returns true if the card is ready to accept read/write commands.
255 static bool sdcard_isReady(void)
257 return sdcard.state == SDCARD_STATE_READY || sdcard.state == SDCARD_STATE_WRITING_MULTIPLE_BLOCKS;
261 * Send the stop-transmission token to complete a multi-block write.
263 * Returns:
264 * SDCARD_OPERATION_IN_PROGRESS - We're now waiting for that stop to complete, the card will enter
265 * the SDCARD_STATE_STOPPING_MULTIPLE_BLOCK_WRITE state.
266 * SDCARD_OPERATION_SUCCESS - The multi-block write finished immediately, the card will enter
267 * the SDCARD_READY state.
270 static sdcardOperationStatus_e sdcard_endWriteBlocks(void)
272 sdcard.multiWriteBlocksRemain = 0;
273 if (sdcard.useCache) {
274 cache_reset();
277 // 8 dummy clocks to guarantee N_WR clocks between the last card response and this token
279 // Card may choose to raise a busy (non-0xFF) signal after at most N_BR (1 byte) delay
280 if (SD_GetState()) {
281 sdcard.state = SDCARD_STATE_READY;
282 return SDCARD_OPERATION_SUCCESS;
283 } else {
284 sdcard.state = SDCARD_STATE_STOPPING_MULTIPLE_BLOCK_WRITE;
285 sdcard.operationStartTime = millis();
287 return SDCARD_OPERATION_IN_PROGRESS;
291 * Call periodically for the SD card to perform in-progress transfers.
293 * Returns true if the card is ready to accept commands.
295 static bool sdcardSdio_poll(void)
297 if (!sdcard.enabled) {
298 sdcard.state = SDCARD_STATE_NOT_PRESENT;
299 return false;
302 #ifdef SDCARD_PROFILING
303 bool profilingComplete;
304 #endif
306 doMore:
307 switch (sdcard.state) {
308 case SDCARD_STATE_RESET:
309 //HAL Takes care of voltage crap.
310 sdcard.state = SDCARD_STATE_CARD_INIT_IN_PROGRESS;
311 goto doMore;
312 break;
314 case SDCARD_STATE_CARD_INIT_IN_PROGRESS:
315 if (sdcard_checkInitDone()) {
316 // Now fetch the CSD and CID registers
317 if (sdcard_fetchCSD()) {
318 sdcard.state = SDCARD_STATE_INITIALIZATION_RECEIVE_CID;
319 goto doMore;
320 } else {
321 sdcard_reset();
322 goto doMore;
325 break;
326 case SDCARD_STATE_INITIALIZATION_RECEIVE_CID:
327 if (sdcard_receiveCID()) {
329 /* The spec is a little iffy on what the default block size is for Standard Size cards (it can be changed on
330 * standard size cards) so let's just set it to 512 explicitly so we don't have a problem.
332 // if (!sdcard.highCapacity && SDMMC_CmdBlockLength(_HSD.Instance, SDCARD_BLOCK_SIZE)) {
333 // sdcard_reset();
334 // goto doMore;
335 // }
337 sdcard.multiWriteBlocksRemain = 0;
339 sdcard.state = SDCARD_STATE_READY;
340 goto doMore;
341 } // else keep waiting for the CID to arrive
342 break;
343 case SDCARD_STATE_SENDING_WRITE:
344 // Have we finished sending the write yet?
345 if (SD_CheckWrite() == SD_OK) {
347 // The SD card is now busy committing that write to the card
348 sdcard.state = SDCARD_STATE_WAITING_FOR_WRITE;
349 sdcard.operationStartTime = millis();
351 // Since we've transmitted the buffer we can go ahead and tell the caller their operation is complete
352 if (sdcard.pendingOperation.callback) {
353 sdcard.pendingOperation.callback(SDCARD_BLOCK_OPERATION_WRITE, sdcard.pendingOperation.blockIndex, sdcard.pendingOperation.buffer, sdcard.pendingOperation.callbackData);
356 break;
357 case SDCARD_STATE_WAITING_FOR_WRITE:
358 if (SD_GetState()) {
359 #ifdef SDCARD_PROFILING
360 profilingComplete = true;
361 #endif
363 sdcard.failureCount = 0; // Assume the card is good if it can complete a write
365 // Still more blocks left to write in a multi-block chain?
366 if (sdcard.multiWriteBlocksRemain > 1) {
367 sdcard.multiWriteBlocksRemain--;
368 sdcard.multiWriteNextBlock++;
369 if (sdcard.useCache) {
370 cache_reset();
372 sdcard.state = SDCARD_STATE_WRITING_MULTIPLE_BLOCKS;
373 } else if (sdcard.multiWriteBlocksRemain == 1) {
374 // This function changes the sd card state for us whether immediately succesful or delayed:
375 sdcard_endWriteBlocks();
376 } else {
377 sdcard.state = SDCARD_STATE_READY;
380 #ifdef SDCARD_PROFILING
381 if (profilingComplete && sdcard.profiler) {
382 sdcard.profiler(SDCARD_BLOCK_OPERATION_WRITE, sdcard.pendingOperation.blockIndex, micros() - sdcard.pendingOperation.profileStartTime);
384 #endif
385 } else if (millis() > sdcard.operationStartTime + SDCARD_TIMEOUT_WRITE_MSEC) {
387 * The caller has already been told that their write has completed, so they will have discarded
388 * their buffer and have no hope of retrying the operation. But this should be very rare and it allows
389 * them to reuse their buffer milliseconds faster than they otherwise would.
391 sdcard_reset();
392 goto doMore;
394 break;
395 case SDCARD_STATE_READING:
396 switch (sdcard_receiveDataBlock(sdcard.pendingOperation.buffer, SDCARD_BLOCK_SIZE)) {
397 case SDCARD_RECEIVE_SUCCESS:
399 sdcard.state = SDCARD_STATE_READY;
400 sdcard.failureCount = 0; // Assume the card is good if it can complete a read
402 #ifdef SDCARD_PROFILING
403 if (sdcard.profiler) {
404 sdcard.profiler(SDCARD_BLOCK_OPERATION_READ, sdcard.pendingOperation.blockIndex, micros() - sdcard.pendingOperation.profileStartTime);
406 #endif
408 if (sdcard.pendingOperation.callback) {
409 sdcard.pendingOperation.callback(
410 SDCARD_BLOCK_OPERATION_READ,
411 sdcard.pendingOperation.blockIndex,
412 sdcard.pendingOperation.buffer,
413 sdcard.pendingOperation.callbackData
416 break;
417 case SDCARD_RECEIVE_BLOCK_IN_PROGRESS:
418 if (millis() <= sdcard.operationStartTime + SDCARD_TIMEOUT_READ_MSEC) {
419 break; // Timeout not reached yet so keep waiting
421 // Timeout has expired, so fall through to convert to a fatal error
423 case SDCARD_RECEIVE_ERROR:
424 goto doMore;
425 break;
427 break;
428 case SDCARD_STATE_STOPPING_MULTIPLE_BLOCK_WRITE:
429 if (SD_GetState()) {
430 sdcard.state = SDCARD_STATE_READY;
432 #ifdef SDCARD_PROFILING
433 if (sdcard.profiler) {
434 sdcard.profiler(SDCARD_BLOCK_OPERATION_WRITE, sdcard.pendingOperation.blockIndex, micros() - sdcard.pendingOperation.profileStartTime);
436 #endif
437 } else if (millis() > sdcard.operationStartTime + SDCARD_TIMEOUT_WRITE_MSEC) {
438 sdcard_reset();
439 goto doMore;
441 break;
442 case SDCARD_STATE_NOT_PRESENT:
443 default:
447 // Is the card's initialization taking too long?
448 if (sdcard.state >= SDCARD_STATE_RESET && sdcard.state < SDCARD_STATE_READY
449 && millis() - sdcard.operationStartTime > SDCARD_TIMEOUT_INIT_MILLIS) {
450 sdcard_reset();
453 return sdcard_isReady();
457 * Write the 512-byte block from the given buffer into the block with the given index.
459 * If the write does not complete immediately, your callback will be called later. If the write was successful, the
460 * buffer pointer will be the same buffer you originally passed in, otherwise the buffer will be set to NULL.
462 * Returns:
463 * SDCARD_OPERATION_IN_PROGRESS - Your buffer is currently being transmitted to the card and your callback will be
464 * called later to report the completion. The buffer pointer must remain valid until
465 * that time.
466 * SDCARD_OPERATION_SUCCESS - Your buffer has been transmitted to the card now.
467 * SDCARD_OPERATION_BUSY - The card is already busy and cannot accept your write
468 * SDCARD_OPERATION_FAILURE - Your write was rejected by the card, card will be reset
470 static sdcardOperationStatus_e sdcardSdio_writeBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData)
473 #ifdef SDCARD_PROFILING
474 sdcard.pendingOperation.profileStartTime = micros();
475 #endif
477 doMore:
478 switch (sdcard.state) {
479 case SDCARD_STATE_WRITING_MULTIPLE_BLOCKS:
480 // Do we need to cancel the previous multi-block write?
481 if (blockIndex != sdcard.multiWriteNextBlock) {
482 if (sdcard_endWriteBlocks() == SDCARD_OPERATION_SUCCESS) {
483 // Now we've entered the ready state, we can try again
484 goto doMore;
485 } else {
486 return SDCARD_OPERATION_BUSY;
490 // We're continuing a multi-block write
491 break;
492 case SDCARD_STATE_READY:
493 break;
494 default:
495 return SDCARD_OPERATION_BUSY;
498 sdcard.pendingOperation.buffer = buffer;
499 sdcard.pendingOperation.blockIndex = blockIndex;
501 uint16_t block_count = 1;
502 if ((cache_getCount() < FATFS_BLOCK_CACHE_SIZE) &&
503 (sdcard.multiWriteBlocksRemain != 0) && sdcard.useCache) {
504 cache_write(buffer);
505 if (cache_getCount() == FATFS_BLOCK_CACHE_SIZE || sdcard.multiWriteBlocksRemain == 1) {
506 //Relocate buffer
507 buffer = (uint8_t*)writeCache;
508 //Recalculate block index
509 blockIndex -= cache_getCount() - 1;
510 block_count = cache_getCount();
511 } else {
512 sdcard.multiWriteBlocksRemain--;
513 sdcard.multiWriteNextBlock++;
514 sdcard.state = SDCARD_STATE_READY;
515 return SDCARD_OPERATION_SUCCESS;
519 sdcard.pendingOperation.callback = callback;
520 sdcard.pendingOperation.callbackData = callbackData;
521 sdcard.pendingOperation.chunkIndex = 1; // (for non-DMA transfers) we've sent chunk #0 already
522 sdcard.state = SDCARD_STATE_SENDING_WRITE;
524 if (SD_WriteBlocks_DMA(blockIndex, (uint32_t*) buffer, 512, block_count) != SD_OK) {
525 /* Our write was rejected! This could be due to a bad address but we hope not to attempt that, so assume
526 * the card is broken and needs reset.
528 sdcard_reset();
530 // Announce write failure:
531 if (sdcard.pendingOperation.callback) {
532 sdcard.pendingOperation.callback(SDCARD_BLOCK_OPERATION_WRITE, sdcard.pendingOperation.blockIndex, NULL, sdcard.pendingOperation.callbackData);
534 return SDCARD_OPERATION_FAILURE;
537 return SDCARD_OPERATION_IN_PROGRESS;
541 * Begin writing a series of consecutive blocks beginning at the given block index. This will allow (but not require)
542 * the SD card to pre-erase the number of blocks you specifiy, which can allow the writes to complete faster.
544 * Afterwards, just call sdcard_writeBlock() as normal to write those blocks consecutively.
546 * It's okay to abort the multi-block write at any time by writing to a non-consecutive address, or by performing a read.
548 * Returns:
549 * SDCARD_OPERATION_SUCCESS - Multi-block write has been queued
550 * SDCARD_OPERATION_BUSY - The card is already busy and cannot accept your write
551 * SDCARD_OPERATION_FAILURE - A fatal error occured, card will be reset
553 static sdcardOperationStatus_e sdcardSdio_beginWriteBlocks(uint32_t blockIndex, uint32_t blockCount)
555 if (sdcard.state != SDCARD_STATE_READY) {
556 if (sdcard.state == SDCARD_STATE_WRITING_MULTIPLE_BLOCKS) {
557 if (blockIndex == sdcard.multiWriteNextBlock) {
558 // Assume that the caller wants to continue the multi-block write they already have in progress!
559 return SDCARD_OPERATION_SUCCESS;
560 } else if (sdcard_endWriteBlocks() != SDCARD_OPERATION_SUCCESS) {
561 return SDCARD_OPERATION_BUSY;
562 } // Else we've completed the previous multi-block write and can fall through to start the new one
563 } else {
564 return SDCARD_OPERATION_BUSY;
568 sdcard.state = SDCARD_STATE_WRITING_MULTIPLE_BLOCKS;
569 sdcard.multiWriteBlocksRemain = blockCount;
570 sdcard.multiWriteNextBlock = blockIndex;
571 return SDCARD_OPERATION_SUCCESS;
575 * Read the 512-byte block with the given index into the given 512-byte buffer.
577 * When the read completes, your callback will be called. If the read was successful, the buffer pointer will be the
578 * same buffer you originally passed in, otherwise the buffer will be set to NULL.
580 * You must keep the pointer to the buffer valid until the operation completes!
582 * Returns:
583 * true - The operation was successfully queued for later completion, your callback will be called later
584 * false - The operation could not be started due to the card being busy (try again later).
586 static bool sdcardSdio_readBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData)
588 if (sdcard.state != SDCARD_STATE_READY) {
589 if (sdcard.state == SDCARD_STATE_WRITING_MULTIPLE_BLOCKS) {
590 if (sdcard_endWriteBlocks() != SDCARD_OPERATION_SUCCESS) {
591 return false;
593 } else {
594 return false;
598 #ifdef SDCARD_PROFILING
599 sdcard.pendingOperation.profileStartTime = micros();
600 #endif
602 // Standard size cards use byte addressing, high capacity cards use block addressing
603 uint8_t status = SD_ReadBlocks_DMA(blockIndex, (uint32_t*) buffer, 512, 1);
605 if (status == SD_OK) {
606 sdcard.pendingOperation.buffer = buffer;
607 sdcard.pendingOperation.blockIndex = blockIndex;
608 sdcard.pendingOperation.callback = callback;
609 sdcard.pendingOperation.callbackData = callbackData;
611 sdcard.state = SDCARD_STATE_READING;
613 sdcard.operationStartTime = millis();
615 return true;
616 } else {
617 sdcard_reset();
618 if (sdcard.pendingOperation.callback) {
619 sdcard.pendingOperation.callback(
620 SDCARD_BLOCK_OPERATION_READ,
621 sdcard.pendingOperation.blockIndex,
622 NULL,
623 sdcard.pendingOperation.callbackData
626 return false;
631 * Returns true if the SD card has successfully completed its startup procedures.
633 static bool sdcardSdio_isInitialized(void)
635 return sdcard.state >= SDCARD_STATE_READY;
638 static const sdcardMetadata_t* sdcardSdio_getMetadata(void)
640 return &sdcard.metadata;
643 #ifdef SDCARD_PROFILING
645 static void sdcardSdio_setProfilerCallback(sdcard_profilerCallback_c callback)
647 sdcard.profiler = callback;
650 #endif
652 sdcardVTable_t sdcardSdioVTable = {
653 NULL,
654 sdcardSdio_init,
655 sdcardSdio_readBlock,
656 sdcardSdio_beginWriteBlocks,
657 sdcardSdio_writeBlock,
658 sdcardSdio_poll,
659 sdcardSdio_isFunctional,
660 sdcardSdio_isInitialized,
661 sdcardSdio_getMetadata,
662 #ifdef SDCARD_PROFILING
663 sdcardSdio_setProfilerCallback,
664 #endif
666 #endif