]> cloudbase.mooo.com Git - z180-stamp.git/blobdiff - avr/mmc.c
Integrate fatfs. Add some sd card test commands.
[z180-stamp.git] / avr / mmc.c
diff --git a/avr/mmc.c b/avr/mmc.c
new file mode 100644 (file)
index 0000000..eb10ea6
--- /dev/null
+++ b/avr/mmc.c
@@ -0,0 +1,637 @@
+/*-----------------------------------------------------------------------*/
+/* MMCv3/SDv1/SDv2 (in SPI mode) control module  (C)ChaN, 2007             */
+/*-----------------------------------------------------------------------*/
+/* Only spi_rcvr(), spi_xmit(), disk_timerproc() and some macros         */
+/* are platform dependent.                                               */
+/*-----------------------------------------------------------------------*/
+
+#include <avr/io.h>
+#include <stdbool.h>
+#include "timer.h"
+#include "spi.h"
+#include "diskio.h"
+#include "debug.h"
+#include "print-utils.h"
+
+
+/* Definitions for MMC/SDC command */
+#define CMD0   (0)                     /* GO_IDLE_STATE */
+#define CMD1   (1)                     /* SEND_OP_COND (MMC) */
+#define        ACMD41  (0x80+41)       /* SEND_OP_COND (SDC) */
+#define CMD8   (8)                     /* SEND_IF_COND */
+#define CMD9   (9)                     /* SEND_CSD */
+#define CMD10  (10)            /* SEND_CID */
+#define CMD12  (12)            /* STOP_TRANSMISSION */
+#define ACMD13 (0x80+13)       /* SD_STATUS (SDC) */
+#define CMD16  (16)            /* SET_BLOCKLEN */
+#define CMD17  (17)            /* READ_SINGLE_BLOCK */
+#define CMD18  (18)            /* READ_MULTIPLE_BLOCK */
+#define CMD23  (23)            /* SET_BLOCK_COUNT (MMC) */
+#define        ACMD23  (0x80+23)       /* SET_WR_BLK_ERASE_COUNT (SDC) */
+#define CMD24  (24)            /* WRITE_BLOCK */
+#define CMD25  (25)            /* WRITE_MULTIPLE_BLOCK */
+#define CMD55  (55)            /* APP_CMD */
+#define CMD58  (58)            /* READ_OCR */
+
+
+/* SD card socket connections */
+#if 0
+//#define MMC_PORT             PORTG           /* Socket contact port */
+//#define MMC_INPORT           PING
+//#define SD_WP_PIN            5                       /* Write protect switch */
+#endif
+
+/* SD card SPI access */
+#define SD_CD_PORT             PORTG
+#define SD_CD_DDR              DDRG
+#define SD_CD_INPORT           PING
+#define SD_CD_PIN              3                       /* Card detect switch */
+
+#define SD_WP_PORT             PORTG
+#define SD_WP_DDR              DDRG
+#define SD_WP_INPORT           PING
+//#define SD_WP_PIN            5                       /* Write protect switch */
+
+#define SD_CS_PORT             PORTG
+#define SD_CS_DDR              DDRG
+#define SD_CS_PIN              4                       /* Chip select pin */
+
+
+/* Port Controls  (Platform dependent) */
+
+#ifdef SD_CD_PIN
+static inline
+bool sd_cd(void)
+{
+       return (SD_CD_INPORT & _BV(SD_CD_PIN)) == 0;
+}
+#endif
+
+#ifdef SD_WP_PIN
+static inline
+bool sd_wp(void)
+{
+       return (SD_WP_INPORT & _BV(SD_WP_PIN)) == 0;
+}
+#endif
+
+#define CS_LOW()       SD_CS_PORT &= ~(1<<SD_CS_PIN)   /* MMC CS = L */
+#define CS_HIGH()      SD_CS_PORT |=  (1<<SD_CS_PIN)   /* MMC CS = H */
+
+#define        FCLK_SLOW()     SPISetMMCInitClock() /* Set slow clock (100k-400k) */
+#define        FCLK_FAST()     SPISetFastClock()        /* Set fast clock (depends on the CSD) */
+
+/*--------------------------------------------------------------------------
+
+ Module Private Functions
+
+ ---------------------------------------------------------------------------*/
+
+static volatile
+DSTATUS disk_stat = STA_NOINIT;        /* Disk status */
+
+static
+BYTE CardType;                         /* Card type flags */
+
+/*-----------------------------------------------------------------------*/
+/* Wait for card ready                                                   */
+/*-----------------------------------------------------------------------*/
+
+static
+int wait_ready (void)  /* 1:OK, 0:Timeout */
+{
+       uint32_t to = get_timer(0);
+
+       /* Wait for ready in timeout of 500ms */
+       do {
+               if (spi_rcvr() == 0xFF) {
+                       return 1;
+               }
+       } while (get_timer(to) < 500);
+
+       return 0;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Deselect the card and release SPI bus                                 */
+/*-----------------------------------------------------------------------*/
+
+static
+void deselect (void)
+{
+//     debug("*** enter deselect()\n");
+       CS_HIGH();
+       /* Dummy clock (TODO: force DO hi-z for multiple slave SPI) */
+       spi_rcvr();
+//     debug("***  exit deselect()\n");
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Select the card and wait for ready                                    */
+/*-----------------------------------------------------------------------*/
+
+static
+int select (void)      /* 1:Successful, 0:Timeout */
+{
+//     debug("*** enter select()\n");
+       CS_LOW();
+       /* Dummy clock (force DO enabled) */
+       spi_rcvr();
+
+       if (wait_ready()) {
+//             debug("***  exit select() == 1\n");
+               return 1;       /* OK */
+       }
+       deselect();
+//     debug("***  exit select() == 0\n");
+
+       return 0;       /* Timeout */
+}
+
+/*-----------------------------------------------------------------------*/
+/* Power Control  (Platform dependent)                                   */
+/*-----------------------------------------------------------------------*/
+/* When the target system does not support socket power control, there   */
+/* is nothing to do in these functions and chk_power always returns 1.   */
+
+static
+void power_on(void)
+{
+//     debug("*** enter power_on()\n");
+
+#ifdef SD_PWR_PIN
+       SD_PWR_DDR |= _BV(SD_PWR_PIN); // Turns on PWR pin as output
+       SD_PWR_PORT &= ~_BV(SD_PWR_PIN); // Drives PWR pin high
+
+       for (uint32_t to = get_timer(0); get_timer(to) < 30;)
+               ; /* Wait for 30ms */
+#endif
+
+#ifdef SD_CD_PIN
+       /* Card detect, input with pullup */
+       SD_CD_DDR &= ~_BV(SD_CD_PIN);
+       SD_CD_PORT |= _BV(SD_CD_PIN);
+#endif
+#ifdef SD_WP_PIN
+       SD_WP_DDR &= ~_BV(SD_WP_PIN);
+       SD_WP_PORT |= _BV(SD_WP_PIN);
+#endif
+       SD_CS_PORT |= _BV(SD_CS_PIN);
+       SD_CS_DDR |= _BV(SD_CS_PIN); // Turns on CS pin as output
+
+//     debug("***  exit power_on()\n");
+}
+
+static
+void power_off (void)
+{
+//     debug("*** enter power_off()\n");
+       select();                               /* Wait for card ready */
+       deselect();
+
+#ifdef SD_PWR_PIN
+       SD_PWR_PORT |= (1 << SD_PWR_PIN); /* Socket power OFF */
+#endif
+       disk_stat |= STA_NOINIT; /* Set STA_NOINIT */
+//     debug("***  exit power_off()\n");
+}
+
+#if 0
+static
+int chk_power(void) /* Socket power state: 0=off, 1=on */
+{
+#ifdef SD_PWR_PIN
+       return (SD_PWR_PORT & (1 << SD_PWR_PIN)) ? 0 : 1;
+#else
+       return 1;
+#endif /* SD_PWR_PIN */
+}
+#endif
+
+/*-----------------------------------------------------------------------*/
+/* Receive a data packet from MMC                                        */
+/*-----------------------------------------------------------------------*/
+
+static
+int rcvr_datablock (
+       BYTE *buff,                     /* Data buffer to store received data */
+UINT btr /* Byte count (must be multiple of 4) */
+) {
+       BYTE token, tmp;
+       uint32_t to = get_timer(0);
+
+       /* Wait for data packet in timeout of 200ms */
+       do {
+               token = spi_rcvr();
+       } while ((token == 0xFF) && get_timer(to) < 200);
+       if(token != 0xFE) return 0;             /* If not valid data token, retutn with error */
+
+       tmp = spi_rcvr(); /* shift in first byte */
+       spi_write(0xff); /* start shift in next byte */
+       while (--btr) {
+               *buff++ = tmp;
+               asm volatile (""::"r"(buff), "r"(btr));
+               spi_wait();
+               tmp = SPDR;
+               spi_write(0xff);
+       }
+       *buff = tmp; /* store last byte in buffer while SPI module shifts in crc part1 */
+       spi_wait();
+       spi_rcvr(); /* second crc */
+
+       return 1; /* Return with success */
+}
+
+/*-----------------------------------------------------------------------*/
+/* Send a data packet to MMC                                             */
+/*-----------------------------------------------------------------------*/
+
+#if    _USE_WRITE
+static
+int xmit_datablock (
+               const BYTE *buff, /* 512 byte data block to be transmitted */
+               BYTE token /* Data/Stop token */
+)
+{
+       BYTE resp, tmp;
+       UINT btr;
+
+       if (!wait_ready()) return 0;
+
+       spi_write(token); /* Xmit data token */
+       if (token != 0xFD) { /* Is data token */
+               btr = 512;
+               do {
+                       tmp = *buff++;
+                       spi_wait();
+                       spi_write(tmp);
+               }while (--btr);
+               spi_wait();
+               spi_xmit(0xff); /* CRC (Dummy) */
+               spi_xmit(0xff);
+               resp = spi_rcvr(); /* Reveive data response */
+               return ((resp & 0x1F) != 0x05) ? 0 : 1; /* If not accepted, return with error */
+       }
+
+       spi_wait();
+       return 1;
+}
+#endif /* _USE_WRITE */
+
+/*-----------------------------------------------------------------------*/
+/* Send a command packet to MMC                                          */
+/*-----------------------------------------------------------------------*/
+
+static
+BYTE send_cmd (                /* Returns R1 resp (bit7==1:Send failed) */
+       BYTE cmd,               /* Command index */
+                                        DWORD arg /* Argument */
+) {
+       union {
+               DWORD as32;
+               BYTE as8[4];
+       } argtmp;
+       BYTE n, res;
+
+//     debug("*** send_cmd( %.2x )\n", cmd);
+
+       if (cmd & 0x80) { /* ACMD<n> is the command sequense of CMD55-CMD<n> */
+               cmd &= 0x7F;
+               res = send_cmd(CMD55, 0);
+               if (res > 1)
+                       return res;
+       }
+
+       /* Select the card and wait for ready except to stop multiple block read */
+       if (cmd != CMD12) {
+               deselect();
+               if (!select())
+                       return 0xFF;
+       }
+
+       /* Send command packet */
+       spi_xmit(0x40 | cmd); /* Start + Command index */
+       argtmp.as32 = arg;
+       spi_xmit(argtmp.as8[3]); /* Argument[31..24] */
+       spi_xmit(argtmp.as8[2]); /* Argument[23..16] */
+       spi_xmit(argtmp.as8[1]); /* Argument[15..8] */
+       spi_xmit(argtmp.as8[0]); /* Argument[7..0] */
+
+       n = 0x01; /* Dummy CRC + Stop */
+       if (cmd == CMD0)
+               n = 0x95; /* Valid CRC for CMD0(0) */
+       if (cmd == CMD8)
+               n = 0x87; /* Valid CRC for CMD8(0x1AA) */
+       spi_xmit(n);
+
+       /* Receive command response */
+       if (cmd == CMD12)
+               spi_rcvr(); /* Skip a stuff byte when stop reading */
+       n = 10; /* Wait for a valid response in timeout of 10 attempts */
+       do
+               res = spi_rcvr();
+       while ((res & 0x80) && --n);
+
+       return res; /* Return with the response value */
+}
+
+/*--------------------------------------------------------------------------
+
+ Public Functions
+
+ ---------------------------------------------------------------------------*/
+
+/*-----------------------------------------------------------------------*/
+/* Initialize Disk Drive                                                 */
+/*-----------------------------------------------------------------------*/
+
+#define MMC_INIT_TO 1000       /* 1s */
+
+DSTATUS disk_initialize (
+       BYTE drv                /* Physical drive nmuber (0) */
+)
+{
+       BYTE n, cmd, ty, ocr[4];
+
+       if (drv)
+               return STA_NOINIT; /* Supports only single drive */
+       if (disk_stat & STA_NODISK)
+               return disk_stat; /* No card in the socket */
+
+       power_on(); /* Force socket power on */
+       FCLK_SLOW();
+       for (n = 10; n; n--)
+               spi_rcvr(); /* 80 dummy clocks */
+
+       ty = 0;
+       if (send_cmd(CMD0, 0) == 1) { /* Enter Idle state */
+               /* Init timeout timer */
+               uint32_t timer = get_timer(0);
+
+               if (send_cmd(CMD8, 0x1AA) == 1) {       /* SDv2? */
+                       for (n = 0; n < 4; n++) ocr[n] = spi_rcvr();            /* Get trailing return value of R7 resp */
+                       if (ocr[2] == 0x01 && ocr[3] == 0xAA) { /* The card can work at vdd range of 2.7-3.6V */
+                               while (get_timer(timer) < MMC_INIT_TO && send_cmd(ACMD41, 1UL << 30));  /* Wait for leaving idle state (ACMD41 with HCS bit) */
+                               if (get_timer(timer) < MMC_INIT_TO && send_cmd(CMD58, 0) == 0) {                /* Check CCS bit in the OCR */
+                                       for (n = 0; n < 4; n++) ocr[n] = spi_rcvr();
+                                       ty = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;      /* SDv2 */
+                               }
+                       }
+               } else {                                                        /* SDv1 or MMCv3 */
+                       if (send_cmd(ACMD41, 0) <= 1) {
+                               ty = CT_SD1; cmd = ACMD41;      /* SDv1 */
+                       } else {
+                               ty = CT_MMC; cmd = CMD1;        /* MMCv3 */
+                       }
+
+                       /* Wait for leaving idle state */
+                       while (get_timer(timer) < MMC_INIT_TO && send_cmd(cmd, 0));
+
+                       /* Set R/W block length to 512 */
+                       if (!(get_timer(timer) < MMC_INIT_TO) || send_cmd(CMD16, 512) != 0)
+                               ty = 0;
+               }
+       }
+       CardType = ty;
+       deselect();
+
+       if (ty) { /* Initialization succeded */
+               disk_stat &= ~STA_NOINIT; /* Clear STA_NOINIT */
+               FCLK_FAST();
+       } else { /* Initialization failed */
+               power_off();
+       }
+
+       return disk_stat;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Get Disk Status                                                       */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_status (
+       BYTE drv                /* Physical drive nmuber (0) */
+)
+{
+       if (drv) return STA_NOINIT;             /* Supports only single drive */
+       return disk_stat;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Read Sector(s)                                                        */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_read (
+       BYTE drv,                       /* Physical drive nmuber (0) */
+       BYTE *buff, /* Pointer to the data buffer to store read data */
+       DWORD sector, /* Start sector number (LBA) */
+       UINT count /* Sector count (1..255) */
+)
+{
+       BYTE cmd;
+
+       if (drv || !count) return RES_PARERR;
+       if (disk_stat & STA_NOINIT) return RES_NOTRDY;
+
+       if (!(CardType & CT_BLOCK)) sector *= 512;      /* Convert to byte address if needed */
+
+       cmd = count > 1 ? CMD18 : CMD17;                        /*  READ_MULTIPLE_BLOCK : READ_SINGLE_BLOCK */
+       if (send_cmd(cmd, sector) == 0) {
+               do {
+                       if (!rcvr_datablock(buff, 512))
+                               break;
+                       buff += 512;
+               } while (--count);
+               if (cmd == CMD18)
+                       send_cmd(CMD12, 0);     /* STOP_TRANSMISSION */
+       }
+       deselect();
+
+       return count ? RES_ERROR : RES_OK;
+}
+
+/*-----------------------------------------------------------------------*/
+/* Write Sector(s)                                                       */
+/*-----------------------------------------------------------------------*/
+
+#if _USE_WRITE
+DRESULT disk_write (
+               BYTE drv, /* Physical drive nmuber (0) */
+               const BYTE *buff, /* Pointer to the data to be written */
+               DWORD sector, /* Start sector number (LBA) */
+               UINT count /* Sector count (1..255) */
+)
+{
+       if (drv || !count) return RES_PARERR;
+       if (disk_stat & STA_NOINIT) return RES_NOTRDY;
+       if (disk_stat & STA_PROTECT) return RES_WRPRT;
+
+       if (!(CardType & CT_BLOCK)) sector *= 512; /* Convert to byte address if needed */
+
+       if (count == 1) { /* Single block write */
+               if ((send_cmd(CMD24, sector) == 0) /* WRITE_BLOCK */
+                               && xmit_datablock(buff, 0xFE))
+               count = 0;
+       }
+       else { /* Multiple block write */
+               if (CardType & CT_SDC) send_cmd(ACMD23, count);
+               if (send_cmd(CMD25, sector) == 0) { /* WRITE_MULTIPLE_BLOCK */
+                       do {
+                               if (!xmit_datablock(buff, 0xFC)) break;
+                               buff += 512;
+                       }while (--count);
+                       if (!xmit_datablock(0, 0xFD)) /* STOP_TRAN token */
+                       count = 1;
+               }
+       }
+       deselect();
+
+       return count ? RES_ERROR : RES_OK;
+}
+#endif /* _USE_WRITE */
+
+/*-----------------------------------------------------------------------*/
+/* Miscellaneous Functions                                               */
+/*-----------------------------------------------------------------------*/
+
+#if _USE_IOCTL
+DRESULT disk_ioctl (
+       BYTE drv,               /* Physical drive nmuber (0) */
+       BYTE cmd,               /* Control code */
+       void *buff              /* Buffer to send/receive control data */
+)
+{
+       DRESULT res;
+       BYTE n, csd[16], *ptr = buff;
+       DWORD csize;
+
+       if (drv)
+               return RES_PARERR;
+
+       res = RES_ERROR;
+
+       if (disk_stat & STA_NOINIT) return RES_NOTRDY;
+
+       switch (cmd) {
+       case CTRL_SYNC :                /* Make sure that no pending write process. Do not remove this or written sector might not left updated. */
+               if (select())
+                       res = RES_OK;
+               break;
+
+       case GET_SECTOR_COUNT: /* Get number of sectors on the disk (DWORD) */
+               if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) {
+                       if ((csd[0] >> 6) == 1) {       /* SDC ver 2.00 */
+                               csize = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;
+                               *(DWORD*)buff = csize << 10;
+                       } else {                                        /* SDC ver 1.XX or MMC*/
+                               n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
+                               csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
+                               *(DWORD*)buff = csize << (n - 9);
+                       }
+                       res = RES_OK;
+               }
+               break;
+
+       case GET_BLOCK_SIZE:    /* Get erase block size in unit of sector (DWORD) */
+               if (CardType & CT_SD2) {        /* SDv2? */
+                       if (send_cmd(ACMD13, 0) == 0) { /* Read SD status */
+                               spi_rcvr();
+                               if (rcvr_datablock(csd, 16)) { /* Read partial block */
+                                       for (n = 64 - 16; n; n--)
+                                               spi_rcvr(); /* Purge trailing data */
+                                       *(DWORD*) buff = 16UL << (csd[10] >> 4);
+                                       res = RES_OK;
+                               }
+                       }
+               } else {                                        /* SDv1 or MMCv3 */
+                       if ((send_cmd(CMD9, 0) == 0) && rcvr_datablock(csd, 16)) { /* Read CSD */
+                               if (CardType & CT_SD1) {        /* SDv1 */
+                                       *(DWORD*)buff = (((csd[10] & 63) << 1) + ((WORD)(csd[11] & 128) >> 7) + 1) << ((csd[13] >> 6) - 1);
+                               } else {                                        /* MMCv3 */
+                                       *(DWORD*)buff = ((WORD)((csd[10] & 124) >> 2) + 1) * (((csd[11] & 3) << 3) + ((csd[11] & 224) >> 5) + 1);
+                               }
+                               res = RES_OK;
+                       }
+               }
+               break;
+
+       /* Following commands are never used by FatFs module */
+
+       case MMC_GET_TYPE:              /* Get card type flags (1 byte) */
+               *ptr = CardType;
+               res = RES_OK;
+               break;
+
+       case MMC_GET_CSD: /* Receive CSD as a data block (16 bytes) */
+               if (send_cmd(CMD9, 0) == 0 /* READ_CSD */
+               && rcvr_datablock(ptr, 16))
+                       res = RES_OK;
+               break;
+
+       case MMC_GET_CID: /* Receive CID as a data block (16 bytes) */
+               if (send_cmd(CMD10, 0) == 0 /* READ_CID */
+               && rcvr_datablock(ptr, 16))
+                       res = RES_OK;
+               break;
+
+       case MMC_GET_OCR: /* Receive OCR as an R3 resp (4 bytes) */
+               if (send_cmd(CMD58, 0) == 0) { /* READ_OCR */
+                       for (n = 4; n; n--)
+                               *ptr++ = spi_rcvr();
+                       res = RES_OK;
+               }
+               break;
+
+       case MMC_GET_SDSTAT: /* Receive SD status as a data block (64 bytes) */
+               if (send_cmd(ACMD13, 0) == 0) { /* SD_STATUS */
+                       spi_rcvr();
+                       if (rcvr_datablock(ptr, 64))
+                               res = RES_OK;
+               }
+               break;
+
+       case CTRL_POWER_OFF :   /* Power off */
+               power_off();
+               disk_stat |= STA_NOINIT;
+               res = RES_OK;
+               break;
+
+       default:
+               res = RES_PARERR;
+       }
+
+       deselect();
+
+       return res;
+}
+#endif /* _USE_IOCTL */
+
+/*-----------------------------------------------------------------------*/
+/* Device Timer Interrupt Procedure  (Platform dependent)                */
+/*-----------------------------------------------------------------------*/
+/* This function must be called in period of 10ms                        */
+
+void disk_timerproc (void)
+{
+       BYTE s;
+
+       s = disk_stat;
+
+#ifdef SD_WP_PIN
+       if (sd_wp())                            /* Write protected */
+               s |= STA_PROTECT;
+       else                                    /* Write enabled */
+               s &= ~STA_PROTECT;
+#endif
+
+#ifdef SD_CD_PIN
+       if (sd_cd())                            /* Card inserted */
+               s &= ~STA_NODISK;
+       else                                    /* Socket empty */
+               s |= (STA_NODISK | STA_NOINIT);
+#endif
+
+       disk_stat = s;                          /* Update MMC status */
+}