osd-contiki/platform/msb430/dev/sd.c

521 lines
13 KiB
C
Executable file

/*
* 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 <nvt@sics.se>
*/
#include "contiki.h"
#include "msb430-uart1.h"
#include "sd.h"
#include <string.h>
#define DEBUG 0
#if DEBUG
#include <stdio.h>
#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 void
spi_write(int c)
{
UART_TX = c;
UART_WAIT_TXDONE();
}
/*---------------------------------------------------------------------------*/
static unsigned
spi_read(void)
{
UART_TX = SPI_IDLE;
UART_WAIT_RX();
return UART_RX;
}
/*---------------------------------------------------------------------------*/
static void
spi_write_block(uint8_t *bytes, int amount)
{
int i;
for(i = 0; i < amount; i++) {
UART_TX = bytes[i];
UART_WAIT_TXDONE();
UART_RX;
}
}
/*---------------------------------------------------------------------------*/
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;
spi_write(SPI_IDLE);
spi_write_block(req, sizeof(req));
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 = 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] = 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)
{
int i;
uint8_t *r;
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 < 10000; 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]);
}
/* 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. */
spi_write(START_BLOCK_TOKEN);
/* Write the data block. */
spi_write_block(buf, SD_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 = spi_read();
if((data_response & 0x11) == 1) {
/* Data response token received. */
status_code = (data_response >> 1) & 0x7;
if(status_code == DATA_ACCEPTED) {
retval = SD_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 = 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] = spi_read();
}
/* Consume CRC. TODO: Validate the block. */
spi_read();
spi_read();
retval = SD_BLOCK_SIZE;
} 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, SD_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] & 3);
c_size_mult = ((reg[9] & 3) << 1) + ((reg[10] & 0x80) >> 7);
mult = 2 << (c_size_mult + 2);
block_nr = (c_size + 1) * mult;
return block_nr * SD_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_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[SD_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 % SD_BLOCK_SIZE;
do {
to_write = MIN(size - written, SD_BLOCK_SIZE - offset_in_block);
address = (offset + written) & ~(SD_BLOCK_SIZE - 1);
for(i = 0; i < SD_READ_BLOCK_ATTEMPTS; i++) {
r = sd_read_block(address, sd_buf);
if(r == sizeof(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[SD_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 % SD_BLOCK_SIZE;
do {
to_read = MIN(size - total_read, SD_BLOCK_SIZE - offset_in_block);
for(i = 0; i < SD_READ_BLOCK_ATTEMPTS; i++) {
r = sd_read_block((offset + total_read) & ~(SD_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;
}
/*---------------------------------------------------------------------------*/