diff options
Diffstat (limited to 'src/drivers/ao_sdcard.c')
| -rw-r--r-- | src/drivers/ao_sdcard.c | 398 | 
1 files changed, 398 insertions, 0 deletions
diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c new file mode 100644 index 00000000..2174af1e --- /dev/null +++ b/src/drivers/ao_sdcard.c @@ -0,0 +1,398 @@ +/* + * Copyright © 2013 Keith Packard <keithp@keithp.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#include "ao.h" +#include "ao_sdcard.h" + +#define ao_sdcard_get_slow()		ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz) +#define ao_sdcard_get()			ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST) +#define ao_sdcard_put()			ao_spi_put(AO_SDCARD_SPI_BUS) +#define ao_sdcard_send_fixed(d,l)	ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_send(d,l)		ao_spi_send((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_recv(d,l)		ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS) +#define ao_sdcard_select()		ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0) +#define ao_sdcard_deselect()		ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1) + + +static uint8_t	initialized; +static uint8_t	present; +static uint8_t	mutex; +static enum ao_sdtype sdtype; + +#define ao_sdcard_lock()	ao_mutex_get(&mutex) +#define ao_sdcard_unlock()	ao_mutex_put(&mutex) + +#if 0 +#define DBG(...) printf(__VA_ARGS__) +#else +#define DBG(...) +#endif + +/* + * Send an SD command and await the status reply + */ + +static uint8_t +ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg) +{ +	uint8_t	data[6]; +	uint8_t	reply; +	int i; + +	DBG ("\tsend_cmd %d arg %08x\n", cmd, arg); +	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) +			return SDCARD_STATUS_TIMEOUT; +	} +	 +	data[0] = cmd & 0x3f | 0x40; +	data[1] = arg >> 24; +	data[2] = arg >> 16; +	data[3] = arg >> 8; +	data[4] = arg; +	if (cmd == SDCARD_GO_IDLE_STATE) +		data[5] = 0x95;	/* Valid for 0 arg */ +	else if (cmd == SDCARD_SEND_IF_COND) +		data[5] = 0x87;	/* Valid for 0x1aa arg */ +	else +		data[5] = 0xff;	/* no CRC */ +	ao_sdcard_send(data, 6); + +	/* The first reply byte will be the status, +	 * which must have the high bit clear +	 */ +	for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) { +		ao_sdcard_recv(&reply, 1); +		DBG ("\t\tgot byte %02x\n", reply); +		if ((reply & 0x80) == 0) +			return reply; +	} +	return SDCARD_STATUS_TIMEOUT; +} + +/* + * Retrieve any reply, discarding the trailing CRC byte + */ +static void +ao_sdcard_recv_reply(uint8_t *reply, int len) +{ +	uint8_t	discard; + +	if (len) +		ao_sdcard_recv(reply, len); +	/* trailing byte */ +	ao_sdcard_recv(&discard, 1); +} + +/* + * Wait while the card is busy. The + * card will return a stream of 0xff + * until it isn't busy anymore + */ +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) +{ +	uint8_t	ret; + +	DBG ("go_idle_state\n"); +	ao_sdcard_select(); +	ret = ao_sdcard_send_cmd(SDCARD_GO_IDLE_STATE, 0); +	ao_sdcard_recv_reply(NULL, 0); +	ao_sdcard_deselect(); +	DBG ("\tgo_idle_state status %02x\n", ret); +	return ret; +} + +static uint8_t +ao_sdcard_send_op_cond(void) +{ +	uint8_t	ret; + +	DBG ("send_op_cond\n"); +	ao_sdcard_select(); +	ret = ao_sdcard_send_cmd(SDCARD_SEND_OP_COND, 0); +	ao_sdcard_recv_reply(NULL, 0); +	ao_sdcard_deselect(); +	DBG ("\tsend_op_cond %02x\n", ret); +	return ret; +} + +static uint8_t +ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4]) +{ +	uint8_t ret; + +	DBG ("send_if_cond\n"); +	ao_sdcard_select(); +	ret = ao_sdcard_send_cmd(SDCARD_SEND_IF_COND, arg); +	if (ret != SDCARD_STATUS_IDLE_STATE) { +		DBG ("\tsend_if_cond failed %02x\n", ret); +		return ret; +	} +	ao_sdcard_recv_reply(send_if_cond_response, 4); +	DBG ("send_if_cond status %02x response %02x %02x %02x %02x\n", +		ret, +		send_if_cond_response[0], +		send_if_cond_response[1], +		send_if_cond_response[2], +		send_if_cond_response[3]); +	ao_sdcard_deselect(); +	return ret; +} + +static uint8_t +ao_sdcard_set_blocklen(uint32_t blocklen) +{ +	uint8_t ret; + +	DBG ("set_blocklen %d\n", blocklen); +	ao_sdcard_select(); +	ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen); +	ao_sdcard_recv_reply(NULL, 0); +	if (ret != SDCARD_STATUS_READY_STATE) +		DBG ("\tsend_if_cond failed %02x\n", ret); +	return ret; +	 +} + +static uint8_t +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; +} + +static uint8_t +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_send_cmd(SDCARD_APP_SEND_OP_COMD, arg); +	ao_sdcard_recv_reply(NULL, 0); +	ao_sdcard_deselect(); +	DBG ("\tapp_send_op_cond status %02x\n", ret); +	return ret; +} + +static uint8_t +ao_sdcard_read_ocr(uint8_t read_ocr_response[4]) +{ +	uint8_t	ret; + +	DBG ("read_ocr\n"); +	ao_sdcard_select(); +	ret = ao_sdcard_send_cmd(SDCARD_READ_OCR, 0); +	if (ret != SDCARD_STATUS_READY_STATE) +		DBG ("\tread_ocr failed %02x\n", ret); +	else { +		ao_sdcard_recv_reply(read_ocr_response, 4); +		DBG ("\tread_ocr status %02x response %02x %02x %02x %02x\n", ret, +			read_ocr_response[0], read_ocr_response[1], +			read_ocr_response[2], read_ocr_response[3]); +	} +	ao_sdcard_deselect(); +	return ret; +} + +static void +ao_sdcard_setup(void) +{ +	int	i; +	uint8_t	ret; +	uint8_t	response[10]; + +	DBG ("Testing sdcard\n"); + +	ao_sdcard_get_slow(); +	/* +	 * min 74 clocks with CS high +	 */ +	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; +	} +	if (i == SDCARD_IDLE_WAIT) +		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) { +		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; +			sdtype = ao_sdtype_mmc3; +		} else { +			/* SD */ +			if (sdver2 != 0) { +				ret = ao_sdcard_read_ocr(response); +				if (ret != SDCARD_STATUS_READY_STATE) +					goto bail; +				if ((response[0] & 0xc0) == 0xc0) +					sdtype = ao_sdtype_sd2block; +				else +					sdtype = ao_sdtype_sd2byte; +			} else { +				sdtype = ao_sdtype_sd1; +			} +		} + +		/* 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"); +		} +	} + +	DBG ("SD card detected, type %d\n", sdtype); +bail: +	ao_sdcard_put(); +} + +static uint8_t +ao_sdcard_wait_block_start(void) +{ +	int	i; +	uint8_t	v; + +	DBG ("\twait_block_start\n"); +	for (i = 0; i < SDCARD_BLOCK_TIMEOUT; i++) { +		ao_sdcard_recv(&v, 1); +		DBG("\t\trecv %02x\n", v); +		if (v != 0xff) +			break; +	} +	return v; +} + +/* + * Read a block of 512 bytes from the card + */ +uint8_t +ao_sdcard_read_block(uint32_t block, uint8_t *data) +{ +	uint8_t	ret; +	uint8_t crc[2]; + +	ao_sdcard_lock(); +	if (!initialized) { +		ao_sdcard_setup(); +		initialized = 1; +		if (sdtype != ao_sdtype_unknown) +			present = 1; +	} +	if (!present) { +		ao_sdcard_unlock(); +		return 0; +	} +	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; + +	if (ao_sdcard_wait_block_start() != 0xfe) { +		ret = 0x3f; +		goto bail; +	} + +	ao_sdcard_recv(data, 512); +	ao_sdcard_recv(crc, 2); +bail: +	ao_sdcard_deselect(); +	ao_sdcard_put(); +	ao_sdcard_unlock(); +	return ret == SDCARD_STATUS_READY_STATE; +} + +/* + * Write a block of 512 bytes to the card + */ +uint8_t +ao_sdcard_write_block(uint32_t block, uint8_t *data) +{ +	/* Not doing anything until the file system code seems reasonable +	 */ +	return 1; +} + +void +ao_sdcard_init(void) +{ +	ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN)); +} + +  | 
