/* * Copyright (c) 2009, Swedish Institute of Computer Science * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the Institute nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is part of the Contiki operating system. * */ /** * \file * SD driver implementation using SPI. * \author * Nicolas Tsiftes */ #include "contiki.h" #include "sd.h" #include "sd-arch.h" #include #define DEBUG 0 #if DEBUG #include #define PRINTF(...) printf(__VA_ARGS__) #else #define PRINTF(...) #endif #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif /* MIN */ #define SPI_IDLE 0xff /* SD commands */ #define GO_IDLE_STATE 0 #define SEND_OP_COND 1 #define SWITCH_FUNC 6 #define SEND_IF_COND 8 #define SEND_CSD 9 #define SEND_CID 10 #define STOP_TRANSMISSION 12 #define SEND_STATUS 13 #define READ_SINGLE_BLOCK 17 #define WRITE_BLOCK 24 #define READ_OCR 58 /* SD response lengths. */ #define R1 1 #define R2 2 #define R3 5 #define R7 5 #define START_BLOCK_TOKEN 0xfe /* Status codes returned after writing a block. */ #define DATA_ACCEPTED 2 #define DATA_CRC_ERROR 5 #define DATA_WRITE_ERROR 6 static uint16_t rw_block_size; static uint16_t block_size; static int read_register(int register_cmd, char *buf, int register_size); /*---------------------------------------------------------------------------*/ static int send_command(uint8_t cmd, uint32_t argument) { uint8_t req[6]; req[0] = 0x40 | cmd; req[1] = argument >> 24; req[2] = argument >> 16; req[3] = argument >> 8; req[4] = argument; /* The CRC hard-wired to 0x95 is only needed for the initial GO_IDLE_STATE command. */ req[5] = 0x95; sd_arch_spi_write(SPI_IDLE); sd_arch_spi_write_block(req, sizeof(req)); sd_arch_spi_write(SPI_IDLE); return 0; } /*---------------------------------------------------------------------------*/ static uint8_t * get_response(int length) { int i; int x; static uint8_t r[R7]; for(i = 0; i < SD_READ_RESPONSE_ATTEMPTS; i++) { x = sd_arch_spi_read(); if((x & 0x80) == 0) { /* A get_response byte is indicated by the MSB being 0. */ r[0] = x; break; } } if(i == SD_READ_RESPONSE_ATTEMPTS) { return NULL; } for(i = 1; i < length; i++) { r[i] = sd_arch_spi_read(); } return r; } /*---------------------------------------------------------------------------*/ static unsigned char * transaction(int command, unsigned long argument, int response_type, unsigned attempts) { unsigned i; unsigned char *r; LOCK_SPI(); r = NULL; for(i = 0; i < attempts; i++) { LOWER_CS(); send_command(command, argument); r = get_response(response_type); RAISE_CS(); if(r != NULL) { break; } } UNLOCK_SPI(); return r; } /*---------------------------------------------------------------------------*/ int sd_initialize(void) { unsigned char reg[16]; int i; uint8_t *r, read_bl_len; if(sd_arch_init() < 0) { return SD_INIT_ERROR_ARCH; } if(SD_CONNECTED() < 0) { return SD_INIT_ERROR_NO_CARD; } r = transaction(GO_IDLE_STATE, 0, R1, SD_TRANSACTION_ATTEMPTS); if(r != NULL) { PRINTF("Go-idle result: %d\n", r[0]); } else { PRINTF("Failed to get go-idle response\n"); } r = transaction(SEND_IF_COND, 0, R7, SD_TRANSACTION_ATTEMPTS); if(r != NULL) { PRINTF("IF cond: %d %d %d %d %d\n", r[0], r[1], r[2], r[3], r[4]); } else { PRINTF("failed to get IF cond\n"); return SD_INIT_ERROR_NO_IF_COND; } LOCK_SPI(); for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { LOWER_CS(); send_command(SEND_OP_COND, 0); r = get_response(R1); RAISE_CS(); if(r != NULL && !(r[0] & 1)) { break; } } UNLOCK_SPI(); if(r != NULL) { PRINTF("OP cond: %d (%d iterations)\n", r[0], i); } else { PRINTF("Failed to get OP cond get_response\n"); } LOCK_SPI(); for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { LOWER_CS(); send_command(READ_OCR, 0); r = get_response(R3); RAISE_CS(); if(r != NULL) { break; } } UNLOCK_SPI(); if(r != NULL) { PRINTF("OCR: %d %d %d %d %d\n", r[0], r[1], r[2], r[3], r[4]); } if(read_register(SEND_CSD, reg, sizeof(reg)) < 0) { PRINTF("Failed to get block size of SD card\n"); return SD_INIT_ERROR_NO_BLOCK_SIZE; } read_bl_len = reg[5] & 0x0f; block_size = 1 << read_bl_len; rw_block_size = (block_size > SD_DEFAULT_BLOCK_SIZE) ? SD_DEFAULT_BLOCK_SIZE : block_size; PRINTF("Found block size %d\n", block_size); /* XXX Arbitrary wait time here. Need to investigate why this is needed. */ MS_DELAY(5); return SD_OK; } /*---------------------------------------------------------------------------*/ int sd_write_block(sd_offset_t offset, char *buf) { unsigned char *r; int retval; int i; unsigned char data_response; unsigned char status_code; LOCK_SPI(); r = NULL; retval = SD_WRITE_ERROR_NO_CMD_RESPONSE; for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { LOWER_CS(); send_command(WRITE_BLOCK, offset); r = get_response(R1); if(r != NULL) { break; } RAISE_CS(); } if(r != NULL && r[0] == 0) { /* We received an R1 response with no errors. Send a start block token to the card now. */ sd_arch_spi_write(START_BLOCK_TOKEN); /* Write the data block. */ sd_arch_spi_write_block(buf, rw_block_size); /* Get a response from the card. */ retval = SD_WRITE_ERROR_NO_BLOCK_RESPONSE; for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { data_response = sd_arch_spi_read(); if((data_response & 0x11) == 1) { /* Data response token received. */ status_code = (data_response >> 1) & 0x7; if(status_code == DATA_ACCEPTED) { retval = rw_block_size; } else { retval = SD_WRITE_ERROR_PROGRAMMING; } break; } } } RAISE_CS(); UNLOCK_SPI(); return retval; } /*---------------------------------------------------------------------------*/ static int read_block(unsigned read_cmd, sd_offset_t offset, char *buf, int len) { unsigned char *r; int i; int token; int retval; LOCK_SPI(); r = NULL; for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { LOWER_CS(); send_command(read_cmd, offset); r = get_response(R1); if(r != NULL) { break; } RAISE_CS(); } if(r != NULL && r[0] == 0) { /* We received an R1 response with no errors. Get a token from the card now. */ for(i = 0; i < SD_TRANSACTION_ATTEMPTS; i++) { token = sd_arch_spi_read(); if(token == START_BLOCK_TOKEN || (token > 0 && token <= 8)) { break; } } if(token == START_BLOCK_TOKEN) { /* A start block token has been received. Read the block now. */ for(i = 0; i < len; i++) { buf[i] = sd_arch_spi_read(); } /* Consume CRC. TODO: Validate the block. */ sd_arch_spi_read(); sd_arch_spi_read(); retval = len; } else if(token > 0 && token <= 8) { /* The card returned a data error token. */ retval = SD_READ_ERROR_TOKEN; } else { /* The card never returned a token after our read attempts. */ retval = SD_READ_ERROR_NO_TOKEN; } RAISE_CS(); UNLOCK_SPI(); return retval; } RAISE_CS(); UNLOCK_SPI(); if(r != NULL) { PRINTF("status during read: %d\n", r[0]); } return SD_READ_ERROR_NO_CMD_RESPONSE; } /*---------------------------------------------------------------------------*/ int sd_read_block(sd_offset_t offset, char *buf) { return read_block(READ_SINGLE_BLOCK, offset, buf, rw_block_size); } /*---------------------------------------------------------------------------*/ static int read_register(int register_cmd, char *buf, int register_size) { return read_block(register_cmd, 0, buf, register_size); } /*---------------------------------------------------------------------------*/ sd_offset_t sd_get_capacity(void) { unsigned char reg[16]; int r; uint16_t c_size; uint8_t c_size_mult; sd_offset_t block_nr; sd_offset_t mult; r = read_register(SEND_CSD, reg, sizeof(reg)); if(r < 0) { return r; } c_size = ((reg[6] & 3) << 10) + (reg[7] << 2) + ((reg[8] >> 6) & 3); c_size_mult = ((reg[9] & 3) << 1) + ((reg[10] & 0x80) >> 7); mult = 1 << (c_size_mult + 2); block_nr = (c_size + 1) * mult; return block_nr * block_size; } /*---------------------------------------------------------------------------*/ char * sd_error_string(int error_code) { #if DEBUG switch(error_code) { case SD_OK: return "operation succeeded"; case SD_INIT_ERROR_NO_CARD: return "no card was found"; case SD_INIT_ERROR_ARCH: return "architecture-dependent initialization failed"; case SD_INIT_ERROR_NO_IF_COND: return "unable to obtain the interface condition"; case SD_INIT_ERROR_NO_BLOCK_SIZE: return "unable to obtain the block size"; case SD_WRITE_ERROR_NO_CMD_RESPONSE: return "no response from the card after submitting a write request"; case SD_WRITE_ERROR_NO_BLOCK_RESPONSE: return "no response from the card after sending a data block"; case SD_WRITE_ERROR_PROGRAMMING: return "the write request failed because of a card error"; case SD_WRITE_ERROR_TOKEN: return "the card is not ready to grant a write request"; case SD_READ_ERROR_NO_TOKEN: case SD_WRITE_ERROR_NO_TOKEN: return "did not receive a start block token"; case SD_READ_ERROR_INVALID_SIZE: return "invalid read block size"; case SD_READ_ERROR_TOKEN: return "the card is not ready to read a data block"; case SD_READ_ERROR_NO_CMD_RESPONSE: return "no response from the card after submitting a read request"; default: break; } #endif return "unspecified error"; } /*---------------------------------------------------------------------------*/ int sd_write(sd_offset_t offset, char *buf, size_t size) { sd_offset_t address; uint16_t offset_in_block; int r, i; size_t written; size_t to_write; char sd_buf[rw_block_size]; /* Emulation of data writing using arbitrary offsets and chunk sizes. */ memset(sd_buf, 0, sizeof(sd_buf)); written = 0; offset_in_block = offset & (rw_block_size - 1); do { to_write = MIN(size - written, rw_block_size - offset_in_block); address = (offset + written) & ~(rw_block_size - 1); for(i = 0; i < SD_READ_BLOCK_ATTEMPTS; i++) { r = sd_read_block(address, sd_buf); if(r == sizeof(sd_buf)) { break; } } if(r != sizeof(sd_buf)) { return r; } memcpy(&sd_buf[offset_in_block], &buf[written], to_write); r = sd_write_block(address, sd_buf); if(r != sizeof(sd_buf)) { return r; } written += to_write; offset_in_block = 0; } while(written < size); return written; } /*---------------------------------------------------------------------------*/ int sd_read(sd_offset_t offset, char *buf, size_t size) { size_t total_read; size_t to_read; char sd_buf[rw_block_size]; uint16_t offset_in_block; int r, i; /* Emulation of data reading using arbitrary offsets and chunk sizes. */ total_read = 0; offset_in_block = offset & (rw_block_size - 1); do { to_read = MIN(size - total_read, rw_block_size - offset_in_block); for(i = 0; i < SD_READ_BLOCK_ATTEMPTS; i++) { r = sd_read_block((offset + total_read) & ~(rw_block_size - 1), sd_buf); if(r == sizeof(sd_buf)) { break; } } if(r != sizeof(sd_buf)) { return r; } memcpy(&buf[total_read], &sd_buf[offset_in_block], to_read); total_read += to_read; offset_in_block = 0; } while(total_read < size); return size; } /*---------------------------------------------------------------------------*/