From b34370cea662eb245e43aca20a6650b84b55ef6f Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Mon, 1 Apr 2013 02:08:18 -0700 Subject: [PATCH] altos: Retry SD card I/O. Use time for timeouts instead of counts Sometimes I/O operations may fail; give the card a chance and retry the operation in case it works the next time. Replace the loop counts with loops that check the clock so that they'll have consistent timeouts even if the CPU or SPI speed changes. Signed-off-by: Keith Packard --- src/drivers/ao_sdcard.c | 361 ++++++++++++++++++++++++++++++---------- src/drivers/ao_sdcard.h | 19 ++- 2 files changed, 281 insertions(+), 99 deletions(-) diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c index 952000a7..6073677a 100644 --- a/src/drivers/ao_sdcard.c +++ b/src/drivers/ao_sdcard.c @@ -28,11 +28,14 @@ #define ao_sdcard_deselect() ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1) /* Include SD card commands */ -#define SDCARD_DEBUG 0 +#define SDCARD_DEBUG 1 /* Spew SD tracing */ #define SDCARD_TRACE 0 +/* Emit error and warning messages */ +#define SDCARD_WARN 0 + static uint8_t initialized; static uint8_t present; static uint8_t mutex; @@ -47,6 +50,38 @@ static enum ao_sdtype sdtype; #define DBG(...) #endif +#if SDCARD_WARN +#define WARN(...) printf(__VA_ARGS__) +#else +#define WARN(...) +#endif + +#define later(x,y) ((int16_t) ((x) - (y)) >= 0) + +/* + * Wait while the card is busy. The card will return a stream of 0xff + * when it is ready to accept a command + */ + +static uint8_t +ao_sdcard_wait_busy(void) +{ + uint16_t timeout = ao_time() + SDCARD_BUSY_TIMEOUT; + uint8_t reply; + for (;;) { + ao_sdcard_recv(&reply, 1); + DBG("\t\twait busy %02x\n", reply); + if (reply == 0xff) + break; + if (later(ao_time(), timeout)) { + WARN("wait busy timeout\n"); + return 0; + } + } + return 1; +} + + /* * Send an SD command and await the status reply */ @@ -57,15 +92,13 @@ ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) uint8_t data[6]; uint8_t reply; int i; + uint16_t timeout; DBG ("\tsend_cmd %d arg %08x\n", cmd, arg); + + /* Wait for the card to not be busy */ if (cmd != SDCARD_GO_IDLE_STATE) { - for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) { - ao_sdcard_recv(&reply, 1); - if (reply == 0xff) - break; - } - if (i == SDCARD_CMD_TIMEOUT) + if (!ao_sdcard_wait_busy()) return SDCARD_STATUS_TIMEOUT; } @@ -85,13 +118,22 @@ ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) /* The first reply byte will be the status, * which must have the high bit clear */ - for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) { + timeout = ao_time() + SDCARD_CMD_TIMEOUT; + for (;;) { ao_sdcard_recv(&reply, 1); DBG ("\t\tgot byte %02x\n", reply); if ((reply & 0x80) == 0) - return reply; + break; + if (later(ao_time(), timeout)) { + WARN("send_cmd %02x timeout\n", cmd); + return SDCARD_STATUS_TIMEOUT; + } } - return SDCARD_STATUS_TIMEOUT; +#if SDCARD_WARN + if (reply != SDCARD_STATUS_READY_STATE && reply != SDCARD_STATUS_IDLE_STATE) + WARN("send_cmd %d failed %02x\n", cmd, reply); +#endif + return reply; } /* @@ -109,21 +151,8 @@ ao_sdcard_recv_reply(uint8_t *reply, int len) } /* - * Wait while the card is busy. The - * card will return a stream of 0xff - * until it isn't busy anymore + * Switch to 'idle' state. This is used to get the card into SPI mode */ -static void -ao_sdcard_wait_busy(void) -{ - uint8_t v; - - do { - ao_sdcard_recv(&v, 1); - } while (v != 0xff); - ao_sdcard_send_fixed(0xff, 1); -} - static uint8_t ao_sdcard_go_idle_state(void) { @@ -175,20 +204,33 @@ ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4]) return ret; } -static uint8_t -ao_sdcard_send_status(void) +/* + * _ao_sdcard_send_status + * + * Get the 2-byte status value. + * + * Called from other functions with CS held low already, + * hence prefixing the name with '_' + */ +static uint16_t +_ao_sdcard_send_status(void) { uint8_t ret; + uint8_t extra; DBG ("send_status\n"); - ao_sdcard_select(); ret = ao_sdcard_send_cmd(SDCARD_SEND_STATUS, 0); - ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_recv_reply(&extra, 1); if (ret != SDCARD_STATUS_READY_STATE) DBG ("\tsend_if_cond failed %02x\n", ret); - return ret; + return ret | (extra << 8); } +/* + * ao_sdcard_set_blocklen + * + * Set the block length for future read and write commands + */ static uint8_t ao_sdcard_set_blocklen(uint32_t blocklen) { @@ -198,21 +240,28 @@ ao_sdcard_set_blocklen(uint32_t blocklen) ao_sdcard_select(); ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen); ao_sdcard_recv_reply(NULL, 0); + ao_sdcard_deselect(); if (ret != SDCARD_STATUS_READY_STATE) DBG ("\tsend_if_cond failed %02x\n", ret); return ret; } +/* + * _ao_sdcard_app_cmd + * + * Send the app command prefix + * + * Called with the CS held low, hence + * the '_' prefix + */ static uint8_t -ao_sdcard_app_cmd(void) +_ao_sdcard_app_cmd(void) { uint8_t ret; DBG ("app_cmd\n"); - ao_sdcard_select(); ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0); ao_sdcard_recv_reply(NULL, 0); - ao_sdcard_deselect(); DBG ("\tapp_cmd status %02x\n"); return ret; } @@ -222,13 +271,14 @@ ao_sdcard_app_send_op_cond(uint32_t arg) { uint8_t ret; - ret = ao_sdcard_app_cmd(); - if (ret != SDCARD_STATUS_IDLE_STATE) - return ret; DBG("send_op_comd\n"); ao_sdcard_select(); + ret = _ao_sdcard_app_cmd(); + if (ret != SDCARD_STATUS_IDLE_STATE) + goto bail; ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg); ao_sdcard_recv_reply(NULL, 0); +bail: ao_sdcard_deselect(); DBG ("\tapp_send_op_cond status %02x\n", ret); return ret; @@ -254,6 +304,10 @@ ao_sdcard_read_ocr(uint8_t read_ocr_response[4]) return ret; } +/* + * Follow the flow-chart defined by the SD group to + * initialize the card and figure out what kind it is + */ static void ao_sdcard_setup(void) { @@ -269,10 +323,7 @@ ao_sdcard_setup(void) */ ao_sdcard_send_fixed(0xff, 10); - ao_delay(AO_MS_TO_TICKS(10)); - /* Reset the card and get it into SPI mode */ - for (i = 0; i < SDCARD_IDLE_WAIT; i++) { if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) break; @@ -281,7 +332,6 @@ ao_sdcard_setup(void) goto bail; /* Figure out what kind of card we have */ - sdtype = ao_sdtype_unknown; if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { @@ -337,18 +387,85 @@ bail: ao_sdcard_put(); } +static uint8_t +_ao_sdcard_reset(void) +{ + int i; + uint8_t ret; + uint8_t response[10]; + + for (i = 0; i < SDCARD_IDLE_WAIT; i++) { + if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE) + break; + } + if (i == SDCARD_IDLE_WAIT) { + ret = 0x3f; + goto bail; + } + + /* Follow the setup path to get the card out of idle state and + * up and running again + */ + if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) { + uint32_t arg = 0; + uint8_t sdver2 = 0; + + /* Check for SD version 2 */ + if ((response[2] & 0xf) == 1 && response[3] == 0xaa) { + arg = 0x40000000; + sdver2 = 1; + } + + for (i = 0; i < SDCARD_IDLE_WAIT; i++) { + ret = ao_sdcard_app_send_op_cond(arg); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + + if (ret != SDCARD_STATUS_READY_STATE) { + /* MMC */ + for (i = 0; i < SDCARD_IDLE_WAIT; i++) { + ret = ao_sdcard_send_op_cond(); + if (ret != SDCARD_STATUS_IDLE_STATE) + break; + } + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + } + + /* For everything but SDHC cards, set the block length */ + if (sdtype != ao_sdtype_sd2block) { + ret = ao_sdcard_set_blocklen(512); + if (ret != SDCARD_STATUS_READY_STATE) + DBG ("set_blocklen failed, ignoring\n"); + } + } +bail: + return ret; +} + +/* + * The card will send 0xff until it is ready to send + * the data block at which point it will send the START_BLOCK + * marker followed by the data. This function waits while + * the card is sending 0xff + */ static uint8_t ao_sdcard_wait_block_start(void) { - int i; - uint8_t v; + uint8_t v; + uint16_t timeout = ao_time() + SDCARD_BLOCK_TIMEOUT; DBG ("\twait_block_start\n"); - for (i = 0; i < SDCARD_BLOCK_TIMEOUT; i++) { + for (;;) { ao_sdcard_recv(&v, 1); DBG("\t\trecv %02x\n", v); if (v != 0xff) break; + if (later(ao_time(), timeout)) { + printf ("wait block start timeout\n"); + return 0xff; + } } return v; } @@ -360,7 +477,9 @@ uint8_t ao_sdcard_read_block(uint32_t block, uint8_t *data) { uint8_t ret; + uint8_t start_block; uint8_t crc[2]; + int tries; ao_sdcard_lock(); if (!initialized) { @@ -376,25 +495,53 @@ ao_sdcard_read_block(uint32_t block, uint8_t *data) DBG("read block %d\n", block); if (sdtype != ao_sdtype_sd2block) block <<= 9; + ao_sdcard_get(); - ao_sdcard_select(); - ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block); - ao_sdcard_recv_reply(NULL, 0); - if (ret != SDCARD_STATUS_READY_STATE) - goto bail; + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); - /* Wait for the data start block marker */ - if (ao_sdcard_wait_block_start() != SDCARD_DATA_START_BLOCK) { - ret = 0x3f; - goto bail; - } + ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) { + uint16_t status; + WARN ("read block command failed %d status %02x\n", block, ret); + status = _ao_sdcard_send_status(); + WARN ("\tstatus now %04x\n", status); + goto bail; + } - ao_sdcard_recv(data, 512); - ao_sdcard_recv(crc, 2); -bail: - ao_sdcard_deselect(); + ao_sdcard_send_fixed(0xff, 1); + + /* Wait for the data start block marker */ + start_block = ao_sdcard_wait_block_start(); + if (start_block != SDCARD_DATA_START_BLOCK) { + WARN ("wait block start failed %02x\n", start_block); + ret = 0x3f; + goto bail; + } + + ao_sdcard_recv(data, 512); + ao_sdcard_recv(crc, 2); + bail: + ao_sdcard_deselect(); + if (ret == SDCARD_STATUS_READY_STATE) + break; + if (ret == SDCARD_STATUS_IDLE_STATE) { + ret = _ao_sdcard_reset(); + if (ret != SDCARD_STATUS_READY_STATE) + break; + } + } ao_sdcard_put(); ao_sdcard_unlock(); + +#if SDCARD_WARN + if (ret != SDCARD_STATUS_READY_STATE) + WARN("read failed\n"); + else if (tries) + WARN("took %d tries to read %d\n", tries + 1, block); +#endif + DBG("read %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); return ret == SDCARD_STATUS_READY_STATE; } @@ -406,9 +553,12 @@ uint8_t ao_sdcard_write_block(uint32_t block, uint8_t *data) { uint8_t ret; - uint8_t response; - uint8_t start_block[2]; + uint8_t response[1]; + uint8_t start_block[8]; + uint16_t status; + static uint8_t check_data[512]; int i; + int tries; ao_sdcard_lock(); if (!initialized) { @@ -424,45 +574,64 @@ ao_sdcard_write_block(uint32_t block, uint8_t *data) DBG("write block %d\n", block); if (sdtype != ao_sdtype_sd2block) block <<= 9; - ao_sdcard_get(); - ao_sdcard_select(); - ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block); - ao_sdcard_recv_reply(NULL, 0); - if (ret != SDCARD_STATUS_READY_STATE) - goto bail; - - /* Write a pad byte followed by the data start block marker */ - start_block[0] = 0xff; - start_block[1] = SDCARD_DATA_START_BLOCK; - ao_sdcard_send(start_block, 2); - - /* Send the data */ - ao_sdcard_send(data, 512); - - /* Fake the CRC */ - ao_sdcard_send_fixed(0xff, 2); + ao_sdcard_get(); - /* See if the card liked the data */ - ao_sdcard_recv(&response, 1); - if ((response & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) { - ret = 0x3f; - goto bail; - } + for (tries = 0; tries < 10; tries++) { + ao_sdcard_select(); + + ret = ao_sdcard_send_cmd(SDCARD_WRITE_BLOCK, block); + ao_sdcard_recv_reply(NULL, 0); + if (ret != SDCARD_STATUS_READY_STATE) + goto bail; + + /* Write a pad byte followed by the data start block marker */ + start_block[0] = 0xff; + start_block[1] = SDCARD_DATA_START_BLOCK; + ao_sdcard_send(start_block, 2); + + /* Send the data */ + ao_sdcard_send(data, 512); + + /* Fake the CRC */ + ao_sdcard_send_fixed(0xff, 2); + + /* See if the card liked the data */ + ao_sdcard_recv(response, sizeof (response)); + if ((response[0] & SDCARD_DATA_RES_MASK) != SDCARD_DATA_RES_ACCEPTED) { + int i; + WARN("Data not accepted, response"); + for (i = 0; i < sizeof (response); i++) + WARN(" %02x", response[i]); + WARN("\n"); + ret = 0x3f; + goto bail; + } - /* Wait for the bus to go idle (should be done with an interrupt) */ - for (i = 0; i < SDCARD_IDLE_TIMEOUT; i++) { - ao_sdcard_recv(&response, 1); - if (response == 0xff) + /* Wait for the bus to go idle (should be done with an interrupt?) */ + if (!ao_sdcard_wait_busy()) { + ret = 0x3f; + goto bail; + } + + /* Check the current status after the write completes */ + status = _ao_sdcard_send_status(); + if ((status & 0xff) != SDCARD_STATUS_READY_STATE) { + WARN ("send status after write %04x\n", status); + ret = status & 0xff; + goto bail; + } + bail: + ao_sdcard_deselect(); + DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + if (ret == SDCARD_STATUS_READY_STATE) break; } - if (i == SDCARD_IDLE_TIMEOUT) - ret = 0x3f; -bail: - ao_sdcard_deselect(); ao_sdcard_put(); ao_sdcard_unlock(); - DBG("write %s\n", ret == SDCARD_STATUS_READY_STATE ? "success" : "failure"); + if (tries) + WARN("took %d tries to write %d\n", tries + 1, block); + return ret == SDCARD_STATUS_READY_STATE; } @@ -473,9 +642,17 @@ static void ao_sdcard_test_read(void) { int i; - if (!ao_sdcard_read_block(1, test_data)) { - printf ("read error\n"); + + ao_cmd_decimal(); + if (ao_cmd_status != ao_cmd_success) return; + + for (i = 0; i < 100; i++) { + printf ("."); flush(); + if (!ao_sdcard_read_block(ao_cmd_lex_u32+i, test_data)) { + printf ("read error %d\n", i); + return; + } } printf ("data:"); for (i = 0; i < 18; i++) diff --git a/src/drivers/ao_sdcard.h b/src/drivers/ao_sdcard.h index 512439b4..e55a3dec 100644 --- a/src/drivers/ao_sdcard.h +++ b/src/drivers/ao_sdcard.h @@ -49,9 +49,14 @@ ao_sdcard_init(void); #define SDCARD_APP_SEND_OP_COMD 41 /* Status */ -#define SDCARD_STATUS_READY_STATE 0 -#define SDCARD_STATUS_IDLE_STATE 1 -#define SDCARD_STATUS_ILLEGAL_COMMAND 4 +#define SDCARD_STATUS_READY_STATE 0x00 +#define SDCARD_STATUS_IDLE_STATE 0x01 +#define SDCARD_STATUS_ERASE_RESET 0x02 +#define SDCARD_STATUS_ILLEGAL_COMMAND 0x04 +#define SDCARD_STATUS_COM_CRC_ERROR 0x08 +#define SDCARD_STATUS_ERASE_SEQ_ERROR 0x10 +#define SDCARD_STATUS_ADDRESS_ERROR 0x20 +#define SDCARD_STATUS_PARAMETER_ERROR 0x40 #define SDCARD_STATUS_TIMEOUT 0xff #define SDCARD_DATA_START_BLOCK 0xfe @@ -60,10 +65,10 @@ ao_sdcard_init(void); #define SDCARD_DATA_RES_MASK 0x1f #define SDCARD_DATA_RES_ACCEPTED 0x05 -#define SDCARD_CMD_TIMEOUT 100 -#define SDCARD_IDLE_WAIT 1000 -#define SDCARD_BLOCK_TIMEOUT 100 -#define SDCARD_IDLE_TIMEOUT 1000 +#define SDCARD_CMD_TIMEOUT AO_MS_TO_TICKS(100) +#define SDCARD_BUSY_TIMEOUT AO_MS_TO_TICKS(100) +#define SDCARD_IDLE_WAIT 10000 +#define SDCARD_BLOCK_TIMEOUT AO_MS_TO_TICKS(1000) enum ao_sdtype { ao_sdtype_unknown, -- 2.30.2