software/liblitesdcard: base it on FatFs generic example code + LiteX's SPIMaster...
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 5 Jun 2020 14:27:38 +0000 (16:27 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 5 Jun 2020 14:27:38 +0000 (16:27 +0200)
litex/soc/software/liblitesdcard/spisdcard.c

index 3049037c16915d7ebb185fe3d4dd25ad1c93ac66..86095177d0df6b91a639342fcdfae7be09188855 100644 (file)
@@ -1,8 +1,20 @@
-// This file is Copyright (c) 2020 Rob Shelton <rob.s.ng15@googlemail.com>
 // This file is Copyright (c) 2020 Florent Kermarrec <florent@enjoy-digital.fr>
+// This file is Copyright (c) 2020 Rob Shelton <rob.s.ng15@googlemail.com>
 // License: BSD
-//
-// SDCard SPI-Mode support for LiteX's SPIMaster.
+// FatFs's generic example adapted for LiteX's SPIMaster.
+
+/*------------------------------------------------------------------------/
+/  Foolproof MMCv3/SDv1/SDv2 (in SPI mode) control module
+/-------------------------------------------------------------------------/
+/
+/  Copyright (C) 2019, ChaN, all right reserved.
+/
+/ * This software is a free software and there is NO WARRANTY.
+/ * No restriction on use. You can use, modify and redistribute it for
+/   personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY.
+/ * Redistributions of source code must retain the above copyright notice.
+/
+/-------------------------------------------------------------------------*/
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -12,6 +24,8 @@
 #include <generated/mem.h>
 #include <system.h>
 
+#include "ff.h"
+#include "diskio.h"
 #include "spisdcard.h"
 
 #ifdef CSR_SPISDCARD_BASE
@@ -53,193 +67,467 @@ static uint8_t spi_xfer(uint8_t byte) {
     return spisdcard_miso_read();
 }
 
-/* SPI SDCard functions */
+/*--------------------------------------------------------------------------
+
+   Module Private Functions
+
+---------------------------------------------------------------------------*/
+
+/* MMC/SD command (SPI mode) */
+#define CMD0    (0)         /* GO_IDLE_STATE */
+#define CMD1    (1)         /* SEND_OP_COND */
+#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 CMD13   (13)        /* SEND_STATUS */
+#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 */
+#define ACMD23  (0x80+23)   /* SET_WR_BLK_ERASE_COUNT (SDC) */
+#define CMD24   (24)        /* WRITE_BLOCK */
+#define CMD25   (25)        /* WRITE_MULTIPLE_BLOCK */
+#define CMD32   (32)        /* ERASE_ER_BLK_START */
+#define CMD33   (33)        /* ERASE_ER_BLK_END */
+#define CMD38   (38)        /* ERASE */
+#define CMD55   (55)        /* APP_CMD */
+#define CMD58   (58)        /* READ_OCR */
+
+static
+DSTATUS Stat = STA_NOINIT;  /* Disk status */
+
+static
+BYTE CardType;          /* b0:MMC, b1:SDv1, b2:SDv2, b3:Block addressing */
+
+/*-----------------------------------------------------------------------*/
+/* Transmit bytes to the card                                            */
+/*-----------------------------------------------------------------------*/
+
+static
+void xmit_mmc (
+    const BYTE* buff,   /* Data to be sent */
+    UINT bc             /* Number of bytes to send */
+)
+{
+    BYTE d;
 
-static uint8_t spisdcard_wait_response(void) {
-    uint8_t timeout;
-    uint8_t response;
 
-    timeout  = 32;
-    /* Do SPI Xfers on SDCard until MISO MSB's is 0 (valid response) or timeout is expired */
     do {
-        response = spi_xfer(0xff);
-        timeout--;
-    } while(((response & 0x80) !=0) && timeout > 0);
-    return response;
+        d = *buff++;    /* Get a byte to be sent */
+        spi_xfer(d);
+    } while (--bc);
 }
 
-static uint8_t spisdcard_set_mode(void) {
-    uint8_t timeout;
-    uint8_t response;
 
-    timeout = 32;
+
+/*-----------------------------------------------------------------------*/
+/* Receive bytes from the card                                           */
+/*-----------------------------------------------------------------------*/
+
+static
+void rcvr_mmc (
+    BYTE *buff, /* Pointer to read buffer */
+    UINT bc     /* Number of bytes to receive */
+)
+{
+    BYTE r;
+
     do {
-        int i;
-        /* Set CS and send 80 clock pulses to set the SDCard in SPI Mode */
-        spisdcard_cs_write(SPI_CS_HIGH);
-        for (i=0; i<10; i++)
-            spi_xfer(0xff);
-        /* Clear CS and read response, if 0 the SDCard has been initialized to SPI Mode */
-        spisdcard_cs_write(SPI_CS_LOW);
-        response = spisdcard_wait_response();
+        r = spi_xfer(0xff);
+        *buff++ = r;            /* Store a received byte */
+    } while (--bc);
+}
 
-        timeout--;
-    } while ((timeout > 0) && (response == 0));
 
-    if(timeout == 0)
-        return 0;
 
-    return 1;
+/*-----------------------------------------------------------------------*/
+/* Wait for card ready                                                   */
+/*-----------------------------------------------------------------------*/
+
+static
+int wait_ready (void)   /* 1:OK, 0:Timeout */
+{
+    BYTE d;
+    UINT tmr;
+
+
+    for (tmr = 5000; tmr; tmr--) {  /* Wait for ready in timeout of 500ms */
+        rcvr_mmc(&d, 1);
+        if (d == 0xFF) break;
+    }
+
+    return tmr ? 1 : 0;
 }
 
-uint8_t spisdcard_init(void) {
-    uint8_t i;
-    uint8_t r;
-    uint8_t timeout;
 
-    /* Set SPI clk freq to 400KHz */
-    spi_set_clk_freq(400000);
 
-    /* Set SDCard in SPI Mode */
-    r = spisdcard_set_mode();
-    if(r != 0x01)
-        return 0;
-
-    /* Send SD CARD IDLE */
-    /* CMD0 */
-    spi_xfer(0xff);
-    spi_xfer(0x40);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x95);
-    /* R1 response, expects 0x1 */
-    r = spisdcard_wait_response();
-    if(r != 0x01)
-        return 0;
-
-    /* Send Check SD CARD type */
-    /* CMD8 */
-    spi_xfer(0xff);
-    spi_xfer(0x48);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x01);
-    spi_xfer(0xaa);
-    spi_xfer(0x87);
-    /* R7, expects 0x1 */
-    r = spisdcard_wait_response();
-    if(r != 0x01)
-        return 0;
-    /* Reveice the 4 trailing bytes */
-    for(i=0; i<4; i++)
-        r = spi_xfer(0xff); /* FIXME: add check? */
-
-    /* Send Force SD CARD READY (CMD55 + ACMD41), expects 0x00 R1 response */
-    timeout = 32;
-    do {
-        /* CMD55 */
-        spi_xfer(0xff);
-        spi_xfer(0x77);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        r = spisdcard_wait_response();
-        /* ACMD41 */
-        spi_xfer(0xff);
-        spi_xfer(0x69);
-        spi_xfer(0x40);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        spi_xfer(0x00);
-        /* R1 */
-        r = spisdcard_wait_response();
-        timeout--;
-        /* 20ms delay */
-        busy_wait(20);
-    } while ((r != 0x00) && (timeout > 0));
-    if(r != 0x00)
-        return 0;
-
-    /* Send Read SD CARD OCR (status register) */
-    /* CMD58 */
-    spi_xfer(0xff);
-    spi_xfer(0x7a);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0xff);
-    /* R3, expects 0x1 */
-    r = spisdcard_wait_response();
-    if(r > 0x01)
-        return 0;
-    /* Reveice the 4 trailing bytes */
-    for(i=0; i<4; i++)
-        r = spi_xfer(0xff); /* FIXME: add check? */
-
-    /* Send Set SD CARD block size */
-    /* CMD16 */
-    spi_xfer(0xff);
-    spi_xfer(0x50);
-    spi_xfer(0x00);
-    spi_xfer(0x00);
-    spi_xfer(0x02);
-    spi_xfer(0x00);
-    spi_xfer(0xff);
-    /* RI, expects 0x00 */
-    r = spisdcard_wait_response();
-    if(r != 0x00)
-        return 0;
-
-    /* Set SPI clk freq to 16MHz */
-    spi_set_clk_freq(16000000);
+/*-----------------------------------------------------------------------*/
+/* Deselect the card and release SPI bus                                 */
+/*-----------------------------------------------------------------------*/
+
+static
+void deselect (void)
+{
+    BYTE d;
+
+    spisdcard_cs_write(SPI_CS_HIGH); /* Set CS# high */
+    rcvr_mmc(&d, 1);    /* Dummy clock (force DO hi-z for multiple slave SPI) */
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Select the card and wait for ready                                    */
+/*-----------------------------------------------------------------------*/
+
+static
+int select (void)   /* 1:OK, 0:Timeout */
+{
+    BYTE d;
+
+    spisdcard_cs_write(SPI_CS_LOW); /* Set CS# high */
+    rcvr_mmc(&d, 1);    /* Dummy clock (force DO enabled) */
+    if (wait_ready()) return 1; /* Wait for card ready */
+
+    deselect();
+    return 0;           /* Failed */
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Receive a data packet from the card                                   */
+/*-----------------------------------------------------------------------*/
+
+static
+int rcvr_datablock (    /* 1:OK, 0:Failed */
+    BYTE *buff,         /* Data buffer to store received data */
+    UINT btr            /* Byte count */
+)
+{
+    BYTE d[2];
+    UINT tmr;
+
+
+    for (tmr = 1000; tmr; tmr--) {  /* Wait for data packet in timeout of 100ms */
+        rcvr_mmc(d, 1);
+        if (d[0] != 0xFF) break;
+    }
+    if (d[0] != 0xFE) return 0;     /* If not valid data token, return with error */
+
+    rcvr_mmc(buff, btr);            /* Receive the data block into buffer */
+    rcvr_mmc(d, 2);                 /* Discard CRC */
+
+    return 1;                       /* Return with success */
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Send a data packet to the card                                        */
+/*-----------------------------------------------------------------------*/
+
+static
+int xmit_datablock (    /* 1:OK, 0:Failed */
+    const BYTE *buff,   /* 512 byte data block to be transmitted */
+    BYTE token          /* Data/Stop token */
+)
+{
+    BYTE d[2];
+
+
+    if (!wait_ready()) return 0;
+
+    d[0] = token;
+    xmit_mmc(d, 1);             /* Xmit a token */
+    if (token != 0xFD) {        /* Is it data token? */
+        xmit_mmc(buff, 512);    /* Xmit the 512 byte data block to MMC */
+        rcvr_mmc(d, 2);         /* Xmit dummy CRC (0xFF,0xFF) */
+        rcvr_mmc(d, 1);         /* Receive data response */
+        if ((d[0] & 0x1F) != 0x05)  /* If not accepted, return with error */
+            return 0;
+    }
 
     return 1;
 }
 
-uint8_t spisdcard_read_block(uint32_t addr, uint8_t *buf) {
-    int i;
-    uint32_t timeout;
+
+
+/*-----------------------------------------------------------------------*/
+/* Send a command packet to the card                                     */
+/*-----------------------------------------------------------------------*/
+
+static
+BYTE send_cmd (     /* Returns command response (bit7==1:Send failed)*/
+    BYTE cmd,       /* Command byte */
+    DWORD arg       /* Argument */
+)
+{
+    BYTE n, d, buf[6];
+
+
+    if (cmd & 0x80) {   /* ACMD<n> is the command sequense of CMD55-CMD<n> */
+        cmd &= 0x7F;
+        n = send_cmd(CMD55, 0);
+        if (n > 1) return n;
+    }
+
+    /* Select the card and wait for ready except to stop multiple block read */
+    if (cmd != CMD12) {
+        deselect();
+        if (!select()) return 0xFF;
+    }
+
+    /* Send a command packet */
+    buf[0] = 0x40 | cmd;            /* Start + Command index */
+    buf[1] = (BYTE)(arg >> 24);     /* Argument[31..24] */
+    buf[2] = (BYTE)(arg >> 16);     /* Argument[23..16] */
+    buf[3] = (BYTE)(arg >> 8);      /* Argument[15..8] */
+    buf[4] = (BYTE)arg;             /* 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)) */
+    buf[5] = n;
+    xmit_mmc(buf, 6);
+
+    /* Receive command response */
+    if (cmd == CMD12) rcvr_mmc(&d, 1);  /* Skip a stuff byte when stop reading */
+    n = 10;                             /* Wait for a valid response in timeout of 10 attempts */
+    do
+        rcvr_mmc(&d, 1);
+    while ((d & 0x80) && --n);
+
+    return d;           /* Return with the response value */
+}
+
+
+
+/*--------------------------------------------------------------------------
+
+   Public Functions
+
+---------------------------------------------------------------------------*/
+
+
+/*-----------------------------------------------------------------------*/
+/* Get Disk Status                                                       */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_status (
+    BYTE drv            /* Drive number (always 0) */
+)
+{
+    if (drv) return STA_NOINIT;
+
+    return Stat;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Initialize Disk Drive                                                 */
+/*-----------------------------------------------------------------------*/
+
+DSTATUS disk_initialize (
+    BYTE drv        /* Physical drive nmuber (0) */
+)
+{
+    BYTE n, ty, cmd, buf[4];
+    UINT tmr;
+    DSTATUS s;
+
+
+    if (drv) return RES_NOTRDY;
+
+    busy_wait(10);  /* 10ms */
+
+    for (n = 10; n; n--) rcvr_mmc(buf, 1);  /* Apply 80 dummy clocks and the card gets ready to receive command */
+
+    ty = 0;
+    if (send_cmd(CMD0, 0) == 1) {           /* Enter Idle state */
+        if (send_cmd(CMD8, 0x1AA) == 1) {   /* SDv2? */
+            rcvr_mmc(buf, 4);                           /* Get trailing return value of R7 resp */
+            if (buf[2] == 0x01 && buf[3] == 0xAA) {     /* The card can work at vdd range of 2.7-3.6V */
+                for (tmr = 1000; tmr; tmr--) {          /* Wait for leaving idle state (ACMD41 with HCS bit) */
+                    if (send_cmd(ACMD41, 1UL << 30) == 0) break;
+                    busy_wait(1);
+                }
+                if (tmr && send_cmd(CMD58, 0) == 0) {   /* Check CCS bit in the OCR */
+                    rcvr_mmc(buf, 4);
+                    ty = (buf[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 */
+            }
+            for (tmr = 1000; tmr; tmr--) {          /* Wait for leaving idle state */
+                if (send_cmd(cmd, 0) == 0) break;
+                busy_wait(1);
+            }
+            if (!tmr || send_cmd(CMD16, 512) != 0)  /* Set R/W block length to 512 */
+                ty = 0;
+        }
+    }
+    CardType = ty;
+    s = ty ? 0 : STA_NOINIT;
+    Stat = s;
+
+    deselect();
+
+    return s;
+}
+
+
+
+/*-----------------------------------------------------------------------*/
+/* Read Sector(s)                                                        */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_read (
+    BYTE drv,           /* Physical drive nmuber (0) */
+    BYTE *buff,         /* Pointer to the data buffer to store read data */
+    LBA_t sector,       /* Start sector number (LBA) */
+    UINT count          /* Sector count (1..128) */
+)
+{
+    BYTE cmd;
+    DWORD sect = (DWORD)sector;
+
+
+    //FIXME if (disk_status(drv) & STA_NOINIT) return RES_NOTRDY;
+    if (!(CardType & CT_BLOCK)) sect *= 512;    /* Convert LBA to byte address if needed */
+
+    cmd = count > 1 ? CMD18 : CMD17;            /*  READ_MULTIPLE_BLOCK : READ_SINGLE_BLOCK */
+    if (send_cmd(cmd, sect) == 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)                                                       */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_write (
+    BYTE drv,           /* Physical drive nmuber (0) */
+    const BYTE *buff,   /* Pointer to the data to be written */
+    LBA_t sector,       /* Start sector number (LBA) */
+    UINT count          /* Sector count (1..128) */
+)
+{
+    DWORD sect = (DWORD)sector;
+
+
+    //FIXME if (disk_status(drv) & STA_NOINIT) return RES_NOTRDY;
+    if (!(CardType & CT_BLOCK)) sect *= 512;    /* Convert LBA to byte address if needed */
+
+    if (count == 1) {   /* Single block write */
+        if ((send_cmd(CMD24, sect) == 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, sect) == 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;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* Miscellaneous Functions                                               */
+/*-----------------------------------------------------------------------*/
+
+DRESULT disk_ioctl (
+    BYTE drv,       /* Physical drive nmuber (0) */
+    BYTE ctrl,      /* Control code */
+    void *buff      /* Buffer to send/receive control data */
+)
+{
+    DRESULT res;
+    BYTE n, csd[16];
+    DWORD cs;
+
+    //FIXME if (disk_status(drv) & STA_NOINIT) return RES_NOTRDY;   /* Check if card is in the socket */
+
+    res = RES_ERROR;
+    switch (ctrl) {
+        case CTRL_SYNC :        /* Make sure that no pending write process */
+            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 */
+                    cs = csd[9] + ((WORD)csd[8] << 8) + ((DWORD)(csd[7] & 63) << 16) + 1;
+                    *(LBA_t*)buff = cs << 10;
+                } else {                    /* SDC ver 1.XX or MMC */
+                    n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
+                    cs = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 3) << 10) + 1;
+                    *(LBA_t*)buff = cs << (n - 9);
+                }
+                res = RES_OK;
+            }
+            break;
+
+        case GET_BLOCK_SIZE :   /* Get erase block size in unit of sector (DWORD) */
+            *(DWORD*)buff = 128;
+            res = RES_OK;
+            break;
+
+        default:
+            res = RES_PARERR;
+    }
+
+    deselect();
+
+    return res;
+}
+
+
+/*-----------------------------------------------------------------------*/
+/* LiteX's BIOS                                                          */
+/*-----------------------------------------------------------------------*/
+
+uint8_t spisdcard_init(void) {
     uint8_t r;
+    /* Set SPI clk freq to 400KHz */
+    spi_set_clk_freq(400000);
 
-    /* Send Read Block */
-    /* CMD17 */
-    spi_xfer(0xff);
-    spi_xfer(0x51);
-    spi_xfer((addr >> 24) & 0xff);
-    spi_xfer((addr >> 16) & 0xff);
-    spi_xfer((addr >>  8) & 0xff);
-    spi_xfer((addr >>  0) & 0xff);
-    spi_xfer(0xff);
-    /* R1, expects 0x00 that indicates the SDCard is processing */
-    r = spisdcard_wait_response();
-    if(r != 0x00)
-        return 0;
-
-    /* Do SPI Xfers on SDCard until 0xfe is received (block start) or timeout is expired */
-    r = spi_xfer(0xff);
-    timeout = 16384;
-    do {
-        r = spi_xfer(0xff);
-        timeout--;
-    } while((r != 0xfe) && (timeout>0));
-    if(r != 0xfe)
-        return 0;
+    /* Initialize the SDCard */
+    r = disk_initialize(0);
 
-    /* Read the block from the SDCard and copy it to the buffer */
-    for(i=0; i<512; i++)
-        buf[i] = spi_xfer(0xff);
+    /* Increase SPI clk freq to 16MHz if successful */
+    if (r == RES_OK) {
+        spi_set_clk_freq(16000000);
+    }
 
-    /* Read the 8 dummy bytes */
-    for(i=0; i<8; i++)
-        r = spi_xfer(0xff);
+    return (r == RES_OK);
+}
 
-    return 1;
+uint8_t spisdcard_read_block(uint32_t addr, uint8_t *buf) {
+    return (disk_read(0, buf, addr, 1) == RES_OK);
 }
 
 #endif