From: lottaviano Date: Fri, 3 Jul 2009 13:51:37 +0000 (+0000) Subject: Import SD card driver. X-Git-Tag: 2.2.0~234 X-Git-Url: https://codewiz.org/gitweb?a=commitdiff_plain;h=1fb9b95e56d03933e5b38709a497b5f5f892ea23;p=bertos.git Import SD card driver. git-svn-id: https://src.develer.com/svnoss/bertos/trunk@2734 38d2e660-2303-0410-9eaa-f027e97ec537 --- diff --git a/bertos/drv/sd.c b/bertos/drv/sd.c new file mode 100644 index 00000000..0c151563 --- /dev/null +++ b/bertos/drv/sd.c @@ -0,0 +1,498 @@ +/** + * \file + * + * + * \brief Function library for secure digital memory. + * + * \version $Id$ + * \author Francesco Sacchi + */ + + +#include "sd.h" +#include "hw/hw_sd.h" +#include +#include + +#include + +#include +#include + +#include /* memset */ + +/** + * Card Specific Data + * read directly from the card. + */ +typedef struct CardCSD +{ + uint16_t block_len; ///< Length of a block + uint32_t block_num; ///< Number of block on the card + uint16_t capacity; ///< Card capacity in MB +} CardCSD; + +#define SD_IN_IDLE 0x01 +#define SD_STARTTOKEN 0xFE + +#define TIMEOUT_NAC 256 + +#define SD_DEFAULT_BLOCKLEN 512 + +/** + * SPI communication channel. + */ +static KFile *fd; + +/** + * Current SD status. + */ +static bool sd_status; + +#define SD_BUSY_TIMEOUT ms_to_ticks(200) + +static bool sd_select(bool state) +{ + if (state) + { + ticks_t start = timer_clock(); + do + { + SD_CS_ON(); + if (kfile_getc(fd) == 0xff) + return true; + SD_CS_OFF(); + kfile_putc(0xff, fd); + kfile_flush(fd); + cpu_relax(); + } + while (timer_clock() - start < SD_BUSY_TIMEOUT); + + LOG_ERR("sd_select timeout\n"); + return false; + } + else + { + kfile_putc(0xff, fd); + kfile_flush(fd); + SD_CS_OFF(); + return true; + } +} + +static int16_t sd_waitR1(void) +{ + uint8_t datain; + + for (int i = 0; i < TIMEOUT_NAC; i++) + { + datain = kfile_getc(fd); + if (datain != 0xff) + return (int16_t)datain; + } + LOG_ERR("Timeout waiting R1\n"); + return EOF; +} + +static int16_t sd_sendCommand(uint8_t cmd, uint32_t param, uint8_t crc) +{ + /* The 7th bit of command must be a 1 */ + kfile_putc(cmd | 0x40, fd); + + /* send parameter */ + kfile_putc((param >> 24) & 0xFF, fd); + kfile_putc((param >> 16) & 0xFF, fd); + kfile_putc((param >> 8) & 0xFF, fd); + kfile_putc((param) & 0xFF, fd); + + kfile_putc(crc, fd); + + return sd_waitR1(); +} + + +static bool sd_getBlock(void *buf, size_t len) +{ + uint8_t token; + uint16_t crc; + + for (int i = 0; i < TIMEOUT_NAC; i++) + { + token = kfile_getc(fd); + if (token != 0xff) + { + if (token == SD_STARTTOKEN) + { + if (kfile_read(fd, buf, len) == len) + { + if (kfile_read(fd, &crc, sizeof(crc)) == sizeof(crc)) + /* check CRC here if needed */ + return true; + else + LOG_ERR("get_block error getting crc\n"); + } + else + LOG_ERR("get_block len error: %d\n", (int)len); + } + else + LOG_ERR("get_block token error: %02X\n", token); + + return false; + } + } + + LOG_ERR("get_block timeout waiting token\n"); + return false; +} + +#define SD_SELECT() \ +do \ +{ \ + if (!sd_select(true)) \ + { \ + LOG_ERR("%s failed, card busy\n", __func__); \ + return EOF; \ + } \ +} \ +while (0) + +#define SD_SETBLOCKLEN 0x50 + +static int16_t sd_setBlockLen(uint32_t newlen) +{ + SD_SELECT(); + + int16_t r1 = sd_sendCommand(SD_SETBLOCKLEN, newlen, 0); + + sd_select(false); + return r1; +} + +#define SD_SEND_CSD 0x49 + +static int16_t sd_getCSD(CardCSD *csd) +{ + SD_SELECT(); + + int16_t r1 = sd_sendCommand(SD_SEND_CSD, 0, 0); + + if (r1) + { + LOG_ERR("send_csd failed: %04X\n", r1); + sd_select(false); + return r1; + } + + uint8_t buf[16]; + bool res = sd_getBlock(buf, sizeof(buf)); + sd_select(false); + + if (res) + { + uint16_t mult = (1L << ((((buf[9] & 0x03) << 1) | ((buf[10] & 0x80) >> 7)) + 2)); + uint16_t c_size = (((uint16_t)(buf[6] & 0x03)) << 10) | (((uint16_t)buf[7]) << 2) | + (((uint16_t)(buf[8] & 0xC0)) >> 6); + + csd->block_len = (1L << (buf[5] & 0x0F)); + csd->block_num = (c_size + 1) * mult; + csd->capacity = (csd->block_len * csd->block_num) >> 20; // in MB + + LOG_INFO("block_len %d bytes, block_num %ld, total capacity %dMB\n", csd->block_len, csd->block_num, csd->capacity); + return 0; + } + else + return EOF; +} + + +#define SD_READ_SINGLEBLOCK 0x51 + +static int16_t sd_readBlock(void *buf, uint32_t addr) +{ + SD_SELECT(); + + int16_t r1 = sd_sendCommand(SD_READ_SINGLEBLOCK, addr, 0); + + if (r1) + { + LOG_ERR("read single block failed: %04X\n", r1); + sd_select(false); + return r1; + } + + bool res = sd_getBlock(buf, SD_DEFAULT_BLOCKLEN); + sd_select(false); + if (!res) + { + LOG_ERR("read single block failed reading data\n"); + return EOF; + } + else + return 0; +} + +#define SD_WRITE_SINGLEBLOCK 0x58 +#define SD_DATA_ACCEPTED 0x05 + +static int16_t sd_writeBlock(const void *buf, uint32_t addr) +{ + SD_SELECT(); + + int16_t r1 = sd_sendCommand(SD_WRITE_SINGLEBLOCK, addr, 0); + + if (r1) + { + LOG_ERR("write single block failed: %04X\n", r1); + sd_select(false); + return r1; + } + + kfile_putc(SD_STARTTOKEN, fd); + kfile_write(fd, buf, SD_DEFAULT_BLOCKLEN); + /* send fake crc */ + kfile_putc(0, fd); + kfile_putc(0, fd); + uint8_t dataresp = (kfile_getc(fd) & 0x1F); + sd_select(false); + + // FIXME: sometimes dataresp is 0, find out why. + if (dataresp != SD_DATA_ACCEPTED) + { + LOG_ERR("write single block failed: %02X\n", dataresp); + return EOF; + } + else + return 0; +} + + +bool sd_test(void) +{ + CardCSD csd; + sd_getCSD(&csd); + + uint8_t buf[SD_DEFAULT_BLOCKLEN]; + + if (sd_readBlock(buf, 0) != 0) + return false; + + kputchar('\n'); + for (int i = 0; i < SD_DEFAULT_BLOCKLEN; i++) + { + kprintf("%02X ", buf[i]); + buf[i] = i; + if (!((i+1) % 16)) + kputchar('\n'); + } + + if (sd_writeBlock(buf, 0) != 0) + return false; + + memset(buf, 0, sizeof(buf)); + if (sd_readBlock(buf, 0) != 0) + return false; + + kputchar('\n'); + for (int i = 0; i < SD_DEFAULT_BLOCKLEN; i++) + { + kprintf("%02X ", buf[i]); + buf[i] = i; + if (!((i+1) % 16)) + kputchar('\n'); + } + + return true; +} + +#define SD_GO_IDLE_STATE 0x40 +#define SD_GO_IDLE_STATE_CRC 0x95 +#define SD_SEND_OP_COND 0x41 +#define SD_SEND_OP_COND_CRC 0xF9 + +#define SD_START_DELAY ms_to_ticks(10) +#define SD_INIT_TIMEOUT ms_to_ticks(1000) +#define SD_IDLE_RETRIES 4 + +bool sd_init(KFile *_fd) +{ + uint16_t r1; + + ASSERT(_fd); + fd = _fd; + + SD_CS_INIT(); + SD_CS_OFF(); + + /* Wait a few moments for supply voltage to stabilize */ + timer_delay(SD_START_DELAY); + + /* Give 80 clk pulses to wake up the card */ + for (int i = 0; i < 10; i++) + kfile_putc(0xff, fd); + kfile_flush(fd); + + for (int i = 0; i < SD_IDLE_RETRIES; i++) + { + SD_SELECT(); + r1 = sd_sendCommand(SD_GO_IDLE_STATE, 0, SD_GO_IDLE_STATE_CRC); + sd_select(false); + + if (r1 == SD_IN_IDLE) + break; + } + + if (r1 != SD_IN_IDLE) + { + LOG_ERR("go_idle_state failed: %04X\n", r1); + return false; + } + + ticks_t start = timer_clock(); + + /* Wait for card to start */ + do + { + SD_SELECT(); + r1 = sd_sendCommand(SD_SEND_OP_COND, 0, SD_SEND_OP_COND_CRC); + sd_select(false); + cpu_relax(); + } + while (r1 != 0 && timer_clock() - start < SD_INIT_TIMEOUT); + + if (r1) + { + LOG_ERR("send_op_cond failed: %04X\n", r1); + return false; + } + + r1 = sd_setBlockLen(SD_DEFAULT_BLOCKLEN); + + if (r1) + { + LOG_ERR("setBlockLen failed: %04X\n", r1); + return false; + } + + sd_status = !r1; + return sd_status; +} + +DSTATUS sd_disk_initialize(BYTE drv) +{ + return sd_disk_status(drv); +} + +DSTATUS sd_disk_status(BYTE drv) +{ + ASSERT(!drv); + + if (sd_status) + return RES_OK; + else + return STA_NOINIT; +} + +DRESULT sd_disk_read(BYTE drv, BYTE* buf, DWORD sector, BYTE count) +{ + ASSERT(!drv); + + if (!sd_status) + return RES_NOTRDY; + + while (count--) + { + if (sd_readBlock(buf, sector * SD_DEFAULT_BLOCKLEN)) + return RES_ERROR; + buf += SD_DEFAULT_BLOCKLEN; + sector++; + } + return RES_OK; +} + +DRESULT sd_disk_write(BYTE drv, const BYTE* buf, DWORD sector, BYTE count) +{ + ASSERT(!drv); + + if (!sd_status) + return RES_NOTRDY; + + while (count--) + { + if (sd_writeBlock(buf, sector * SD_DEFAULT_BLOCKLEN)) + return RES_ERROR; + buf += SD_DEFAULT_BLOCKLEN; + sector++; + } + return RES_OK; +} + +DRESULT sd_disk_ioctl(BYTE drv, BYTE cmd, void* buf) +{ + ASSERT(!drv); + + if (!sd_status) + return RES_NOTRDY; + + switch (cmd) + { + case CTRL_SYNC: + return RES_OK; + + case GET_SECTOR_SIZE: + *(WORD *)buf = SD_DEFAULT_BLOCKLEN; + return RES_OK; + + case GET_SECTOR_COUNT: + { + CardCSD csd; + if (sd_getCSD(&csd)) + return RES_ERROR; + else + { + *(DWORD *)buf = csd.block_num; + return RES_OK; + } + + } + + case GET_BLOCK_SIZE: + *(DWORD *)buf = 1; + return RES_OK; + + default: + LOG_ERR("unknown command: [%d]\n", cmd); + return RES_PARERR; + } +} + +DWORD get_fattime(void) +{ + return 0; +} diff --git a/bertos/drv/sd.h b/bertos/drv/sd.h new file mode 100644 index 00000000..8be9c501 --- /dev/null +++ b/bertos/drv/sd.h @@ -0,0 +1,113 @@ +/** + * \file + * + * + * \brief Function library for secure digital memory. + * + * Right now, the interface for these function is the one defined in diskio.h from + * the FatFS module. + * + * \version $Id$ + * \author Francesco Sacchi + */ + + +#ifndef DRV_SD_H +#define DRV_SD_H + +#include +#include +#include + +bool sd_test(void); + +/** + * Initializes the SD driver. + * + * \param _fd A pointer to a kfile where the SD will read/write to. + * \return true if initialization succeds, false otherwise. + */ +bool sd_init(KFile *_fd); + +#if CONFIG_FAT_DRIVES == 1 + + /** + * Same as sd_disk_status. + * + * Card initialization must be done with sd_init. + */ + #define sd_disk_initialize disk_initialize + + /** + * Return the status of the disk. + * + * \param drv The number of the drive to initialize. Currently only drive 0 is allowed. + * \return RES_OK if the sd card was correctly initialized by a previous call to sd_init(), STA_NOINIT otherwise. + */ + #define sd_disk_status disk_status + /** + * Read \a count sectors from SD card. + * + * \param drv The drive number to read from. Only 0 is supported. + * \param buf A buffer to store read data. You can get sector size using sd_disk_ioctl. + * \param sector Start sector number. + * \param count The number of sectors to read. + * \return RES_OK if the function succeded, RES_ERROR if any error occurred, RES_NOTRDY if the disk is not initialized. + * + * \sa diskio.h + */ + #define sd_disk_read disk_read + + #if CONFIG_FAT_FS_READONLY == 0 + + /** + * Write \a count sectors to SD card. + * + * \param drv The drive number to read from. Only 0 is supported. + * \param buf The data to be written. + * \param sector Start sector number. + * \param count The number of sectors to write. + * \return RES_OK if the function succeded, RES_ERROR if any error occurred, RES_NOTRDY if the disk is not initialized. + * + * \sa diskio.h + */ + #define sd_disk_write disk_write + #endif + + /** + * Interface to send device independant commands to the device. + * + * \sa diskio.h and related documentation for further explanations. + */ + #define sd_disk_ioctl disk_ioctl + +#endif /* CONFIG_FAT_DRIVES == 1 */ + +#endif /* DRV_SD_H */ diff --git a/bertos/hw/hw_sd.h b/bertos/hw/hw_sd.h new file mode 100644 index 00000000..754b87ce --- /dev/null +++ b/bertos/hw/hw_sd.h @@ -0,0 +1,51 @@ +/** + * \file + * + * + * \brief SD driver hardware-specific definitions. + * + * \version $Id$ + * + * \author Luca Ottaviano + */ + +#ifndef HW_SD_H +#define HW_SD_H + +#define SD_CS_INIT() do { PIOA_PER = BV(11); PIOA_OER = BV(11); } while(0) +#define SD_CS_ON() do { PIOA_CODR = BV(11); } while(0) +#define SD_CS_OFF() do { PIOA_SODR = BV(11); } while(0) + +#define SD_PIN_INIT() do { PIOA_PER = BV(30) | BV(31); PIOA_PUER = BV(30) | BV(31); } while(0) +#define SD_CARD_PRESENT() (!(PIOA_PDSR & BV(31))) +#define SD_WRITE_PROTECT() ((PIOA_PDSR & BV(30))) + +#endif /* HW_SD_H */