Import SD card driver.
authorlottaviano <lottaviano@38d2e660-2303-0410-9eaa-f027e97ec537>
Fri, 3 Jul 2009 13:51:37 +0000 (13:51 +0000)
committerlottaviano <lottaviano@38d2e660-2303-0410-9eaa-f027e97ec537>
Fri, 3 Jul 2009 13:51:37 +0000 (13:51 +0000)
git-svn-id: https://src.develer.com/svnoss/bertos/trunk@2734 38d2e660-2303-0410-9eaa-f027e97ec537

bertos/drv/sd.c [new file with mode: 0644]
bertos/drv/sd.h [new file with mode: 0644]
bertos/hw/hw_sd.h [new file with mode: 0644]

diff --git a/bertos/drv/sd.c b/bertos/drv/sd.c
new file mode 100644 (file)
index 0000000..0c15156
--- /dev/null
@@ -0,0 +1,498 @@
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction.  Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License.  This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2007 Develer S.r.l. (http://www.develer.com/)
+ * -->
+ *
+ * \brief Function library for secure digital memory.
+ *
+ * \version $Id$
+ * \author Francesco Sacchi <batt@develer.com>
+ */
+
+
+#include "sd.h"
+#include "hw/hw_sd.h"
+#include <kern/kfile.h>
+#include <drv/timer.h>
+
+#include <fs/fat.h>
+
+#include <cfg/log.h>
+#include <cpu/power.h>
+
+#include <string.h> /* 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 (file)
index 0000000..8be9c50
--- /dev/null
@@ -0,0 +1,113 @@
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction.  Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License.  This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2007 Develer S.r.l. (http://www.develer.com/)
+ * -->
+ *
+ * \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 <batt@develer.com>
+ */
+
+
+#ifndef DRV_SD_H
+#define DRV_SD_H
+
+#include <fs/fatfs/diskio.h>
+#include <kern/kfile.h>
+#include <cfg/cfg_fat.h>
+
+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 (file)
index 0000000..754b87c
--- /dev/null
@@ -0,0 +1,51 @@
+/**
+ * \file
+ * <!--
+ * This file is part of BeRTOS.
+ *
+ * Bertos is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * As a special exception, you may use this file as part of a free software
+ * library without restriction.  Specifically, if other files instantiate
+ * templates or use macros or inline functions from this file, or you compile
+ * this file and link it with other files to produce an executable, this
+ * file does not by itself cause the resulting executable to be covered by
+ * the GNU General Public License.  This exception does not however
+ * invalidate any other reasons why the executable file might be covered by
+ * the GNU General Public License.
+ *
+ * Copyright 2009 Develer S.r.l. (http://www.develer.com/)
+ * All Rights Reserved.
+ * -->
+ *
+ * \brief SD driver hardware-specific definitions.
+ *
+ * \version $Id$
+ *
+ * \author Luca Ottaviano <lottaviano@develer.com>
+ */
+
+#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 */