The unified diff between revisions [4f22e7ef..] and [dc88787e..] is displayed below. It can also be downloaded as a raw diff.
This diff has been restricted to the following files: 'sdcard.c'
# # old_revision [4f22e7ef7d3064e3b51a5b868a4722f3f13c747b] # new_revision [dc88787ecd1d574feba045763baed2a7651ff33d] # # add_file "sdcard.c" # content [d2762fa54e59dc4bb97f17ac0f3ff90735d55445] # ============================================================ --- /dev/null +++ sdcard.c d2762fa54e59dc4bb97f17ac0f3ff90735d55445 @@ -0,0 +1,689 @@ +/* sdcard.c */ + +#include "spi.h" +#include "types.h" +#include "uart.h" +#include "timer.h" +#include "event.h" +#include "log.h" +#include "config.h" + +#define spi_write_array(x) spi_write_bytes(x, sizeof(x)/sizeof(x[0])) + +#define SDCARD_COMMAND_TIMEOUT 0xffff + +char dummy_block[] = {0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff}; +char reset_command[] = {0x40, 0, 0, 0, 0, 0x95}; + +/* Voltage = 2.7-3.6V, check pattern = 0xaa, CRC matters for CMD8 */ +char sdcard_cmd8[] = {0x48, 0, 0, 0x01, 0xaa, 0x87}; + +char sdcard_cmd55[] = {0x77, 0, 0, 0, 0, 0xff}; + +char sdcard_acmd41[] = {0x69, 0, 0, 0, 0, 0xff}; +char sdcard_acmd41_hcs[] = {0x69, 0x40, 0, 0, 0, 0xff}; + +char sdcard_cmd58[] = {0x7a, 0, 0, 0, 0, 0xff}; + +/* 512 bytes block length */ +char sdcard_cmd16[] = {0x50, 0, 0, 2, 0, 0xff}; + +/* Read CSD */ +char sdcard_cmd9[] = {0x49, 0, 0, 0, 0, 0xff}; + + +static bool high_capacity; + +#ifdef SDCARD_BOUNDARY_128K +/* 128K */ +#define SDCARD_BOUNDARY_MASK 0xff +#define SDCARD_BOUNDARY_SIZE 0x100 +#else +/* 32K */ +#define SDCARD_BOUNDARY_MASK 0x3f +#define SDCARD_BOUNDARY_SIZE 0x40 +#endif + +unsigned int sdcard_sector; +unsigned int sdcard_offset; +unsigned int sdcard_size; /* defined as number of sectors */ + +#define SDCARD_IDLE 0 +#define SDCARD_WRITE_GAP 1 +#define SDCARD_WRITING_BLOCK 2 +#define SDCARD_STOPPING 3 +#define SDCARD_ERROR 4 + +unsigned int sdcard_active; + +static bool sdcard_command(char *command, unsigned int command_length, + char *response, unsigned int response_length, bool wait_busy); + +bool sdcard_write(unsigned int address, char *buffer, unsigned int length); +bool sdcard_read_csd(char *buffer); +void sdcard_prepare(void); + +/* SD card (SPI mode initialisation) + +power on + +CMD0+ +CMD8 +if (no response from CMD8) { + // legacy (MMC) card + CMD58 (optional, read OCR) - no or bad response = don't use card + while (ACMD41(arg 0x00) & in_idle_state_mask) + ; + done +} else { + // SD card + if (response from CMD8 was present but invalid + (check pattern not matched)) + retry CMD8; + CMD58 (optional, read OCR) + while (ACMD41(arg HCS=1) & in_idle_state_mask) + ; + CMD58 (Get CCS) + if (CCS) + done - high capacity SD card + else + done - standard SD card +} + + +*/ + +bool init_sdcard(void) +{ + char response[16]; + unsigned int i; + + unsigned int read_bl_len, c_size_mult, c_size; + unsigned int block_len, mult, blocknr; + + putstr("Initialising SPI\r\n"); + + init_spi(); + + high_capacity = FALSE; + + putstr("Sending 80 clocks\r\n"); + + spi_transaction_start(); + spi_write_array(dummy_block); + spi_transaction_stop(); + + putstr("Sending reset command\r\n"); + + if (!sdcard_command(reset_command, sizeof(reset_command), + response, 1, FALSE)) + return FALSE; + + putstr("Reset command successful. Checking response.\r\n"); + + puthex(response[0]); + + putstr("\r\n"); + + if (response[0] != 0x01) + return FALSE; + + putstr("Sending CMD8\r\n"); + + if (!sdcard_command(sdcard_cmd8, sizeof(sdcard_cmd8), + response, 5, FALSE)) + { + putstr("No response. Legacy device.\r\n"); + /* Legacy device */ + do { + if (!sdcard_command(sdcard_cmd55, sizeof(sdcard_cmd55), + response, 1, FALSE)) + return FALSE; + if (response[0] != 0x01) + return FALSE; + if (!sdcard_command(sdcard_acmd41, + sizeof(sdcard_acmd41), + response, 1, FALSE)) + return FALSE; + } while (response[0] & 1); + putstr("ACMD41 gave us the right response.\r\n"); + } else { + putstr("We got a response. Not a legacy device.\r\n"); + /* Not legacy device */ + for (i = 1; i < 4; i++) { + if (response[i] != sdcard_cmd8[i]) { + /* We should really retry here. Meh. */ + return FALSE; + } + } + putstr("Response OK. Safe to continue.\r\n"); + do { + if (!sdcard_command(sdcard_cmd55, sizeof(sdcard_cmd55), + response, 1, FALSE)) + return FALSE; + if (response[0] != 0x01) + return FALSE; + if (!sdcard_command(sdcard_acmd41_hcs, + sizeof(sdcard_acmd41_hcs), + response, 1, FALSE)) + return FALSE; + } while (response[0] & 1); + putstr("ACMD41 gave us the right response.\r\n"); + if (!sdcard_command(sdcard_cmd58, sizeof(sdcard_cmd58), + response, 5, FALSE)) + return FALSE; + putstr("OCR register retrieved.\r\n"); + if ((response[1] & 0x80) == 0) + return FALSE; + putstr("Chip isn't still powering up.\r\n"); + if (response[1] & 0x40) { + putstr("We have a high capacity device.\r\n"); + high_capacity = TRUE; + } else { + putstr("We have a low capacity device.\r\n"); + high_capacity = FALSE; + } + } + + spi_speedup(); + + /* Set block length to 512 */ + if (!sdcard_command(sdcard_cmd16, sizeof(sdcard_cmd16), + response, 1, FALSE)) + return FALSE; + + putstr("Determining card size.\r\n"); + + if (!sdcard_read_csd(response)) + return FALSE; + + putstr("Read CSD\r\n"); + + switch ((response[0] & 0xc0) >> 6) { + case 0: + /* CSD Version 1.0 */ + read_bl_len = response[5] & 0x0f; + c_size_mult = ((response[9] & 0x03) << 1) | (response[10] >> 7); + c_size = ((response[6] & 0x03) << 10) | (response[7] << 2) | + (response[8] >> 6); + + block_len = 1<<read_bl_len; + mult = 1<<(c_size_mult+2); + blocknr = (c_size+1) * mult; + sdcard_size = blocknr * block_len / 512; + break; + case 1: + /* CSD Version 2.0 */ + c_size = ((response[7] & 0x3f) << 16) | + (response[8] << 8) | response[9]; + sdcard_size = (c_size+1) * 1024; + break; + default: + /* Unrecognised CSD version */ + putstr("Unrecognised CSD version\r\n"); + return FALSE; + } + + putstr("SD initialisation sequence complete.\r\n"); + putstr("size = "); + putint(sdcard_size / 2); + putstr("KB\r\n"); + + putstr("Initialising logging system.\r\n"); + sdcard_prepare(); + + return TRUE; +} + +static bool sdcard_command_innards(char *command, unsigned int command_length, + char *response, unsigned int response_length, bool wait_busy) +{ + char byte; + unsigned int i; + + spi_write_bytes(command, command_length); + + i = 0; + + do + { + byte = spi_read_byte(); + i++; + } while (((byte & 0x80) != 0) && (i < SDCARD_COMMAND_TIMEOUT)); + + if (byte & 0x80) + return FALSE; + + if (response_length > 0) + response[0] = byte; + + /* We need to store the response, plus read one extra byte for luck. */ + /* XXX not an extra byte for luck any more */ + for (i = 1; i < response_length; i++) { + byte = spi_read_byte(); + response[i] = byte; + } + + if (wait_busy) { + do { + byte = spi_read_byte(); + } while (byte == 0); + + spi_write_byte(0xff); + } + + return TRUE; +} + +static bool sdcard_check_data_response(void) +{ + char byte; + unsigned int i; + + i = 0; + + do + { + byte = spi_read_byte(); + i++; + } while (((byte & 0x11) != 0x01) && (i < SDCARD_COMMAND_TIMEOUT)); + + if ((byte & 0x11) != 0x01) + return FALSE; + + if ((byte & 0x0f) != 0x05) /* Data accepted */ + return FALSE; + + /* Read one more byte for luck */ + byte = spi_read_byte(); + + return TRUE; +} + +static bool sdcard_command(char *command, unsigned int command_length, + char *response, unsigned int response_length, bool wait_busy) +{ + bool result; + + spi_transaction_start(); + + result = sdcard_command_innards(command, command_length, + response, response_length, wait_busy); + + spi_transaction_stop(); + + return result; +} + +static bool sdcard_read_block(char *buffer, unsigned int length) +{ + unsigned int i; + unsigned int crc_hi; + unsigned int crc_lo; + unsigned int crc; + + while (1) { + char byte = spi_read_byte(); + if (byte == 0xff) + continue; + if (byte == 0xfe) + break; + if ((byte & 0xf0) == 0) + if (byte != 0) + return FALSE; + } + + /* We need to store the response, plus read one extra byte for luck. */ + for (i = 0; i < length; i++) { + buffer[i] = spi_read_byte(); + } + + crc_hi = spi_read_byte(); + crc_lo = spi_read_byte(); + + crc = (crc_hi << 8) + crc_lo; + + /* XXX check CRC and return FALSE if doesn't match */ + + return TRUE; +} + +bool sdcard_read(unsigned int address, char *buffer, unsigned int length) +{ + bool valid; + char response; + + char cmd[6]; + + if (!high_capacity) + address = address * 512; + + cmd[0] = 0x51; /* CMD17 */ + cmd[1] = (address >> 24) & 0xff; + cmd[2] = (address >> 16) & 0xff; + cmd[3] = (address >> 8) & 0xff; + cmd[4] = (address >> 0) & 0xff; + cmd[5] = 0xff; /* dummy CRC */ + + spi_transaction_start(); + + if (!sdcard_command_innards(cmd, sizeof(cmd), + &response, 1, FALSE)) { + spi_transaction_stop(); + return FALSE; + } + + if (response != 0) { + spi_transaction_stop(); + return FALSE; + } + + valid = sdcard_read_block(buffer, length); + + spi_transaction_stop(); + + return valid; +} + +bool sdcard_read_csd(char *buffer) +{ + bool valid; + char response; + + spi_transaction_start(); + + if (!sdcard_command_innards(sdcard_cmd9, sizeof(sdcard_cmd9), + &response, 1, FALSE)) { + spi_transaction_stop(); + return FALSE; + } + + if (response != 0) { + spi_transaction_stop(); + return FALSE; + } + + valid = sdcard_read_block(buffer, 16); + + spi_transaction_stop(); + + return valid; +} + +bool sdcard_send_write_cmd(unsigned int address) +{ + char response; + + char cmd[6]; + + if (!high_capacity) + address = address * 512; + + cmd[0] = 0x59; /* CMD25 */ + cmd[1] = (address >> 24) & 0xff; + cmd[2] = (address >> 16) & 0xff; + cmd[3] = (address >> 8) & 0xff; + cmd[4] = (address >> 0) & 0xff; + cmd[5] = 0xff; /* dummy CRC */ + + spi_transaction_start(); + + if (!sdcard_command_innards(cmd, sizeof(cmd), + &response, 1, FALSE)) { + spi_transaction_stop(); + return FALSE; + } + + if (response != 0) { + spi_transaction_stop(); + return FALSE; + } + + return TRUE; +} + +static void sdcard_send_data_token(void) +{ + spi_write_byte(0xfc); +} + +static void sdcard_send_stop_token(void) +{ + spi_write_byte(0xfd); +} + +#define READ_UINT(b, i) ((b)[(i)] + ((b)[(i)+1] << 8) + \ + ((b)[(i)+2] << 16) + ((b)[(i)+3] << 24)) + +#define WRITE_UINT(b, i, d) \ + do { \ + (b)[(i)] = (d) & 0xff; \ + (b)[(i)+1] = ((d) >> 8) & 0xff; \ + (b)[(i)+2] = ((d) >> 16) & 0xff; \ + (b)[(i)+3] = ((d) >> 24) & 0xff; \ + } while (0) + + +/* We assume that the magic is to be found within this area. If not, + * we will need to read a bigger area. If the typical record size grows + * to more than a sector, for example, then we will need to read in multiple + * sectors where this function is called. + */ +bool sdcard_scan_magic(char *buffer, unsigned int size, unsigned int generation) +{ + unsigned int i; + + for (i = 0; i < size - 8; i++) { + if ((buffer[i] == (LOG_MAGIC & 0xff)) && + (buffer[i+1] == ((LOG_MAGIC >> 8) & 0xff)) && + (buffer[i+2] == ((LOG_MAGIC >> 16) & 0xff)) && + (buffer[i+3] == ((LOG_MAGIC >> 24) & 0xff)) && + (buffer[i+4] == ((generation >> 0) & 0xff)) && + (buffer[i+5] == ((generation >> 8) & 0xff)) && + (buffer[i+6] == ((generation >> 16) & 0xff)) && + (buffer[i+7] == ((generation >> 24) & 0xff))) + return TRUE; + } + + return FALSE; +} + +void sdcard_prepare(void) +{ + unsigned int magic; + unsigned int start_sector; + unsigned int config_sector; + unsigned int count; + + if (!sdcard_read(0, log_buffer, 512)) + return; + + magic = READ_UINT(log_buffer, 0); + + if (magic != LOG_MAGIC) { + unsigned int i; + for (i = 0; i < 512; i++) + log_buffer[i] = 0; + WRITE_UINT(log_buffer, 0, LOG_MAGIC); + start_sector = SDCARD_BOUNDARY_SIZE; + log_generation = 0; + config_sector = 1; + putstr("Did not find header. Formatting.\r\n"); + } else { + start_sector = READ_UINT(log_buffer, 4); + log_generation = READ_UINT(log_buffer, 8); + config_sector = READ_UINT(log_buffer, 12); + count = 0; + putstr("Found header.\r\n"); + putstr("Last started at sector "); + putint(start_sector); + putstr(" with generation "); + putint(log_generation); + putstr("\r\n"); + while (1) { + if (!sdcard_read(start_sector, log_buffer+512, 512)) + return; + /* This needs to change if record length exceeds 512 */ + if (sdcard_scan_magic(log_buffer+512, 512, + log_generation)) { + start_sector += SDCARD_BOUNDARY_SIZE; + if (start_sector >= sdcard_size) + start_sector = SDCARD_BOUNDARY_SIZE; + } else { + break; + } + if (count++ > (sdcard_size / SDCARD_BOUNDARY_SIZE)) { + start_sector = SDCARD_BOUNDARY_SIZE; + break; + } + } + log_generation++; + } + + WRITE_UINT(log_buffer, 4, start_sector); + WRITE_UINT(log_buffer, 8, log_generation); + WRITE_UINT(log_buffer, 12, config_sector); + + putstr("Starting at sector "); + putint(start_sector); + putstr(" with generation "); + putint(log_generation); + putstr("\r\n"); + + if (!sdcard_write(0, log_buffer, 512)) + return; + + sdcard_sector = start_sector; + sdcard_offset = 0; + + if (!sdcard_read(config_sector, log_buffer, 512)) + return; + + log_enabled = TRUE; + + config_init(log_buffer); +} + + +static bool sdcard_busy(void) +{ + return (spi_read_byte() != 0xff); +} + +static void sdcard_send_dummy_crc(void) +{ + spi_write_byte(0xff); + spi_write_byte(0xff); +} + +void sdcard_poll(void) +{ + if (!log_enabled) + return; + if (LOG_BUFFER_EMPTY) + return; + log_mark_busy(); + if (sdcard_active == SDCARD_IDLE) { + spi_transaction_start(); + if (sdcard_busy()) { + spi_transaction_stop(); + log_mark_idle(); + return; + } + putch('C'); + if (sdcard_send_write_cmd(sdcard_sector)) + sdcard_active = SDCARD_WRITE_GAP; + else { + spi_transaction_stop(); + sdcard_active = SDCARD_ERROR; + } + } + if (sdcard_active == SDCARD_WRITE_GAP) { + if (sdcard_busy()) { + log_mark_idle(); + return; + } + sdcard_send_data_token(); + sdcard_active = SDCARD_WRITING_BLOCK; + } + if (sdcard_active == SDCARD_WRITING_BLOCK) { + unsigned int bytes_to_end_of_sector; + unsigned int i; + + i = LOG_BUFFER_BYTES; + bytes_to_end_of_sector = 512 - sdcard_offset; + if (i > bytes_to_end_of_sector) + i = bytes_to_end_of_sector; + if (i > 32) + i = 32; + sdcard_offset += i; + while (i--) { + spi_write_byte(log_get_byte()); + } + if (sdcard_offset >= 512) { + sdcard_offset = 0; + sdcard_sector++; + sdcard_send_dummy_crc(); + putch('.'); + if (!sdcard_check_data_response()) { + /* Set state to STOPPING instead? */ + /* How do we test this? */ + spi_transaction_stop(); + sdcard_active = SDCARD_ERROR; + log_mark_idle(); + return; + } + sdcard_active = SDCARD_WRITE_GAP; + if ((sdcard_sector & SDCARD_BOUNDARY_MASK) == 0) { + putch('S'); + sdcard_active = SDCARD_STOPPING; + } + } + } + if (sdcard_active == SDCARD_STOPPING) { + if (sdcard_busy()) { + log_mark_idle(); + return; + } + sdcard_send_stop_token(); + spi_transaction_stop(); + sdcard_active = SDCARD_IDLE; + } + log_mark_idle(); +} + +bool sdcard_write(unsigned int address, char *buffer, unsigned int length) +{ + unsigned int i; + + spi_transaction_start(); + + if (!sdcard_send_write_cmd(address)) { + spi_transaction_stop(); + return FALSE; + } + + sdcard_send_data_token(); + + for (i = 0; i < length; i++) { + spi_write_byte(buffer[i]); + } + + sdcard_send_dummy_crc(); + if (!sdcard_check_data_response()) { + spi_transaction_stop(); + return FALSE; + } + + while (sdcard_busy()) ; + + sdcard_send_stop_token(); + + while (sdcard_busy()) ; + + spi_transaction_stop(); + + return TRUE; +} +