From: Florent Kermarrec Date: Fri, 5 Jun 2020 14:27:38 +0000 (+0200) Subject: software/liblitesdcard: base it on FatFs generic example code + LiteX's SPIMaster... X-Git-Tag: 24jan2021_ls180~215 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=f972c8e45e17e2fb57c3ed048fb0e2701d3f6196;p=litex.git software/liblitesdcard: base it on FatFs generic example code + LiteX's SPIMaster specific functions. --- diff --git a/litex/soc/software/liblitesdcard/spisdcard.c b/litex/soc/software/liblitesdcard/spisdcard.c index 3049037c..86095177 100644 --- a/litex/soc/software/liblitesdcard/spisdcard.c +++ b/litex/soc/software/liblitesdcard/spisdcard.c @@ -1,8 +1,20 @@ -// This file is Copyright (c) 2020 Rob Shelton // This file is Copyright (c) 2020 Florent Kermarrec +// This file is Copyright (c) 2020 Rob Shelton // 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 #include @@ -12,6 +24,8 @@ #include #include +#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 is the command sequense of CMD55-CMD */ + 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