/* Copyright 2005, 2006, 2007 Dennis van Weeren Copyright 2008, 2009 Jakub Bednarski This file is part of Minimig Minimig 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 3 of the License, or (at your option) any later version. Minimig 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, see . */ // --== based on the work by Dennis van Weeren and Jan Derogee ==-- // 2008-10-03 - adaptation for ARM controller // 2009-07-23 - clean-up and some optimizations // 2009-11-22 - multiple sector read implemented // FIXME - get capacity from SD card //1GB: //CSD: //0000: 00 7f 00 32 5b 59 83 bc f6 db ff 9f 96 40 00 93 ...2[Y.��.�.@.� //CID: //0000: 3e 00 00 34 38 32 44 00 00 73 2f 6f 93 00 c7 cd >..482D..s/o�... #include "stdio.h" #include "string.h" #include "spi.h" #include "mmc.h" #include "fat.h" // variables static unsigned char crc; static unsigned long timeout; static unsigned char response; static unsigned char CardType; // internal functions static void MMC_CRC(unsigned char c) RAMFUNC; static unsigned char MMC_Command(unsigned char cmd, unsigned long arg) RAMFUNC; static unsigned char MMC_CMD12(void); unsigned char MMC_CheckCard() { // check for removal of card if((CardType != CARDTYPE_NONE) && !mmc_inserted()) { CardType = CARDTYPE_NONE; return 0; } return 1; } static RAMFUNC char check_card() { // check of card has been removed and try to re-initialize it if(CardType == CARDTYPE_NONE) { iprintf("Card was removed, try to init it\n"); if(!mmc_inserted()) return 0; if(!MMC_Init()) return 0; } return 1; } // init memory card unsigned char MMC_Init(void) { unsigned char n; unsigned char ocr[4]; if(!mmc_inserted()) { iprintf("No card inserted\r"); return(CARDTYPE_NONE); } spi_slow(); // set slow clock DisableCard(); // CS = 1 SPI(0xff); // DI = 1 TIMER_wait(20); // 20ms delay for (n=0; n<10; n++) SPI(0xff); // 80 dummy clocks, DI = 1 TIMER_wait(20); // 20ms delay EnableCard(); CardType = CARDTYPE_NONE; for(n=0; n<16; n++) { TIMER_wait(1); if (MMC_Command(CMD0, 0) == 0x01) break; // try to send CMD0 multiple times } if (n<16) // got CMD0 IDLE response { // idle state timeout = GetTimer(1000); // initialization timeout 1s, 4s doesn't work with the original arm timer if (MMC_Command(CMD8, 0x1AA) == 0x01) // check if the card can operate with 2.7-3.6V power { // SDHC card for (n = 0; n < 4; n++) ocr[n] = SPI(0xFF); // get the rest of R7 response if (ocr[2] == 0x01 && ocr[3] == 0xAA) { // the card can work at 2.7-3.6V iprintf("SDHC card detected\r"); while (!CheckTimer(timeout)) { // now we must wait until CMD41 returns 0 (or timeout elapses) if (MMC_Command(CMD55, 0) == 0x01) { // CMD55 must precede any ACMD command if (MMC_Command(CMD41, 1 << 30) == 0x00) // ACMD41 with HCS bit { // initialization completed if (MMC_Command(CMD58, 0) == 0x00) { // check CCS (Card Capacity Status) bit in the OCR for (n = 0; n < 4; n++) ocr[n] = SPI(0xFF); CardType = (ocr[0] & 0x40) ? CARDTYPE_SDHC : CARDTYPE_SD; // if CCS set then the card is SDHC compatible } else iprintf("CMD58 (READ_OCR) failed!\r"); DisableCard(); // set appropriate SPI speed spi_fast(); return(CardType); } } else { iprintf("CMD55 (APP_CMD) failed!\r"); DisableCard(); return(CARDTYPE_NONE); } } iprintf("SDHC card initialization timed out!\r"); DisableCard(); return(CARDTYPE_NONE); } } // it's not an SDHC card if (MMC_Command(CMD55, 0) == 0x01) { // CMD55 accepted so it's an SD card (or Kingston 128 MB MMC) if (MMC_Command(CMD41, 0) <= 0x01) { // SD card detected - wait for the end of initialization iprintf("SD card detected\r"); while (!CheckTimer(timeout)) { // now we must wait until CMD41 returns 0 (or timeout elapses) if (MMC_Command(CMD55, 0) == 0x01) { // CMD55 must precede any ACMD command if (MMC_Command(CMD41, 0) == 0x00) { // initialization completed if (MMC_Command(CMD16, 512) != 0x00) //set block length iprintf("CMD16 (SET_BLOCKLEN) failed!\r"); DisableCard(); // set appropriate SPI speed spi_fast(); CardType = CARDTYPE_SD; return(CardType); } } else { iprintf("CMD55 (APP_CMD) failed!\r"); DisableCard(); return(CARDTYPE_NONE); } } iprintf("SD card initialization timed out!\r"); DisableCard(); return(CARDTYPE_NONE); } } // it's not an SD card iprintf("MMC card detected\r"); while (!CheckTimer(timeout)) { // now we must wait until CMD1 returns 0 (or timeout elapses) if (MMC_Command(CMD1, 0) == 0x00) { // initialization completed if (MMC_Command(CMD16, 512) != 0x00) // set block length iprintf("CMD16 (SET_BLOCKLEN) failed!\r"); DisableCard(); // set appropriate SPI speed spi_fast_mmc(); CardType = CARDTYPE_MMC; return(CardType); } } iprintf("MMC card initialization timed out!\r"); DisableCard(); return(CARDTYPE_NONE); } DisableCard(); iprintf("No memory card detected!\r"); return(CARDTYPE_NONE); } // Read single 512-byte block RAMFUNC unsigned char MMC_Read(unsigned long lba, unsigned char *pReadBuffer) { // if pReadBuffer is NULL then use direct to the FPGA transfer mode (FPGA2 asserted) // check of card has been removed and try to re-initialize it if(!check_card()) return 0; unsigned long i; unsigned char *p; if (CardType != CARDTYPE_SDHC) // SDHC cards are addressed in sectors not bytes lba = lba << 9; // otherwise convert sector adddress to byte address EnableCard(); if (MMC_Command(CMD17, lba)) { // iprintf("CMD17 (READ_BLOCK): invalid response 0x%02X (lba=%lu)\r", response, lba); DisableCard(); return(0); } // now we are waiting for data token, it takes around 300us timeout = 0; while ((SPI(0xFF)) != 0xFE) { if (timeout++ >= 1000000) // we can't wait forever { // iprintf("CMD17 (READ_BLOCK): no data token! (lba=%lu)\r", lba); DisableCard(); return(0); } } if (pReadBuffer == 0) { // in this mode we do not receive data, instead the FPGA captures directly the data stream transmitted by the SD/MMC card EnableDMode(); spi_block(511); SPI(0xff); // dummy write for 4096 clocks SPI(0xff); DisableDMode(); } else spi_block_read(pReadBuffer); SPI(0xFF); // read CRC lo byte SPI(0xFF); // read CRC hi byte DisableCard(); return(1); } static unsigned char MMC_GetCXD(unsigned char cmd, unsigned char *ptr) { int i; EnableCard(); if (MMC_Command(cmd,0)) { iprintf("CMD%d (GET_C%cD): invalid response 0x%02X \r", (cmd==CMD9)?9:10, (cmd==CMD9)?'S':'I', response); DisableCard(); return(0); } // now we are waiting for data token, it takes around 300us timeout = 0; while ((SPI(0xFF)) != 0xFE) { if (timeout++ >= 1000000) { // we can't wait forever iprintf("CMD%d (GET_C%cD): no data token!\r", (cmd==CMD9)?9:10, (cmd==CMD9)?'S':'I'); DisableCard(); return(0); } } for (i = 0; i < 16; i++) ptr[i]=SPI(0xFF); DisableCard(); return(1); } // Read CSD register unsigned char MMC_GetCSD(unsigned char *csd) { return MMC_GetCXD(CMD9, csd); } // Read CID register unsigned char MMC_GetCID(unsigned char *cid) { return MMC_GetCXD(CMD10, cid); } // MMC get capacity unsigned long MMC_GetCapacity() { unsigned long result=0; unsigned char CSDData[16]; MMC_GetCSD(CSDData); if ((CSDData[0] & 0xC0)==0x40) //CSD Version 2.0 - SDHC { result=(CSDData[7]&0x3f)<<26; result|=CSDData[8]<<18; result|=CSDData[9]<<10; result+=1024; return(result); } else { int blocksize=CSDData[5]&15; // READ_BL_LEN blocksize=1<<(blocksize-9); // Now a scalar: physical block size / 512. result=(CSDData[6]&3)<<10; result|=CSDData[7]<<2; result|=(CSDData[8]>>6)&3; // result now contains C_SIZE int cmult=(CSDData[9]&3)<<1; cmult|=(CSDData[10]>>7) & 1; ++result; result<<=cmult+2; return(result); } } // read multiple 512-byte blocks unsigned char MMC_ReadMultiple(unsigned long lba, unsigned char *pReadBuffer, unsigned long nBlockCount) { // if pReadBuffer is NULL then use direct to the FPGA transfer mode (FPGA2 asserted) // check of card has been removed and try to re-initialize it if(!check_card()) return 0; unsigned long i; unsigned char *p; if (CardType != CARDTYPE_SDHC) // SDHC cards are addressed in sectors not bytes lba = lba << 9; // otherwise convert sector adddress to byte address EnableCard(); if (MMC_Command(CMD18, lba)) { iprintf("CMD18 (READ_MULTIPLE_BLOCK): invalid response 0x%02X (lba=%u)\r", response, lba); DisableCard(); return(0); } while (nBlockCount--) { // now we are waiting for data token, it takes around 300us timeout = 0; while ((SPI(0xFF)) != 0xFE) { if (timeout++ >= 1000000) // we can't wait forever { iprintf("CMD18 (READ_MULTIPLE_BLOCK): no data token! (lba=%u)\r", lba); DisableCard(); return(0); } } if (pReadBuffer == 0) { // in this mode we do not receive data, instead the FPGA captures directly the data stream transmitted by the SD/MMC card EnableDMode(); spi_block(511); SPI(0xff); // dummy write for 4096 clocks SPI(0xff); DisableDMode(); } else { spi_block_read(pReadBuffer); pReadBuffer += 512; // point to next sector } SPI(0xFF); // read CRC lo byte SPI(0xFF); // read CRC hi byte } MMC_CMD12(); // stop multi block transmission DisableCard(); return(1); } // write 512-byte block unsigned char MMC_Write(unsigned long lba, unsigned char *pWriteBuffer) { unsigned long i; // check of card has been removed and try to re-initialize it if(!check_card()) return 0; if (CardType != CARDTYPE_SDHC) // SDHC cards are addressed in sectors not bytes lba = lba << 9; // otherwise convert sector adddress to byte address EnableCard(); if (MMC_Command(CMD24, lba)) { iprintf("CMD24 (WRITE_BLOCK): invalid response 0x%02X (lba=%lu)\r", response, lba); DisableCard(); return(0); } SPI(0xFF); // one byte gap SPI(0xFE); // send Data Token // send sector bytes spi_block_write(pWriteBuffer); spi_wait4xfer_end(); SPI(0xFF); // send CRC lo byte SPI(0xFF); // send CRC hi byte response = SPI(0xFF); // read packet response // Status codes // 010 = Data accepted // 101 = Data rejected due to CRC error // 110 = Data rejected due to write error response &= 0x1F; if (response != 0x05) { iprintf("CMD24 (WRITE_BLOCK): invalid status 0x%02X (lba=%lu)\r", response, lba); DisableCard(); return(0); } // TODO: Move this to the beginning of MMC_Command to interleave Core and SD card better timeout = 0; while (spi_in() == 0x00) // wait until the card is not busy { if (timeout++ >= 1000000) { iprintf("CMD24 (WRITE_BLOCK): busy wait timeout! (lba=%lu)\r", lba); DisableCard(); return(0); } } // real life values here are ~500 until card becomes not busy again // iprintf("W:%d\n", timeout); DisableCard(); return(1); } // MMC command static RAMFUNC unsigned char MMC_Command(unsigned char cmd, unsigned long arg) { unsigned char c,b; crc = 0; // flush spi, give card a moment to wake up (needed for old 2GB Panasonic card) // spi_n(0xff, 8); // this is not flash save if not in ram for(b=0;b<8;b++) SPI(0xff); SPI(cmd); MMC_CRC(cmd); #if 1 // code 100 bytes smaller than below for(b=0;b<4;b++) { c = ((unsigned char*)&arg)[3]; SPI(c); MMC_CRC(c); arg <<= 8; } #else c = (unsigned char)(arg >> 24); SPI(c); MMC_CRC(c); c = (unsigned char)(arg >> 16); SPI(c); MMC_CRC(c); c = (unsigned char)(arg >> 8); SPI(c); MMC_CRC(c); c = (unsigned char)(arg); SPI(c); MMC_CRC(c); #endif crc <<= 1; crc++; SPI(crc); unsigned char Ncr = 100; // Ncr = 0..8 (SD) / 1..8 (MMC) do response = SPI(0xFF); // get response while (response == 0xFF && Ncr--); return response; } // stop multi block data transmission static unsigned char MMC_CMD12(void) { SPI(CMD12); // command SPI(0x00); SPI(0x00); SPI(0x00); SPI(0x00); SPI(0x00); // dummy CRC7 SPI(0xFF); // skip stuff byte unsigned char Ncr = 100; // Ncr = 0..8 (SD) / 1..8 (MMC) do { response = SPI(0xFF); // get response // RS232(response); } while (response == 0xFF && Ncr--); timeout = 0; while ((SPI(0xFF)) == 0x00) // wait until the card is not busy { // RS232('+'); if (timeout++ >= 1000000) { // iprintf("CMD12 (STOP_TRANSMISSION): busy wait timeout!\r"); DisableCard(); return(0); } } return response; } // MMC CRC calc static RAMFUNC void MMC_CRC(unsigned char c) { unsigned char i; for (i = 0; i < 8; i++) { crc <<= 1; if (c & 0x80) crc ^= 0x09; if (crc & 0x80) crc ^= 0x09; c <<= 1; } } unsigned char MMC_IsSDHC(void) { return(CardType == CARDTYPE_SDHC); }