diff options
Diffstat (limited to 'src/drivers')
| -rw-r--r-- | src/drivers/ao_sdcard.c | 361 | ||||
| -rw-r--r-- | 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) { @@ -338,17 +388,84 @@ bail:  }  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, | 
