You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
689 lines
14 KiB
689 lines
14 KiB
/* 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; |
|
} |
|
|
|
|